Refactor Telegram API and enhance TelegramPanel form handling
Updated the TelegramApi class to improve URL construction for API calls by introducing a new MethodUrl method, ensuring proper handling of bot tokens. Refactored the TelegramPanel component to use a form for input fields, allowing for better user experience and submission handling. Adjusted password input fields to use 'new-password' for improved security. These changes aim to enhance the functionality and usability of the Telegram integration.
This commit is contained in:
@@ -185,11 +185,10 @@ public sealed class TelegramApi(ILogger<TelegramApi> logger) : ITelegramApi
|
|||||||
using var handler = CreateHandler(settings);
|
using var handler = CreateHandler(settings);
|
||||||
using var client = new HttpClient(handler)
|
using var client = new HttpClient(handler)
|
||||||
{
|
{
|
||||||
BaseAddress = new Uri("https://api.telegram.org/"),
|
|
||||||
Timeout = timeout ?? TimeSpan.FromSeconds(30),
|
Timeout = timeout ?? TimeSpan.FromSeconds(30),
|
||||||
};
|
};
|
||||||
|
|
||||||
var url = $"bot{settings.BotToken}/{method}";
|
var url = MethodUrl(settings.BotToken, method);
|
||||||
using var response = payload is null
|
using var response = payload is null
|
||||||
? await client.PostAsync(url, null, cancellationToken)
|
? await client.PostAsync(url, null, cancellationToken)
|
||||||
: await client.PostAsJsonAsync(url, payload, Json, cancellationToken);
|
: await client.PostAsJsonAsync(url, payload, Json, cancellationToken);
|
||||||
@@ -205,6 +204,14 @@ public sealed class TelegramApi(ILogger<TelegramApi> logger) : ITelegramApi
|
|||||||
return await response.Content.ReadFromJsonAsync<T>(Json, cancellationToken);
|
return await response.Content.ReadFromJsonAsync<T>(Json, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Адрес метода — абсолютным, а не относительным к BaseAddress. Токен содержит двоеточие
|
||||||
|
/// («8613942817:AAH…»), и как относительный путь «bot8613942817:AAH…/getUpdates» разбирается
|
||||||
|
/// в схему «bot8613942817»: запрос падает с «scheme is not supported», не дойдя до сети.
|
||||||
|
/// </summary>
|
||||||
|
public static Uri MethodUrl(string token, string method) =>
|
||||||
|
new($"https://api.telegram.org/bot{token}/{method}");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Транспорт с прокси, если он задан. SOCKS5 поддерживает сам <see cref="WebProxy"/> начиная
|
/// Транспорт с прокси, если он задан. SOCKS5 поддерживает сам <see cref="WebProxy"/> начиная
|
||||||
/// с .NET 6 — достаточно схемы в адресе, отдельной библиотеки не нужно.
|
/// с .NET 6 — достаточно схемы в адресе, отдельной библиотеки не нужно.
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
using TeleWave.Infrastructure.Notifications;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace TeleWave.Application.Tests.Notifications;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Адрес метода Bot API. Отдельный тест на одну строку — потому что ошибка в ней не видна ни при
|
||||||
|
/// сборке, ни в юнит-тестах логики: токен содержит двоеточие, и относительный путь превращается
|
||||||
|
/// в «схему» ещё до обращения к сети.
|
||||||
|
/// </summary>
|
||||||
|
public class TelegramApiUrlTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void MethodUrl_KeepsTokenInPath_AndStaysHttps()
|
||||||
|
{
|
||||||
|
var url = TelegramApi.MethodUrl("8613942817:AAHrandom-part_x", "getUpdates");
|
||||||
|
|
||||||
|
Assert.Equal("https", url.Scheme);
|
||||||
|
Assert.Equal("api.telegram.org", url.Host);
|
||||||
|
Assert.Equal("/bot8613942817:AAHrandom-part_x/getUpdates", url.AbsolutePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,7 +101,15 @@ export function TelegramPanel() {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="crt-panel flex flex-col gap-4 rounded-md p-4 text-sm">
|
{/* Форма, а не просто панель: поле пароля вне <form> браузер считает ошибкой разметки —
|
||||||
|
менеджеру паролей не к чему его привязать. Заодно работает отправка по Enter. */}
|
||||||
|
<form
|
||||||
|
className="crt-panel flex flex-col gap-4 rounded-md p-4 text-sm"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
save.mutate()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<label className="flex items-center gap-2">
|
<label className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
@@ -116,7 +124,7 @@ export function TelegramPanel() {
|
|||||||
<Label>{t('admin.telegram.token')}</Label>
|
<Label>{t('admin.telegram.token')}</Label>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="new-password"
|
||||||
placeholder={settings?.hasToken ? t('admin.telegram.tokenKept') : '123456:ABC...'}
|
placeholder={settings?.hasToken ? t('admin.telegram.tokenKept') : '123456:ABC...'}
|
||||||
value={form.botToken ?? ''}
|
value={form.botToken ?? ''}
|
||||||
onChange={(e) => patch({ botToken: trimmed(e.target.value) })}
|
onChange={(e) => patch({ botToken: trimmed(e.target.value) })}
|
||||||
@@ -212,7 +220,7 @@ export function TelegramPanel() {
|
|||||||
<Label>{t('admin.telegram.proxyPassword')}</Label>
|
<Label>{t('admin.telegram.proxyPassword')}</Label>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="new-password"
|
||||||
placeholder={settings?.hasProxyPassword ? t('admin.telegram.tokenKept') : undefined}
|
placeholder={settings?.hasProxyPassword ? t('admin.telegram.tokenKept') : undefined}
|
||||||
value={form.proxyPassword ?? ''}
|
value={form.proxyPassword ?? ''}
|
||||||
onChange={(e) => patch({ proxyPassword: trimmed(e.target.value) })}
|
onChange={(e) => patch({ proxyPassword: trimmed(e.target.value) })}
|
||||||
@@ -222,7 +230,7 @@ export function TelegramPanel() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<Button size="sm" disabled={save.isPending} onClick={() => save.mutate()}>
|
<Button size="sm" type="submit" disabled={save.isPending}>
|
||||||
{t('common.save')}
|
{t('common.save')}
|
||||||
</Button>
|
</Button>
|
||||||
{settings?.lastContactAt && (
|
{settings?.lastContactAt && (
|
||||||
@@ -231,7 +239,7 @@ export function TelegramPanel() {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
<h3 className="text-sm font-semibold uppercase tracking-wide text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user