Files
TeleWave/docs/telegram-bot.md
T
Leonid Pershin 3dbe0b7698
ci / build-backend (push) Successful in 1m21s
ci / build-frontend (push) Successful in 51s
ci / tests (push) Successful in 1m33s
ci / sonar (push) Successful in 3m51s
Enhance TelegramNotifier to manage previous alerts and improve notification clarity
Updated the TelegramNotifier to delete previous alert messages before sending new notifications, ensuring that only the latest alert is visible to subscribers. Introduced a new property in TelegramSubscriber to track the latest alert message ID. Adjusted related tests to verify the new alert management behavior and updated documentation to reflect these changes, enhancing the overall user experience in message handling.
2026-07-31 21:20:25 +03:00

9.6 KiB

Бот расписания в Telegram

Оповещения зрителю: «на канале началось шоу» и «скоро начнётся». Настраивается в админке (вкладка «Телеграм»), подписки собирает сам зритель кнопками в чате.

Что решено и почему

Подписка только по одноразовому коду. Кнопка «Открыть чат с ботом» на странице эфира выдаёт ссылку t.me/бот?start=КОД; код живёт 15 минут, сгорает при первом использовании и привязывает чат к учётной записи TeleWave. Анонимный бот («кто нашёл, тот и подписался») означал бы рассылку неизвестно кому и невозможность отписать чат вместе с блокировкой пользователя. Ссылка выдаётся по нажатию, а не лежит в разметке: в ней пропуск к чужим оповещениям.

Два транспорта, переключателем. Polling — сервер сам ходит за обновлениями: работает из-за NAT и через прокси, и это единственный вариант там, где публичного адреса нет. Webhook — Telegram стучится на наш HTTPS-эндпоинт: дешевле и быстрее, но требует доступного извне домена, и прокси здесь не поможет — соединение устанавливает Telegram. Разговор с ботом при этом один и тот же: оба транспорта приносят одинаковое обновление и разбираются общим кодом.

Прокси — часть настроек, а не окружения. HTTP или SOCKS5 с логином и паролем; клиент собирается на каждый вызов, поэтому смена прокси не требует перезапуска контейнера. Кривой адрес — ошибка, а не повод молча пойти напрямую: «работает в обход прокси» на канале, ради которого прокси и заводили, хуже отказа.

Секреты в базе, наружу не отдаются. Токен и пароль прокси лежат открытым текстом: шифровать их нечем — Data Protection в проекте сознательно не заводили (см. CLAUDE.md). Наружу уходит только признак «задан», а пустое поле в форме означает «оставить как есть». Секрет вебхука генерируется сам и проверяется заголовком X-Telegram-Bot-Api-Secret-Token: адрес рано или поздно окажется в чужих логах, и без секрета туда постучится кто угодно.

Проверка связи — на сохранении. Настройки сразу спрашивают у Telegram getMe: неверный токен или мёртвый прокси иначе всплыли бы через сутки молчания бота. Оттуда же приходит имя бота — без него не собрать ссылку на чат, поэтому кнопка у зрителя появляется только после успешного контакта. Вебхук ставится и снимается тем же сохранением: пока он стоит, Telegram не отдаёт обновления опросом, и переключение режима без этого оставило бы бота немым.

Оповещения

Считаются по материализованной ленте, а не по факту раздачи: лента и есть эфир, а ждать подтверждения от плеера не от кого — зрителей может не быть вовсе.

  • Начало шоу — на смену шоу, а не на каждую запись: четыре серии подряд это одна программа в глазах зрителя, и четыре сообщения о ней читаются как спам.
  • Скоро в эфире — за пять минут до следующей программы.

Граница разобранного (TelegramSettings.NotifiedUntil) двигается на каждом тике, даже когда подписчиков нет: иначе первый же подписавшийся получил бы пачку сообщений про всё, что успело пройти. Догон ограничен пятнадцатью минутами — после долгого простоя бот не станет пересказывать вчерашний эфир.

В чате живёт только последнее оповещение: новое снимает предыдущее (TelegramSubscriber.AlertMessageId). Оповещение отвечает на вопрос «что идёт сейчас», а вчерашнее «сейчас в эфире» на этот вопрос отвечает неправдой — за сутки таких набирается несколько десятков, и найти среди них актуальное нельзя. Счётчик отдельный от одноразовых ответов бота: рассылка приходит сама и не должна сносить программу передач, которую зритель только что запросил.

Меню в чате

/start с кодом привязывает чат и открывает меню; /start без кода — открывает меню уже привязанному. Заголовок канала в меню — это кнопка «программа передач»: ближайшие восемь блоков в часовом поясе канала, идущее сейчас помечено. Подряд идущие серии одного шоу схлопываются в один блок — зритель спрашивает «что дальше», а не «в каком порядке лежат файлы». Программа приходит отдельным сообщением, меню остаётся на месте: иначе после просмотра пришлось бы звать /start, чтобы вернуть кнопки. Меню живёт одним сообщением: нажатие кнопки переписывает его на месте, иначе чат заполняется одинаковыми «ваши подписки» после каждого клика. Кнопка — тумблер: повторное нажатие снимает подписку. /stop останавливает рассылку, не стирая подписки: вернувшись, зритель получит свои каналы, а не пустой список.

Бот убирает за собой. В чате живут ровно три его сообщения: меню подписок, последнее одноразовое (программа передач, подтверждение привязки или отказ) и последнее оповещение рассылки. Новое меню снимает предыдущее (две панели с разными галочками означают, что одна врёт), новая программа — предыдущую программу, новое оповещение — предыдущее оповещение. Идентификаторы всех трёх хранятся у подписчика. Команды зрителя остаются: в личном чате бот вправе удалять только собственные сообщения, и права на чужие у него нет и не будет. Не удалившееся сообщение (зритель убрал его сам, прошло больше 48 часов) уборку не срывает — это не та задача, ради которой стоит рвать разговор.

Данные кнопок несут действие: sub:канал:вид переключает подписку, epg:канал показывает программу. Действие в первом поле, а не «угадаем по числу частей»: кнопок со временем станет больше, и разбор по длине строки сломался бы на первой же новой.

Границы

  • Бот не отвечает на произвольные вопросы и не показывает программу передач — только подписки.
  • Групповые чаты не поддерживаются: подписка привязана к учётке, а в группе её владельца нет.
  • Один чат — один подписчик; повторная привязка по новому коду меняет учётку, а не плодит копии.