🎟

Бот доступа к стрим-сервису: вход по коду из Telegram

собственный бот для Переговорки (ToshaStream): вход по коду, заявки, роли

Telegram-бот управления аккаунтами стрим-сервиса: привязка к Telegram, вход на сайт по одноразовому коду, одобрение заявок и роли из чата. Python, aiogram 3, без своей базы, связь с сервисом через frp-туннель.

В продакшене · собственный продукт
Без своей базы
бот ходит во внутренний API сервиса: аккаунты хранятся в одном месте
11 команд
доступны любому участнику: привязка, вход по коду, профиль, имя, аватарка, пароль, выход, уведомления
120 секунд
через столько бот удаляет из чата сообщение с выданным паролем

Задача

У сервиса чата, голосовой связи и эфира для своей команды (это Переговорка, она же ToshaStream, отдельный кейс) регистрация была открыта любому, кому переслали ссылку. Владелец узнавал о новом аккаунте постфактум. Заявки терялись, а любая мелочь вроде смены имени или пароля требовала прийти к владельцу или зайти в веб-админку.

Нужно было привязать каждый аккаунт сервиса к живому человеку в Telegram. Участнику: вход без пароля по одноразовому коду, смена пароля и имени без участия владельца. Владельцу: заявки на одобрение, список аккаунтов и уведомления о событиях сервиса. Исходное задание оформлено в репозитории как отдельный документ технического задания.

Решение

Stream Bot — собственный Telegram-бот управления аккаунтами. Реализовано в версии 1.2.0:

Участнику

  • Привязка аккаунта. /link: бот спрашивает логин (ищется и по логину, и по отображаемому имени) и выдаёт код для ввода на сайте.
  • Вход без пароля. /login присылает одноразовый код для входа на сайт.
  • Профиль и имя. /me показывает профиль, /name меняет отображаемое имя; без аргумента бот спрашивает имя отдельным сообщением.
  • Аватарка. /avatar берёт аватарку сервиса из фото профиля Telegram.
  • Пароль. /password выдаёт новый пароль веб-входа; сообщение с ним бот удаляет через 120 секунд.
  • Выход везде. /logout закрывает все веб-сессии.
  • Уведомления. /notify — личные настройки оповещений об эфире и рассылках владельца.

Владельцу (только Telegram-ID из настройки):

  • /users — список аккаунтов постранично; /pending — заявки с кнопками «Одобрить» и «Отклонить»; /approve, /deny, /role, /kick, /sessions <логин>.
  • /stats — кто сейчас на сервисе: зрители и голосовые каналы.
  • /broadcast — рассылка любого сообщения (текст, картинка, голосовое) тем, кто не отключил рассылки, с подтверждением.
  • /announce — общий выключатель оповещений о начале эфира.
  • /version — скрытая команда: версия запущенного экземпляра.

Постоянное меню на кнопках строится по роли: непривязанному человеку видна одна кнопка привязки, владельцу добавляется ряд «Аккаунты», «Заявки», «Кто на сервисе».

Как это устроено

Вход на сайт по коду из Telegram

Аккаунт сервиса привязывается к Telegram один раз через /link. После этого код для входа выдаётся по /login, а подтверждения привязки и входа бот присылает самому человеку. Код — это одноразовый набор символов: его вводят на сайте вместо пароля. Вход по логину и паролю остаётся отдельным способом, поэтому остановка бота не закрывает сервис.

Бот без своей базы: внутренний API с общим секретом

Бот не хранит аккаунты и не читает базу сервиса напрямую. Он обращается по HTTP во внутренний API сервиса (API — набор адресов, через которые одна программа просит другую что-то сделать), а каждый запрос подписан общим секретом в заголовке. Владелец данных один, поэтому расхождений между ботом и сервисом нет. Даже настройки уведомлений хранит сервис: в его таблице настроек лежат поля для оповещений об эфире и рассылок.

События сервиса: опрос очереди раз в 20 секунд

Бот раз в 20 секунд запрашивает у сервиса очередь событий. Владельцу приходят заявки на регистрацию, вход с нового адреса, серия неудачных входов, начало и конец эфира. Человеку приходят подтверждение привязки и вход по коду. Если туннель оборвался, бот отвечает «Сервис чата сейчас не отвечает», а после восстановления дочитывает очередь с прежнего курсора. При старте бота хвост очереди пропускается, чтобы не пересылать старые события.

frp-туннель между сервером сервиса и ботом

Сервис стоит на российском сервере, откуда Telegram недоступен. Поэтому бот работает на зарубежной машине в отдельном проекте Docker Compose. Связь строит frp — программа обратного туннеля: клиент на стороне сервиса сам, исходящим соединением, подключается к серверу на стороне бота и отдаёт внутренний API через TLS. Управляющий порт на стороне бота открыт только для адреса сервера сервиса. Более ранний вариант — обратный SSH-туннель — оставлен в репозитории как запасной.

Long polling: бот без открытого порта

Бот получает сообщения методом long polling: сам держит запрос к Telegram и ждёт ответа. Поэтому ему не нужны ни публичный адрес, ни сертификат, ни webhook. Состояния диалогов (например, «бот ждёт имя») хранятся в памяти, и перезапуск обрывает незавершённые диалоги.

Скрытые команды владельца

Команды владельца не показываются в меню. Для чужого Telegram-ID на /users или /version бот отвечает так, как отвечал бы на несуществующую команду. Права определяются переменной с ID владельца: без неё команды владельца и уведомления выключены.

Что ещё лежит в репозитории

Кроме самого бота в каталоге deploy/ хранятся патчи и новые файлы сервиса чата (Go и JS), которыми он дорабатывался на боевом сервере: эмоции, ответы и реакции, поиск по истории, удаление сообщений, вложения, показ экрана, монитор качества связи, записи эфиров, сторож и ночные бэкапы. Там же конфигурация туннеля. Полного исходного кода сервиса в репозитории нет.

Результат

  • Аккаунт привязан к живому человеку в Telegram, а вход без пароля работает по одноразовому коду.
  • Заявки на регистрацию приходят владельцу в чат и решаются кнопками или командами.
  • Смена имени, пароля, выход из всех сессий и права доступа выполняются из чата, без веб-админки.
  • У бота нет собственной базы: аккаунты хранятся в одном месте, в сервисе.
  • Остановка бота не останавливает сайт, чат, голосовую и эфир.
  • Бот работает в Docker от пользователя без root, версия пишется в лог при старте.

Технологии: что и зачем

  • Python 3.12 и aiogram 3.15 — асинхронный Telegram-бот, диалоги на конечных автоматах состояний.
  • aiohttp — клиент внутреннего API сервиса и long polling.
  • Внутренний API сервиса с общим секретом — единственный источник данных об аккаунтах.
  • frp — туннель между российским сервером сервиса и зарубежным сервером бота, через TLS.
  • Docker — образ python:3.12-slim, процесс без root, TZ=Europe/Moscow.

Статус

Собственный продукт, часть стенда Переговорки (ToshaStream). Текущая версия 1.2.0 от 10 сентября 2026 года добавила /notify, /broadcast, /announce и привязку по отображаемому имени. С 14 сентября в коде без повышения версии: команда /avatar и доработки самого сервиса чата в deploy/.

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

Вопросы по проекту

Как сделать вход на сайт по коду из Telegram без пароля?
Участник привязывает аккаунт командой /link: бот спрашивает логин или отображаемое имя и выдаёт код, который вводится на сайте. Дальше команда /login присылает одноразовый код для входа без пароля. Подтверждение привязки и входа приходит самому человеку в чат.
Как владельцу одобрять новые аккаунты стрим-сервиса из Telegram?
Заявка на регистрацию приходит владельцу в чат, команда /pending показывает все ожидающие с кнопками «Одобрить» и «Отклонить». То же можно сделать командами /approve и /deny. Ещё владельцу приходят уведомления о входе с нового адреса, серии неудачных входов, начале и конце эфира.
Где бот хранит аккаунты и пароли?
Нигде: своей базы у бота нет. Все действия идут по HTTP во внутренний API сервиса с общим секретом в заголовке. Даже настройки уведомлений хранит сам сервис, а не бот.
Что видит и может обычный пользователь, а что только владелец?
Участник управляет своим аккаунтом: /link, /login, /me, /name, /avatar, /password, /logout, /notify. Команды владельца (/users, /pending, /approve, /deny, /role, /kick, /sessions, /stats, /broadcast, /announce) доступны только Telegram-ID из настройки, для остальных бот отвечает как на опечатку.
Что будет с сайтом и голосовой, если бот остановится?
Сайт, чат, голосовая и эфир продолжают работать. При обрыве туннеля бот отвечает «Сервис чата сейчас не отвечает», а после восстановления дочитывает очередь событий с прежнего места. Незавершённые диалоги при перезапуске бота обрываются: их состояние хранится в памяти.
Как бот связан с сервером, если Telegram с него недоступен?
Сервис живёт на российском сервере, откуда Telegram недоступен, поэтому бот работает на зарубежной машине. Сервис сам, исходящим соединением, отдаёт свой внутренний API боту через frp-туннель с TLS. Боту не нужен ни открытый порт, ни публичный адрес: он опрашивает Telegram сам (long polling).

Нужно похожее?

Расскажите о задаче — покажем, как решали такое, и прикинем объём работ.