Implement Telegram bot configuration updates and user messaging enhancements
CI / Backend (build + test) (push) Successful in 1m23s
CI / Frontend (lint + typecheck + build) (push) Successful in 30s

- Added inline button functionality to the `/configs` command, allowing users to request connection strings for their configurations without displaying them in chat history.
- Introduced a constant message for unlinked Telegram accounts to improve user understanding of the linking process.
- Updated the handling of configuration messages to include inline buttons for better user interaction and experience.
This commit is contained in:
Leonid Pershin
2026-07-02 18:29:32 +03:00
parent 85becacea5
commit 1452e5c4af
3 changed files with 57 additions and 19 deletions
@@ -2,10 +2,12 @@ using Microsoft.Extensions.Options;
using PnvPanel.Application.Admin.Activation;
using PnvPanel.Application.Common.Interfaces;
using PnvPanel.Application.Common.Messaging;
using PnvPanel.Application.Configs.GetConfigLink;
using PnvPanel.Application.Configs.GetMyConfigs;
using PnvPanel.Application.Telegram;
using PnvPanel.Application.Telegram.Bot;
using PnvPanel.Domain.Activation;
using PnvPanel.Domain.Configs;
using PnvPanel.Infrastructure.Telegram;
using Telegram.Bot;
using Telegram.Bot.Polling;
@@ -23,6 +25,11 @@ public sealed class PnvBotUpdateHandler(
IServiceScopeFactory scopeFactory, IOptions<TelegramOptions> options, ILogger<PnvBotUpdateHandler> logger)
: IUpdateHandler
{
// Показывается везде, где боту нужен привязанный аккаунт, а его нет — явно проговариваем оба шага,
// иначе новые пользователи не понимают, что сначала нужен обычный аккаунт на сайте.
private const string NotLinkedMessage =
"Сначала зарегистрируйтесь и войдите на сайте, затем привяжите Telegram: Настройки → «Привязать Telegram».";
public async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
@@ -139,6 +146,24 @@ public sealed class PnvBotUpdateHandler(
await botClient.SendMessage(chatId.Value, text, cancellationToken: cancellationToken);
}
break;
}
case "cfg":
{
if (!await TrySetCurrentUserAsync(services, fromId, cancellationToken))
{
await botClient.AnswerCallbackQuery(callback.Id, "Telegram не привязан.", cancellationToken: cancellationToken);
return;
}
var linkResult = await sender.Send(new GetConfigLinkQuery(requestId), cancellationToken);
await botClient.AnswerCallbackQuery(callback.Id, cancellationToken: cancellationToken);
await botClient.SendMessage(
chatId.Value,
linkResult.IsSuccess ? linkResult.Value.ConnectionString : $"Не удалось получить ссылку: {linkResult.Error.Message}",
cancellationToken: cancellationToken);
break;
}
}
@@ -171,7 +196,7 @@ public sealed class PnvBotUpdateHandler(
var userId = await identityService.FindUserIdByTelegramUserIdAsync(fromId, cancellationToken);
if (userId is null)
{
await botClient.SendMessage(chatId, "Сначала привяжите Telegram к аккаунту на сайте.", cancellationToken: cancellationToken);
await botClient.SendMessage(chatId, NotLinkedMessage, cancellationToken: cancellationToken);
return;
}
@@ -191,9 +216,7 @@ public sealed class PnvBotUpdateHandler(
{
if (!await TrySetCurrentUserAsync(services, fromId, cancellationToken))
{
await botClient.SendMessage(
chatId, "Сначала привяжите Telegram к аккаунту на сайте (Настройки → Привязать Telegram).",
cancellationToken: cancellationToken);
await botClient.SendMessage(chatId, NotLinkedMessage, cancellationToken: cancellationToken);
return;
}
@@ -206,9 +229,17 @@ public sealed class PnvBotUpdateHandler(
return;
}
var lines = result.Value.Configs.Select(c =>
$"• {c.Label ?? c.Location} ({c.Protocol}) — {c.Status}");
await botClient.SendMessage(chatId, "Ваши конфиги:\n" + string.Join('\n', lines), cancellationToken: cancellationToken);
foreach (var config in result.Value.Configs)
{
var text = $"• {config.Label ?? config.Location} ({config.Protocol}) — {config.Status}";
// Отозванному конфигу нечего показывать — кнопку не даём.
InlineKeyboardMarkup? keyboard = config.Status == ConfigStatus.Revoked
? null
: new InlineKeyboardMarkup(new[] { InlineKeyboardButton.WithCallbackData("🔗 Показать ссылку", $"cfg:link:{config.Id}") });
await botClient.SendMessage(chatId, text, replyMarkup: keyboard, cancellationToken: cancellationToken);
}
}
private async Task HandleUnlinkAsync(