Skip to main content
Оператор может требовать от клиентов членства в одном или нескольких каналах Telegram. Это маркетинговый механизм, и это единственное в Omniflow, что может заблокировать платящего клиента по причине, не связанной с оплатой. Поэтому каждая его часть обратима, сопровождается предварительным предупреждением и поддаётся последующей проверке по журналу.

Неизвестный ответ — это никогда не отсутствие

getChatMember может завершиться неудачей: Telegram недоступен, бот потерял права администратора в канале, API ограничивает частоту запросов. Ничто из этого не означает, что клиент вышел. Неудавшаяся проверка записывает unknown — состояние, отличное от absent, и оно ничего не блокирует. Если бы эти два состояния слились в одно, людей блокировали бы из-за сбоя, и это тот режим отказа, который есть у данного механизма и которого нет больше нигде в продукте. Клиент, у которого вообще нет записи о членстве, находится в том же положении: никто не установил, что он отсутствует, поэтому ничто не ограничивается.

Два требования — два обещания

Они разделены, потому что это разные обещания. Заблокировать может только требование при активации; ограничение покупки ни у кого ничего не отбирает. Требование при покупке применяется на обеих поверхностях по одному правилу. Бот проверяет его, когда клиент нажимает Оплатить, и спрашивает Telegram вживую, если кэшированному ответу больше пяти минут. Веб-оформление проверяет его, когда клиент нажимает Подтвердить, но у процесса API нет собственного транспорта Telegram, поэтому он читает только подписки, записанные воркером: ответ старше двадцати четырёх часов или его отсутствие — это unknown, а unknown никогда не блокирует. Клиенту, которому веб отказал, показываются каналы с кнопкой подписки для каждого; как только он подписался, следующий проход воркера — или «Я подписался» в боте — записывает новое состояние, и подтверждение проходит. Клиент без идентичности Telegram не может состоять в Telegram-канале, и его об этом не просят.

О выходе предупреждают до того, как он что-то стоит

Последовательность такова: compliant → warned → suspended, и она управляется льготным периодом, который настраивает оператор. Первая проверка, обнаружившая отсутствие клиента, предупреждает его и запускает отсчёт. До истечения отсчёта не происходит ничего. Предупреждение срабатывает один раз за эпизод, а не при каждой проверке, потому что ежедневное напоминание подписаться на канал — это то, как клиент учится игнорировать бота. Три дня выбраны значением по умолчанию потому, что этот срок покрывает выходные, когда человек с наибольшей вероятностью разбирает список своих каналов и с наименьшей вероятностью замечает последствия. Нулевой льготный период — законная конфигурация, и он блокирует сразу, но предупреждение всё равно отправляется, потому что клиент, заблокированный без предупреждения, — это клиент, который не может понять почему.

Что получает клиент и что происходит на самом деле

Каждая смена состояния отправляет в чат клиента одно сообщение. Предупреждение перечисляет каналы, на которые он всё ещё не подписан, с кнопкой для каждого, и называет момент, когда доступ прекратится, если не подписаться раньше. Сообщение о приостановке говорит, что доступ выключен и что повторная подписка вернёт его при следующей проверке. Сообщение о восстановлении говорит, что доступ вернулся. Проверка, которая ничего не изменила, ничего не отправляет. Приостановка и восстановление не пишутся в Remnawave напрямую. Воркер ставит fulfillment-операцию disable для каждого entitlement, чей оплаченный период ещё не закончился, и enable для каждого, который Remnawave считает отключённым, когда клиент возвращается, — под ключом идемпотентности, в котором записаны entitlement и момент решения. Поэтому клиент, отключённый за выход из канала, несёт ту же историю операций, что и отключённый оператором вручную, а оператор, читающий журнал fulfillment, видит корреляцию channel-gate. Entitlement, приостановленный оператором, не затрагивается: он выключен по решению, которое повторная подписка отменять не должна. Состояние принуждения записывается до попытки применить последствие, так что последствие, которое не удалось поставить в очередь, повторяется следующим проходом, а не теряется.

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

Клиенту, устранившему проблему, не должно приходиться просить кого-то отменить последствия. Следующая проверка, обнаружившая его членство, восстанавливает доступ независимо от того, был ли он предупреждён или уже заблокирован. left_at проставляется в момент, когда отсутствие замечено впервые, и очищается при возвращении, поэтому льготный отсчёт ведётся от момента, когда клиент действительно вышел, а не от момента, когда это заметил проход воркера.

Исключения

Правило является маркетинговым механизмом, и всегда есть те, к кому оно применяться не должно: платящий клиент, который не может подписаться по своим причинам, кто-то, кому оператор пообещал, тестовый аккаунт. Исключение фиксирует, кто его выдал и почему, потому что исключение, которое никто не может объяснить, неотличимо от ошибки. Оно может иметь срок действия. Исключение для заблокированного клиента восстанавливает его немедленно — правило больше не применяется, а значит, не должно применяться и его следствие. Отзыв исключения возвращает клиента в состояние compliant, а не сразу в suspended: решает следующая проверка, и она предупреждает первой, как и для всех остальных. Блокировка при отзыве отобрала бы доступ без всякого уведомления.

Кеширование и ограничение частоты запросов

Ответы о членстве кешируются на настраиваемое окно, по умолчанию шесть часов. getChatMember ограничен по частоте запросов у третьей стороны, и повторный опрос на каждом экране сделал бы бота медленнее для всех ради соблюдения маркетингового правила. Воркер перепроверяет ограниченными партиями, поэтому крупная установка перепроверяется за много проходов, а не одним всплеском, из-за которого бота начнут троттлить.

Как это выключить

Отключение всех каналов немедленно останавливает применение правила. Воркер сначала читает набор включённых каналов и возвращается, ничего не делая, если тот пуст, вместо того чтобы продолжать работу до истечения какого-нибудь кеша конфигурации.

Настройки