Обзор
Система обязательной подписки на каналы блокирует доступ к боту, пока пользователь не подпишется на все указанные 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_SUB | false | Мастер-переключатель. Можно менять без перезапуска — проверяется на каждый запрос |
CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE | true | Деактивировать триальные подписки при отписке от канала |
CHANNEL_REQUIRED_FOR_ALL | false | Деактивировать все подписки (включая платные) при отписке. Приоритет выше 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, подписки не деактивируются |
При деактивации:
- Подписка переводится в статус
DISABLED
- Пользователь отключается в панели Remnawave
- Пользователь получает уведомление с кнопками подписки
При повторной подписке на все каналы:
- Подписка автоматически реактивируется
- Пользователь включается в Remnawave
- Пользователь получает уведомление о восстановлении
Заблокированные пользователи (BLOCKED) никогда не реактивируются автоматически, даже если подпишутся на все каналы.
Управление каналами
Через бот (админ-панель)
В меню бота: Админ-панель → Настройки → Обязательные каналы.
Доступные действия:
- Список каналов — все каналы со статусом (активен/неактивен)
- Добавить канал — пошаговый ввод: ID → ссылка → название
- Детали канала — ID, ссылка, статус, порядок
- Вкл/Выкл — переключить активность канала (без удаления)
- Удалить — полное удаление канала
Через Cabinet (веб-панель)
Cabinet предоставляет REST API и UI для управления каналами.
API-эндпоинты (/cabinet/admin/channel-subscriptions):
| Метод | Путь | Описание |
|---|
GET | / | Список всех каналов |
POST | / | Создать канал |
PATCH | /{id} | Обновить канал (частичное обновление) |
POST | /{id}/toggle | Переключить активность |
DELETE | /{id} | Удалить канал |
Поля канала:
| Поле | Тип | Описание |
|---|
channel_id | string | Telegram ID канала (формат -100...) |
channel_link | string? | Ссылка на канал (https://t.me/...) |
title | string? | Название (до 255 символов), отображается на кнопке |
is_active | bool | Активен ли канал для проверки |
sort_order | int | Порядок отображения (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:active | 60с | Список активных каналов (JSON) |
sub_check_rate:{telegram_id} | 5с | Rate limit на кнопку «Я подписался» |
pending_start_payload:{telegram_id} | 1ч | Сохранённый deeplink (реферальный код, кампания) |
Безопасность
- Fail-closed модель: при любой ошибке (сеть, rate limit, бот удалён из канала) пользователь считается неподписанным. Ложный отказ предпочтительнее несанкционированного доступа.
- Админы исключены: администраторы бота всегда проходят проверку подписки.
- Email/OAuth пользователи: пользователи без
telegram_id (вход через email или OAuth) пропускают проверку подписки — она работает только для Telegram-пользователей.
Сохранение deeplinks
Если пользователь пришёл по реферальной ссылке или ссылке кампании, а подписка на канал обязательна — deeplink сохраняется:
- В FSM-состояние aiogram (основное хранилище)
- В Redis как бэкап (TTL 1 час, на случай сброса FSM)
После подписки на все каналы deeplink восстанавливается и регистрация продолжается с применением реферального кода или бонуса кампании.
Если пользователь не подпишется на каналы в течение 1 часа — deeplink из Redis истекает. FSM-состояние может сохраняться дольше.
Устранение проблем
Бот не видит подписку пользователя
- Убедитесь, что бот является администратором канала
- Проверьте
channel_id — должен быть в формате -100... (числовой ID, не username)
- Если канал только что добавлен — подождите до 60 секунд (кэш списка каналов)
Пользователь подписан, но бот не пропускает
- Нажмите «Я подписался» — это инвалидирует кэш и делает свежий запрос к API
- Если проблема сохраняется — проверьте логи бота на ошибки
TelegramForbiddenError (бот удалён из канала)
Подписка не деактивируется при отписке от канала
- Проверьте
CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE=true или CHANNEL_REQUIRED_FOR_ALL=true
- Платные подписки (с реальными платежами) не деактивируются при
CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE — только при CHANNEL_REQUIRED_FOR_ALL
- Проверьте, получает ли бот
ChatMemberUpdated события (бот должен быть админом)
Deeplink потерялся после подписки на канал
Deeplink хранится в Redis (TTL 1 час). Если пользователь подписался через час+ после перехода по ссылке — deeplink мог истечь.