> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bedolagam.ru/llms.txt
> Use this file to discover all available pages before exploring further.

# Обязательная подписка на каналы

> Мультиканальная система обязательной подписки с цветными кнопками, автодеактивацией и кэшированием для 100k+ пользователей

## Обзор

Система обязательной подписки на каналы блокирует доступ к боту, пока пользователь не подпишется на все указанные Telegram-каналы. Поддерживает неограниченное количество каналов с управлением через бот и Cabinet.

Основные возможности:

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

***

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

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

```env theme={null}
CHANNEL_IS_REQUIRED_SUB=true
```

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

В админ-панели бота: **Настройки → Обязательные каналы → Добавить канал**.

Укажите:

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

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

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

<Warning>
  Если бот не является администратором канала, проверка подписки завершится ошибкой и пользователь не сможет пройти дальше.
</Warning>

***

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

| Переменная                             | По умолчанию | Описание                                                                                                             |
| -------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------- |
| `CHANNEL_IS_REQUIRED_SUB`              | `false`      | Мастер-переключатель. Можно менять без перезапуска — проверяется на каждый запрос                                    |
| `CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE` | `true`       | Деактивировать **триальные** подписки при отписке от канала                                                          |
| `CHANNEL_REQUIRED_FOR_ALL`             | `false`      | Деактивировать **все** подписки (включая платные) при отписке. Приоритет выше `CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE` |

<Note>
  Переменные `CHANNEL_SUB_ID` и `CHANNEL_LINK` из старых версий больше не нужны. Каналы управляются через админ-панель бота или Cabinet API.
</Note>

***

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

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

```
Пользователь пишет /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. Пользователь получает уведомление о восстановлении

<Warning>
  Заблокированные пользователи (`BLOCKED`) никогда не реактивируются автоматически, даже если подпишутся на все каналы.
</Warning>

***

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

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

В меню бота: **Админ-панель → Настройки → Обязательные каналы**.

Доступные действия:

* **Список каналов** — все каналы со статусом (активен/неактивен)
* **Добавить канал** — пошаговый ввод: 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. Это обеспечивает актуальные данные без задержек.

<Note>
  Для получения `ChatMemberUpdated` событий бот должен быть **администратором** каждого обязательного канала.
</Note>

### 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 сохраняется:

1. В **FSM-состояние** aiogram (основное хранилище)
2. В **Redis** как бэкап (TTL 1 час, на случай сброса FSM)

После подписки на все каналы deeplink восстанавливается и регистрация продолжается с применением реферального кода или бонуса кампании.

<Warning>
  Если пользователь не подпишется на каналы в течение 1 часа — deeplink из Redis истекает. FSM-состояние может сохраняться дольше.
</Warning>

***

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

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

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 потерялся после подписки на канал

Deeplink хранится в Redis (TTL 1 час). Если пользователь подписался через час+ после перехода по ссылке — deeplink мог истечь.
