Skip to main content

Обзор

Система обязательной подписки на каналы блокирует доступ к боту, пока пользователь не подпишется на все указанные Telegram-каналы. Поддерживает неограниченное количество каналов с управлением через бот и Cabinet. Основные возможности:
  • Мультиканальная подписка — произвольное количество каналов с приоритетом (sort_order)
  • Цветные кнопки (Bot API 9.4) — подписанные каналы зелёные, неподписанные синие
  • Реал-тайм обновление через ChatMemberUpdated события
  • Автодеактивация/реактивация подписок при отписке/подписке на каналы
  • 3-уровневый кэш (Redis → PostgreSQL → Telegram API) для масштабирования на 100k+ пользователей
  • Сохранение deeplinks — реферальные коды и кампании переживают блокировку подписки

Быстрый старт

1. Включите подписку

CHANNEL_IS_REQUIRED_SUB=true

2. Добавьте каналы через бот

В админ-панели бота: Настройки → Обязательные каналы → Добавить канал. Укажите:
  • ID канала — числовой ID (например 1234567890, префикс -100 добавится автоматически)
  • Ссылкаhttps://t.me/your_channel или @your_channel
  • Название — отображается на кнопке подписки

3. Сделайте бота администратором канала

Бот должен быть администратором каждого обязательного канала — иначе он не сможет проверять подписки.
Если бот не является администратором канала, проверка подписки завершится ошибкой и пользователь не сможет пройти дальше.

Конфигурация

ПеременнаяПо умолчаниюОписание
CHANNEL_IS_REQUIRED_SUBfalseМастер-переключатель. Можно менять без перезапуска — проверяется на каждый запрос
CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBEtrueДеактивировать триальные подписки при отписке от канала
CHANNEL_REQUIRED_FOR_ALLfalseДеактивировать все подписки (включая платные) при отписке. Приоритет выше CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE
Переменные CHANNEL_SUB_ID и CHANNEL_LINK из старых версий больше не нужны. Каналы управляются через админ-панель бота или Cabinet API.

Как это работает

Пользовательский флоу

Пользователь пишет /start

Все каналы подписаны? ──── ДА → Бот работает нормально
    │ НЕТ

Показываем кнопки каналов (синие) + кнопку «Я подписался»

Пользователь подписывается на канал, жмёт «Я подписался»

Обновляем кнопки: подписанные → зелёные ✅, остальные → синие

Все подписаны? ──── НЕТ → Алерт «Подпишитесь на остальные каналы»
    │ ДА

Бот работает нормально

Цветные кнопки (Bot API 9.4)

При нескольких обязательных каналах пользователь видит прогресс подписки:
  • Синие кнопки (style: primary) — каналы, на которые нужно подписаться
  • Зелёные кнопки (style: success) — каналы, на которые уже подписан (с ✅)
После каждого нажатия «Я подписался» клавиатура обновляется — подписанные каналы меняют цвет на зелёный.

Автодеактивация подписок

При отписке от обязательного канала бот может автоматически приостановить подписку пользователя:
НастройкаПоведение
CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE=trueДеактивируются только триальные подписки. Платные остаются активными
CHANNEL_REQUIRED_FOR_ALL=trueДеактивируются все подписки (кроме оплаченных через платёжную систему)
Оба falseБот только блокирует UI, подписки не деактивируются
При деактивации:
  1. Подписка переводится в статус DISABLED
  2. Пользователь отключается в панели Remnawave
  3. Пользователь получает уведомление с кнопками подписки
При повторной подписке на все каналы:
  1. Подписка автоматически реактивируется
  2. Пользователь включается в Remnawave
  3. Пользователь получает уведомление о восстановлении
Заблокированные пользователи (BLOCKED) никогда не реактивируются автоматически, даже если подпишутся на все каналы.

Управление каналами

Через бот (админ-панель)

В меню бота: Админ-панель → Настройки → Обязательные каналы. Доступные действия:
  • Список каналов — все каналы со статусом (активен/неактивен)
  • Добавить канал — пошаговый ввод: ID → ссылка → название
  • Детали канала — ID, ссылка, статус, порядок
  • Вкл/Выкл — переключить активность канала (без удаления)
  • Удалить — полное удаление канала

Через Cabinet (веб-панель)

Cabinet предоставляет REST API и UI для управления каналами. API-эндпоинты (/cabinet/admin/channel-subscriptions):
МетодПутьОписание
GET/Список всех каналов
POST/Создать канал
PATCH/{id}Обновить канал (частичное обновление)
POST/{id}/toggleПереключить активность
DELETE/{id}Удалить канал
Поля канала:
ПолеТипОписание
channel_idstringTelegram ID канала (формат -100...)
channel_linkstring?Ссылка на канал (https://t.me/...)
titlestring?Название (до 255 символов), отображается на кнопке
is_activeboolАктивен ли канал для проверки
sort_orderintПорядок отображения (0 = первый)

Архитектура

3-уровневый кэш

Система оптимизирована для масштабирования и никогда не вызывает Telegram API напрямую в основном потоке:
Запрос пользователя

[Уровень 1] Redis — MGET за один раунд-трип (TTL 10 мин)
    │ промах

[Уровень 2] PostgreSQL — один запрос на все каналы (свежесть 30 мин)
    │ устарело

[Уровень 3] Telegram API — rate-limited getChatMember (20 concurrent, 50ms delay)

Результат записывается: PostgreSQL → Redis

Реал-тайм обновление через ChatMemberUpdated

Бот получает события ChatMemberUpdated при подписке/отписке пользователя на канал и сразу обновляет PostgreSQL + Redis. Это обеспечивает актуальные данные без задержек.
Для получения ChatMemberUpdated событий бот должен быть администратором каждого обязательного канала.

Redis-ключи

КлючTTLОписание
channel_sub:{telegram_id}:{channel_id}600сСтатус подписки пользователя (1/0)
required_channels:active60сСписок активных каналов (JSON)
sub_check_rate:{telegram_id}Rate limit на кнопку «Я подписался»
pending_start_payload:{telegram_id}Сохранённый deeplink (реферальный код, кампания)

Безопасность

  • Fail-closed модель: при любой ошибке (сеть, rate limit, бот удалён из канала) пользователь считается неподписанным. Ложный отказ предпочтительнее несанкционированного доступа.
  • Админы исключены: администраторы бота всегда проходят проверку подписки.
  • Email/OAuth пользователи: пользователи без telegram_id (вход через email или OAuth) пропускают проверку подписки — она работает только для Telegram-пользователей.

Если пользователь пришёл по реферальной ссылке или ссылке кампании, а подписка на канал обязательна — deeplink сохраняется:
  1. В FSM-состояние aiogram (основное хранилище)
  2. В Redis как бэкап (TTL 1 час, на случай сброса FSM)
После подписки на все каналы deeplink восстанавливается и регистрация продолжается с применением реферального кода или бонуса кампании.
Если пользователь не подпишется на каналы в течение 1 часа — deeplink из Redis истекает. FSM-состояние может сохраняться дольше.

Устранение проблем

Бот не видит подписку пользователя

  1. Убедитесь, что бот является администратором канала
  2. Проверьте channel_id — должен быть в формате -100... (числовой ID, не username)
  3. Если канал только что добавлен — подождите до 60 секунд (кэш списка каналов)

Пользователь подписан, но бот не пропускает

  1. Нажмите «Я подписался» — это инвалидирует кэш и делает свежий запрос к API
  2. Если проблема сохраняется — проверьте логи бота на ошибки TelegramForbiddenError (бот удалён из канала)

Подписка не деактивируется при отписке от канала

  1. Проверьте CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE=true или CHANNEL_REQUIRED_FOR_ALL=true
  2. Платные подписки (с реальными платежами) не деактивируются при CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE — только при CHANNEL_REQUIRED_FOR_ALL
  3. Проверьте, получает ли бот ChatMemberUpdated события (бот должен быть админом)
Deeplink хранится в Redis (TTL 1 час). Если пользователь подписался через час+ после перехода по ссылке — deeplink мог истечь.