Telegram-бот, который не даёт остаться в канале материалу без обязательной маркировки иностранного агента. Он проверяет каждый пост и удаляет немаркированный, сообщая о нём администраторам. По желанию можно публиковать через самого бота — тогда проверка происходит до публикации.
Материалы, произведённые или распространённые иностранным агентом, должны сопровождаться указанием на этот статус — 255-ФЗ. Форма указания установлена постановлением Правительства РФ от 22.11.2022 № 2108. Типовая формулировка:
НАСТОЯЩИЙ МАТЕРИАЛ (ИНФОРМАЦИЯ) ПРОИЗВЕДЕН, РАСПРОСТРАНЕН И (ИЛИ) НАПРАВЛЕН
ИНОСТРАННЫМ АГЕНТОМ «ИМЯ АГЕНТА» ЛИБО КАСАЕТСЯ ДЕЯТЕЛЬНОСТИ ИНОСТРАННОГО
АГЕНТА «ИМЯ АГЕНТА». 18+
Первый пропуск — административная ответственность: статья 19.34 КоАП, от 30 тыс. ₽ для граждан до 500 тыс. ₽ для юридических лиц. Второй — уголовная: статья 330.1 УК, вплоть до лишения свободы на два года. Порог недавно снизили: раньше для возбуждения дела требовались два административных наказания за год, но 378-ФЗ от 15.10.2025 оставил одно.
Отсюда и смысл бота: один забытый пост — это первая ступень, после которой второй такой же становится уголовным делом. Полагаться на внимательность не стоит, поэтому проверка вынесена в бота.
⚠️ Бот — техническое средство контроля, а не юридическая консультация. Формулировку маркировки, её применимость к вашему случаю и порядок размещения согласуйте с юристом; законодательство меняется часто, поэтому приведённые нормы и суммы проверяйте на актуальность. Бот сверяет только наличие заданной строки в тексте — требования постановления № 2108 к оформлению (размер и цвет шрифта, размещение в начале материала) он не проверяет и проверить не может.
flowchart TD
A["Кто-то публикует пост в канале"] --> B{"Есть маркировка?"}
B -- "да" --> C["Пост остаётся"]
B -- "нет" --> D["Уведомление администраторам с копией поста,<br/>затем пост удаляется"]
E["Автор пишет боту в личные сообщения"] --> F{"Есть маркировка?"}
F -- "да" --> G["Бот копирует сообщение в канал"]
F -- "нет" --> H["Отказ и копия исходного сообщения автору,<br/>уведомление администраторам"]
1. Модерация канала — основной режим. Публикуйте как обычно. Каждый пост проверяется; немаркированный сначала пересылается автору и подписанным администраторам, затем удаляется. Ничего настраивать в процессе работы не нужно — режим включается сам, как только у бота есть право удалять сообщения и задан текст маркировки.
2. Публикация через бота — строгий режим, по желанию. Автор отправляет пост боту в личные сообщения. Маркированный уходит в канал через copyMessage, так что форматирование сохраняется. Немаркированный не публикуется вовсе, а бот присылает обратно копию исходного сообщения, чтобы его можно было поправить и отправить снова.
Это размен удобства на надёжность: модерация удобнее, но пост успевает выйти; публикация через бота неудобнее, но не даёт ему выйти вовсе.
| Модерация канала | Публикация через бота | |
|---|---|---|
| Удобство | публикуете как обычно, прямо из Telegram | пишете боту в личные сообщения, форматирование ограничено возможностями copyMessage |
| Когда проверяет | после публикации | до публикации |
| Пост успевает увидеть аудитория | да — подписчики получают уведомление, пост живёт до удаления секунды или минуты | нет |
| Если бот недоступен | немаркированный пост остаётся в канале, и об этом никто не узнает | пост не выходит вообще, автор замечает это сразу |
Последняя строка — главная. Модерация отказывает «в открытую» и молча: сбой бота выглядит ровно как его отсутствие, а немаркированный пост тем временем висит в канале. Публикация через бота отказывает «в закрытую» и заметно: пост не вышел — вы это видите.
Поэтому выбор простой. Если публиковать через личные сообщения приемлемо — второй режим строже, и лучше пользоваться им. Если нет — модерация закрывает большую часть риска и не меняет вашу привычную работу.
Режимы не исключают друг друга и работают одновременно.
Этого достаточно, чтобы заработала модерация канала.
- Получите токен бота у @BotFather.
- Добавьте бота в канал администратором с правом «Удалять сообщения». Сделайте это до шага 4: кнопка выбора канала показывает только те каналы, где бот уже состоит.
- Запустите бота — см. Установка и запуск.
- Напишите боту
/startв личные сообщения и выберите канал кнопкой. Либо укажите его вручную:/setchannel @mychannelили/setchannel -1001234567890. Сама модерация к вам не привязана, но выбрать канал нужно: иначе боту некуда записать текст маркировки. - Задайте текст маркировки командой
/set_fa_blurb <текст>. Без этого шага бот бесполезен: он не проверяет ничего, пока текст не задан. - Проверьте
/info— модерация канала должна быть отмечена как работающая.
Рекомендуется, но не обязательно: /notify_add — получатели уведомлений видят копию каждого удалённого поста. Без них модерация работает точно так же, просто удаляет молча.
Дополнительный режим для тех, кому нужна проверка до публикации, — со всеми оговорками из раздела Чем режимы отличаются. Поверх быстрого старта понадобится:
- Выдать боту право «Публиковать сообщения» в канале.
- Выдать автору право «Редактировать сообщения» в канале. Это не то же самое, что «Публиковать сообщения»: первое разрешает публиковать через бота, второе — постить напрямую.
- Отправить боту любой пост в личные сообщения, чтобы проверить.
💡 Чтобы через бота шло всё, снимите право «Публиковать сообщения» у администраторов-людей: тогда единственным путём в канал останется проверка до публикации, а модерация останется страховкой на случай, если право кому-то вернут. Команда
/infoподсказывает это, когда публикация через бота уже настроена.
| Команда | Что делает | Кто может |
|---|---|---|
/start |
Как работает бот и что осталось настроить; если канал не выбран — сразу предлагает его выбрать | все |
/help |
Список команд по группам и краткое описание обоих режимов | все |
/info |
Сводка: вы, выбранный канал, готовность каждого режима по отдельности, текущая маркировка, ваши права в канале | все |
/setchannel <@channel или ID> |
Выбрать канал, за которым следит бот. Без аргументов показывает кнопку выбора | все |
/removechannel |
Убрать настройку канала | все |
/set_fa_blurb [текст] |
Без аргументов — показать текущую маркировку. С аргументами — задать новую | просмотр — все; изменение — администратор канала с правом управления чатом |
/notify_add [user_id] |
Добавить получателя уведомлений. Без аргументов открывает кнопку выбора пользователя | администратор канала с правом управления чатом |
/notify_remove [user_id] |
Убрать получателя уведомлений | администратор канала с правом управления чатом |
/notify_list |
Показать список получателей | администратор канала |
Что стоит знать отдельно:
/setchannelи/removechannelисчезают, если задана переменнаяFIXED_CHANNEL_ID: в этом режиме канал один для всех и меняться не может.- Настройки привязаны к каналу, а не к пользователю. Маркировка и список уведомлений общие для всех, кто настроил у себя этот канал. Из этого же следует, что модерация продолжает работать после
/removechannel: она смотрит на настройки канала, а не на вашу сессию. Чтобы выключить её, уберите бота из администраторов канала. /notify_addи/notify_removeпринимают только числовой ID пользователя — поиск по@usernameв Telegram Bot API недоступен. Проще пользоваться кнопкой выбора. Добавляемый пользователь должен быть администратором канала./dump_db— служебная команда, отправляющая владельцу бота весь файл базы данных. Она не входит в меню и не показывается в/help, регистрируется только при заданномBOT_OWNER_IDи работает только для этого пользователя в личных сообщениях.
- Проверяется наличие заданной строки в тексте сообщения, либо в подписи к медиа, либо в вопросе опроса.
- Сравнение чувствительно к регистру и пробелам: маркировка должна совпадать с настроенной посимвольно.
- Форматирование не мешает: если часть маркировки выделена жирным или курсивом, она всё равно найдётся.
- Rich-сообщения Telegram (заголовки, списки, таблицы, цитаты, сворачиваемые блоки, формулы) не содержат обычного текстового поля, поэтому их блоки сначала разворачиваются в плоский текст. Для них сравнение дополнительно повторяется с нормализованными пробелами — структура блоков не сохраняет переносы строк из настроенной маркировки.
- Альбом (несколько медиа одним сообщением) считается маркированным, если маркировка есть хотя бы в одном сообщении группы: подписи к одной фотографии достаточно.
- Собственные посты бота при модерации пропускаются.
Получателям из /notify_list приходит карточка (канал, пользователь, его ID, время по Москве, причина) и следом — копия отклонённого сообщения.
При модерации канала автор получает уведомление в личные сообщения — если Telegram сообщил его ID. Иногда доступна только подпись автора; тогда бот всё равно удаляет пост и уведомляет администраторов, просто не может написать автору лично. При публикации через бота автор из рассылки, наоборот, исключается: он и так получил отказ.
Список получателей необязателен. Пустой список не мешает модерации: посты удаляются как обычно, просто копия никому не уходит.
Требуется Bun 1.3.14 или выше. Node.js не используется, шага сборки нет — бот запускается прямо из TypeScript.
bun install
cp .env.example .env # впишите TELEGRAM_BOT_TOKEN из @BotFather
bun run dev # с автоперезапуском; для обычного запуска — bun run startЕсли TELEGRAM_BOT_TOKEN не задан, процесс падает сразу при старте.
| Переменная | Назначение | Обязательна |
|---|---|---|
TELEGRAM_BOT_TOKEN |
Токен бота от @BotFather. Без него бот не запустится | да |
NODE_ENV |
Окружение (development / production). Передаётся в Sentry как имя окружения |
нет |
FIXED_CHANNEL_ID |
Жёстко закрепляет один канал за всеми пользователями и убирает команды /setchannel и /removechannel |
нет |
BOT_OWNER_ID |
Числовой Telegram ID владельца. Включает /dump_db, отправляющую всю базу данных в Telegram. Не задавайте без необходимости |
нет |
SENTRY_DSN |
DSN для отправки ошибок в Sentry. Если пусто — трекинг ошибок отключён | нет |
POSTHOG_API_KEY |
Ключ проекта PostHog (phc_...) для продуктовой аналитики. Если пусто — телеметрия отключена целиком. См. Телеметрия |
нет |
POSTHOG_HOST |
Адрес PostHog. По умолчанию https://eu.i.posthog.com. Регион должен совпадать с регионом проекта; для self-hosted укажите свой адрес |
нет |
POSTHOG_ID_SALT |
Соль для HMAC, которым хешируются Telegram ID пользователей. Значения по умолчанию нет | да, если задан POSTHOG_API_KEY |
FIXED_CHANNEL_ID подставляется при создании сессии пользователя, поэтому применяется только к сессиям, созданным после установки переменной. Пользователи, уже работавшие с ботом, останутся на своём канале.
Каталог /app/data обязан быть примонтированным томом. В нём лежит база SQLite с настройками каналов и сессиями пользователей; без тома она живёт в изменяемом слое контейнера, и каждый перезапуск молча стирает всю конфигурацию.
docker build -t rf-compliance-bot .
docker run -v ./data:/app/data -e TELEGRAM_BOT_TOKEN=ваш_токен rf-compliance-botОстальные переменные из таблицы выше передаются так же — через -e или блок environment в compose.
Единственное хранилище — файл SQLite data/channels.db рядом с рабочим каталогом процесса. Две таблицы: channel_settings (текст маркировки и список получателей уведомлений, одним JSON-блобом в колонке settings, поэтому новые настройки не требуют миграции схемы) и sessions (состояние диалога с каждым пользователем, включая выбранный канал).
Миграций нет: обе таблицы создаются при старте через CREATE TABLE IF NOT EXISTS. Резервная копия — это просто копия файла channels.db (или /dump_db, если задан BOT_OWNER_ID).
Бот умеет отправлять продуктовую аналитику в PostHog — какие посты проходят проверку, а какие удаляются, и доходят ли администраторы до конца настройки. По умолчанию телеметрия выключена: без POSTHOG_API_KEY ни одного сетевого запроса не уходит.
Никогда не отправляются:
- текст сообщений, подписи к медиа, вопросы опросов, содержимое rich-текста;
- текст самой маркировки (он содержит наименование агента) — только его длина в символах;
- имена, фамилии,
@username, подписи авторов постов; - Telegram ID пользователей в открытом виде и ID сообщений;
- IP-адрес и геолокация (
disableGeoip: true).
Данные о людях и данные о каналах обрабатываются по-разному. Telegram ID пользователя уходит только как HMAC-SHA256 с солью из POSTHOG_ID_SALT; соль не покидает процесс, значения по умолчанию у неё нет, и при заданном POSTHOG_API_KEY без соли бот не запустится — хеш с предсказуемой солью подбирается перебором. Сгенерируйте её один раз (openssl rand -hex 32) и сохраните: при смене соли все пользователи в статистике станут новыми. ID и названия каналов, наоборот, отправляются как есть — канал, который модерирует бот, публичен.
Единственное исключение про текст пользователя — имя команды, если сообщение начинается с /. Аргументы не записываются никогда: /notify_add 123456789 уходит как notify_add плюс признак «аргументы были».
По умолчанию данные уходят в EU-облако PostHog. POSTHOG_HOST позволяет указать другой регион или собственный self-hosted инстанс, чтобы они вообще не покидали вашу инфраструктуру.
bun test # тесты (покрытие включено в bunfig.toml)
bun run lint # ESLint
bun run format # PrettierПолный список скриптов, соглашения по коду и правила для pull request — в CONTRIBUTING.md.
MIT.