Enhance Telegram bot functionality and configuration options
CI / Backend (build + test) (push) Successful in 1m16s
CI / Frontend (lint + typecheck + build) (push) Successful in 35s

- Added new inline button features to the `/configs` command, allowing users to view their configurations in a single message with active links and a back button.
- Implemented a menu for unlinking Telegram accounts, providing a clearer user experience when managing account connections.
- Updated the `.env.example` file to include a new `Telegram__PublicSiteUrl` setting, enabling a button for accessing the panel's website directly from the bot.
- Enhanced documentation to reflect the new features and configuration options available in the Telegram bot.
This commit is contained in:
Leonid Pershin
2026-07-02 19:39:14 +03:00
parent cf3d8fcad8
commit 5b398f9c59
4 changed files with 165 additions and 52 deletions
+38 -23
View File
@@ -6,10 +6,13 @@ Telegram-бот — **второй канал доставки** (presentation-
## Возможности (реализовано)
1. **Мои конфиги**`/configs` присылает по сообщению на конфиг (метка/локация, протокол, статус) с
inline-кнопкой **«🔗 Показать ссылку»**; connection string приходит отдельным сообщением только по
нажатию (не светится в списке/истории чата без явного действия пользователя). Только текстовая
ссылка, без QR-картинки — за QR пользователь идёт на сайт. Доступно только привязанному аккаунту.
1. **Мои конфиги**`/configs` (или кнопка «📋 Мои конфиги» из главного меню) присылает **одно**
сообщение со списком (метка/локация, протокол, статус) и по кнопке `🔗 {Label}` на каждый активный
конфиг + кнопкой **«🔙 В меню»** внизу. Нажатие на конфиг **редактирует то же сообщение**: дописывает
connection string моноширинным блоком (тап = копирование целиком) и убирает именно эту кнопку —
остальные конфиги и «В меню» остаются на месте. Ничего не светится без явного нажатия, отдельных
сообщений не плодится. Только текстовая ссылка, без QR-картинки — за QR пользователь идёт на сайт.
Доступно только привязанному аккаунту.
2. **Авторизация через Telegram (passwordless)** — вход на сайт без пароля: инициируется на сайте,
подтверждается в боте кнопками «Подтвердить/Отклонить». Требует предварительной привязки Telegram.
3. **Админ: обработка запросов активации** — админ (по Telegram id из `Telegram__AdminTelegramUserIds`)
@@ -17,13 +20,19 @@ Telegram-бот — **второй канал доставки** (presentation-
«✅ Активировать / ❌ Отклонить» прямо в сообщении. `/requests` показывает все ожидающие запросы по требованию.
4. **DM-уведомления пользователю** (если Telegram привязан): активация аккаунта, блокировка,
принудительный отзыв конфига админом.
5. **Отвязка**`/unlink`.
5. **Отвязка**`/unlink` или кнопка «🔓 Отвязать Telegram» из главного меню (редактирует то же
сообщение в подтверждение + меню для непривязанного состояния).
6. **Регистрация прямо из бота** — кнопка «📝 Зарегистрироваться» показывается там, где боту нужен
привязанный аккаунт, а Telegram ещё не привязан (`/start`, `/help`, `/configs`, запрос passwordless-
входа). Логин — `@username` из Telegram; если его нет или он уже занят на сайте — используется
Telegram id (гарантированно уникален). Пароль генерируется и присылается в чат один раз — сохраните
его сразу, при желании логин и пароль можно сменить в Настройках на сайте. Новый аккаунт получает
роль `user` и `IsActivated = false` — активация нужна как для обычной регистрации на сайте.
7. **Главное меню**`/start`/`/help` показывают одно сообщение с кнопками вместо текстового списка
команд: привязанному аккаунту — «📋 Мои конфиги» / «🔓 Отвязать Telegram», непривязанному — «📝
Зарегистрироваться»; плюс кнопка «🌐 Сайт панели» со ссылкой на сайт, если задан `Telegram__PublicSiteUrl`
(пусто — кнопки нет). Слэш-команды `/configs`/`/unlink` продолжают работать как раньше — кнопки лишь
вызывают те же обработчики через callback (`menu:configs`/`menu:unlink`/`menu:back`).
**Не реализовано / backlog:**
- QR-картинкой и агрегированная подписка в самом боте (только текстовая ссылка на конфиг по кнопке).
@@ -39,7 +48,7 @@ Telegram-бот — **второй канал доставки** (presentation-
`PnvPanel.Api/Telegram/`) — условие «фронт+бек в одном контейнере».
- Транспорт — **только long polling** (`ITelegramBotClient.ReceiveAsync`). Webhook рассматривался на
этапе планирования, но не реализован: `TelegramOptions` (`Infrastructure/Telegram/TelegramOptions.cs`)
содержит только `BotToken`, `BotUsername`, `PublicSiteUrl`, `AdminTelegramUserIds` — полей
содержит только `BotToken`, `ProxyUrl`, `PublicSiteUrl`, `AdminTelegramUserIds` — полей
`Mode`/`WebhookUrl`/`WebhookSecret` в коде нет.
- Библиотека — **Telegram.Bot**. Каждый апдейт обрабатывается в своём DI-scope (`PnvBotUpdateHandler`,
как HTTP-запрос — свежие scoped-сервисы на апдейт).
@@ -122,16 +131,19 @@ Telegram ──updates──► TelegramBotHostedService → PnvBotUpdateHandl
## Флоу 4 — Просмотр конфигов в боте
1. Привязанный пользователь: `/configs`.
1. Привязанный пользователь: `/configs` (текстовая команда → новое сообщение) или «📋 Мои конфиги» из
главного меню (callback `menu:configs` → редактирует текущее сообщение, см. `BuildConfigsMenuAsync`).
2. Бот вызывает `GetMyConfigsQuery` (тот же, что и веб) от пользователя, найденного по `TelegramUserId`.
3. Ответ — отдельное сообщение на каждый конфиг: `• {Label ?? Location} ({Protocol}) — {Status}` +
inline-кнопка «🔗 Показать ссылку» (кроме отозванных — там кнопки нет). Если конфигов нет —
«У вас пока нет конфигов.»
4. Нажатие кнопки → callback `cfg:link:{configId}` → бот вызывает `GetConfigLinkQuery` (тот же, что
эндпоинт `/api/configs/{id}/link`) от текущего пользователя и **редактирует то же сообщение**
(`EditMessageText`), дописывая ссылку моноширинным блоком (`<code>`, тап = копирование целиком) и
убирая кнопку — не плодит отдельное сообщение с сырым URL. Ссылка не раскрывается нигде до явного
нажатия. QR-картинки нет — только текст.
3. Текст — **одно** сообщение: `Ваши конфиги:` + по строке `• {Label ?? Location} ({Protocol}) — {Status}`.
Клавиатура — по кнопке `🔗 {Label}` на каждый **не отозванный** конфиг + «🔙 В меню» внизу. Если
конфигов нет — «У вас пока нет конфигов.» с той же кнопкой «В меню».
4. Нажатие `🔗 {Label}` → callback `cfg:link:{configId}` → бот вызывает `GetConfigLinkQuery` (тот же,
что эндпоинт `/api/configs/{id}/link`) от текущего пользователя и **редактирует то же сообщение**
(`EditMessageText`): дописывает ссылку моноширинным блоком (`<code>`, тап = копирование целиком) и
убирает **именно эту** кнопку из клавиатуры (`InlineKeyboardMarkup.InlineKeyboard`, фильтр по
`CallbackData`) — остальные конфиги и «В меню» остаются кликабельными. Ссылка не раскрывается нигде
до явного нажатия, отдельных сообщений не плодится. QR-картинки нет — только текст.
5. «🔙 В меню» (`menu:back`) — редактирует сообщение обратно в главное меню (`BuildMainMenu`).
## Флоу 5 — Обработка активации админом в боте
@@ -157,13 +169,17 @@ Telegram ──updates──► TelegramBotHostedService → PnvBotUpdateHandl
| `/start` | Приветствие + справка по командам | нет |
| `/start link_<token>` | Привязка аккаунта по токену | нет |
| `/start login_<requestId>` | Подтверждение passwordless-входа (deep-link с сайта) | да |
| `/configs` | Список конфигов с кнопкой «Показать ссылку» на каждом | да |
| `/unlink` | Отвязать Telegram от аккаунта | да |
| `/configs`, «📋 Мои конфиги» (`menu:configs`) | Список конфигов, кнопка `🔗 {Label}` на каждый активный + «🔙 В меню» | да |
| `/unlink`, «🔓 Отвязать Telegram» (`menu:unlink`) | Отвязать Telegram от аккаунта | да |
| «🔙 В меню» (`menu:back`) | Вернуться из списка конфигов к главному меню (edit-in-place) | нет |
| «🌐 Сайт панели» | Открыть сайт (`InlineKeyboardButton.WithUrl`, только если задан `Telegram__PublicSiteUrl`) | нет |
| `/help` | Справка (то же сообщение, что `/start`) | нет |
| «📝 Зарегистрироваться» (`reg:new`) | Регистрация нового аккаунта прямо из бота (Флоу 3) | нет (нужно, чтобы **не** был привязан) |
| «✅ Активировать»/«❌ Отклонить» | (admin) решение по конкретному запросу активации | админ по env |
| `/requests` | (admin) список ожидающих запросов активации (до 10) | админ по env |
Главное меню (`/start`/`/help`) — см. пункт 7 в «Возможности» выше.
Любой другой текст → «Не понимаю эту команду. /help — список команд.»
## Безопасность
@@ -191,9 +207,8 @@ Telegram ──updates──► TelegramBotHostedService → PnvBotUpdateHandl
"Telegram": {
"BotToken": "…", // секрет; пусто = бот не стартует
"ProxyUrl": "socks5://[user:pass@]host:port", // прокси для запросов к Bot API; пусто = без прокси
"PublicSiteUrl": "https://dashboard.example.com", // кнопка «🌐 Сайт панели» в меню; пусто = кнопки нет
"AdminTelegramUserIds": "123456789,987654321" // через запятую
// "PublicSiteUrl" — поле есть в TelegramOptions, но нигде не читается (мёртвый код,
// не задавай его — эффекта не будет)
}
```
@@ -203,10 +218,10 @@ Username бота для deepLink (кнопка «Привязать Telegram»/
`BotUsername`, убрано: опечатка/лишний пробел в env ломали ссылку, а источник истины и так есть в
самом Telegram). Если `BotToken` пуст или `getMe` не отвечает — `deepLink` в ответах API будет `null`.
Переменные окружения — `Telegram__BotToken`, `Telegram__ProxyUrl`, `Telegram__AdminTelegramUserIds`
(см. [`.env.example`](../.env.example)). Именно они авторизуют админ-кнопки в боте и определяют,
кому слать уведомления о запросах активации — **не** сидируются в БД и не связаны с учёткой
сид-админа (`AdminSeed:*`), это независимый список.
Переменные окружения — `Telegram__BotToken`, `Telegram__ProxyUrl`, `Telegram__PublicSiteUrl`,
`Telegram__AdminTelegramUserIds` (см. [`.env.example`](../.env.example)). `AdminTelegramUserIds`
авторизует админ-кнопки в боте и определяет, кому слать уведомления о запросах активации — **не**
сидируется в БД и не связан с учёткой сид-админа (`AdminSeed:*`), это независимый список.
Сообщения бота **не локализованы** по языку пользователя — все тексты на русском независимо от языка
интерфейса на сайте (в отличие от веба, где RU/EN переключаются). Синхронизация языка бота с вебом —