zavtrachat_bot

command module
v0.0.0-...-0ee65bb Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: MIT Imports: 84 Imported by: 0

README

zavtrachat_bot

Русский | English

Telegram-бот для одного конкретного чата на Go. Работает через Bot API (long polling или webhook, поддерживается self-hosted Bot API server), дополнительно использует MTProto-клиент для нескольких команд. Состояние — в PostgreSQL.

  • 99 команд, зарегистрированных в main.go (часть — алиасы одной функции)
  • 112 исходников .go, 291 тест в 49 файлах
  • GitHub Actions: сборка + деплой на self-hosted раннере и публикация образа в ghcr.io
  • Лицензия — MIT
  • С 16.10.2026 разработку ведёт ИИ — модели локального стека Strata (swift-1.5-iq3_xxs, qwen3.8-flash-next-iq3_xxs и др., подробности — в разделе Разработка)

Дни рождения

Даты хранятся в PostgreSQL, по Telegram ID пользователя. При первом запуске импортируются 90 записей из «Календаря Завтрачата» (assets/birthdays.json): только день и месяц, без возраста; две завершенные серии и годовщина подкаста исключены. Запись 9 февраля без RRULE также импортируется как день рождения. Удаленные даты не возвращаются при перезапуске. Импортированные ники связываются с ID из базы пользователей; неизвестные сохраняются до появления пользователя в базе. Привязанный ID сохраняется при смене ника.

  • /addbirthday ДД.ММ — добавить свой день рождения. Например, /addbirthday 23.06. Если дата уже записана, она не заменяется: сначала /delbirthday, затем добавление.
  • /delbirthday — удалить свой день рождения.
  • /whenyourbirthday @username — узнать день рождения участника.
  • /birthdays — десять ближайших дат, включая сегодняшние, с переходом через Новый год.
  • /testbirthday — публично поздравить @nakxwest в основном чате. Только админам Telegram, настроенным админам бота и системному администратору; не расходует ежегодное поздравление.

Ответы команд приватны для вызвавшего пользователя; они также работают в личке. Ежедневно с 10:00 МСК бот проверяет членство именинника в основном чате. Участники, администраторы и замьюченные участники получают открытку; вышедшие и забаненные — нет. Если бот перезапущен после 10:00, он догоняет только текущий день. Ошибка API повторяется на следующей минуте; неподтвержденное членство не позволяет отправить открытку. 29 февраля отмечается 28 февраля в невисокосные годы.

Карточка: один из десяти случайных праздничных фонов, текущая аватарка (или инициалы, если Telegram не отдает фото), имя профиля и «с днем рождения!», блестки. В подписи — кликабельный @ник или имя, если ника нет. Фоны встроены в бинарник, отдельный сервер изображений не нужен. Запись доставки и блокировка строки защищают от повторов после обычного перезапуска и при одновременной работе экземпляров. Редкий сбой строго между успешным ответом Telegram и фиксацией транзакции может привести к повтору: Bot API не поддерживает идемпотентную отправку сообщений.

Команды

Справка
  • /zbhelp — команды для всех участников с описаниями, примерами и алиасами.
  • /zbadminhelp — административные команды; доступ администраторам/владельцу основного чата Telegram, настроенным администраторам, модераторам и системному администратору.

В группе обе команды и их ответы эфемерные: справку и отказ в доступе видит только вызвавший пользователь. В личке ответ обычный. При ошибке скрытой доставки публичного ответа нет. /zbhelp находится в меню группы и лички, /zbadminhelp — в меню администраторов основного чата (в личке и для настроенных модераторов доступна по имени). Справка разделена по фактическим проверкам прав; команды редактирования/удаления своих гетов доступны участникам. Права существующих команд не меняются.

Модерация
Команда Что делает
/ban, /unban бан / разбан, опционально время в секундах (/ban @user 3600)
/mute, /unmute заглушить / разглушить, опционально время в секундах
/muted кто сейчас замьючен: причина, инициатор, остаток времени; скрытый ответ вызвавшему админу
/kick кик (бан + разбан, чтобы можно было вернуться по ссылке)
/warn, /mywarns предупреждение / сколько у меня предупреждений
/remove удалить сообщение, ответом на которое послана команда
/debug, /debuguser, /finduserinmessage JSON-дампы сообщения/юзера для отладки

Доступ к модерационным командам — по спискам ZAVTRA_ADMINS / ZAVTRA_MODERS / ZAVTRA_SYSADMIN.

/muted вызывается в основном чате. Доступ: администраторы Telegram, владелец чата, пользователи из ZAVTRA_ADMINS, ZAVTRA_MODERS и ZAVTRA_SYSADMIN. В меню команда показывается администраторам чата; настроенные модераторы могут вызвать её по имени. И команда из меню, и ответ эфемерные: отчёт видит только вызвавший пользователь. Кнопки обновления и страниц редактируют его скрытое сообщение и заново проверяют права. Время окончания указано по Москве.

Полный текущий список запрашивается через MTProto, включая ручные ограничения и старые муты. При недоступном MTProto команда проверяет через Bot API известные боту активные ограничения и явно предупреждает, что список неполон; ошибки проверки показываются отдельно. Снятые, истёкшие ограничения, вышедшие участники и запреты только на медиа в список не входят. Новая таблица mute_events хранит успешные муты/снятия, инициатора и источник: самоблесс, рикошет, убийство, дуэль, массовый выстрел, административный мут или три предупреждения. Ручные изменения отслеживаются через chat_member (бот должен быть администратором, chat_member должен входить в ZAVTRA_ALLOWED_UPDATES; он включён по умолчанию). Для ограничений без подходящей записи причина обозначается как неизвестная. Имеющиеся игровые счётчики и правила ограничений не меняются.

Игры и «социальное»
Команда Что делает
/duel, /russianroulette вызов на дуэль (русская рулетка), кнопки принятия/отказа
/duelstats своя статистика дуэлей
/kill и алиасы bite, kilobite, kilomlem, megabite, bless «пристрелить» юзера (временное ограничение)
/revive, /resurrect, /redemption, /massrevive, /megarevive возродить одного / всех недавних
/shotgun, gigabite, gigakill, ultrakill выстрел по чату
/megamlem те же права и ограничения участников, что у /shotgun, с картинкой гигантского чёрного кота
/isekai, suicide, blessing, /isekaitop, /blessingtop «устроиться в роскомнадзор» и топ чата
/addbless, /addnope добавить причину блесса / текст отказа по кнопке
/addsalvation добавить фразу спасения (до 200 символов): текстом после команды или ответом на сообщение
/salvationlist управление отдельным набором фраз спасения в личке: страницы и удаление с подтверждением
/salvationlistfull весь набор фраз спасения обычным текстом в личке
/blesslist управление общими фразами блесса в личке: страницы, выбор и подтверждение удаления
/pidor, /pidoreg, /pidorlist, /pidorme, /pidorstats, /pidorall, /pidorules, /pidordel, /pidorremovetoday игра «Пидор Дня!»
/slap, /hug, /bonk, /marco, /shrug, /me стикеры/мем-действия и IRC-подобный /me

/blesslist доступна в личке пользователям из ZAVTRA_ADMINS и ZAVTRA_MODERS. В группе команда только предлагает перейти в личку. Фразы общие для /blessing, /kill, /shotgun, /megamlem и их аналогов. Удаление требует отдельного подтверждения точного текста; последнюю фразу удалить нельзя. Бот оставляет копию удалённого текста для восстановления через неизменённую /addbless (до 200 символов; для длинных старых фраз выводится предупреждение). Кнопки привязаны к пользователю и сообщению, действуют 30 минут; после перезапуска или истечения срока откройте /blesslist заново. Статистика не меняется.

Медиа
Команда Что делает Нужно
/meow, /mlem, /woof гифка с котиком / не-котиком MTProto
/meme мем из канала MTProto
/quote карточка с цитатой из сообщения, на которое ответили —
/convert конвертация медиа: mp3, ogg, gif, audio, voice, animation ffmpeg, ZAVTRA_BOT_API_DATADIR
/distort, /invert, /reverse, /loop «переебать»/инвертировать/залупить медиа ffmpeg, ZAVTRA_BOT_API_DATADIR
/mp3 скачать аудио по ссылке (YouTube и т. п.) yt-dlp, ffmpeg
/download, /wget скачать видео по ссылке yt-dlp
/savetopm, /giveme, /topm сохранить пост в личку (копирует в PM, бот должен быть разблокирован) —

Неполный фрагмент отмечается многоточиями со стороны пропущенного текста; технические статусы под карточками не публикуются. /quote создаёт публичную карточку ответом на сообщение или выделенный фрагмент Telegram. Обычные участники делят общий лимит 7 успешно отправленных карточек за календарный час по Москве, сохраняемый в базе после перезапуска. Под карточкой нет служебной подписи. При исчерпании лимита пользователь получает приватное уведомление; ошибки отправки возвращают место. После успешной отправки карточки бот удаляет сообщение с командой. Администраторы бота, системный администратор и администраторы основного чата цитируют без ограничений и без подписи о лимите. Сама команда отправляется обычным сообщением; ошибки, отказы и /quote debug остаются приватными. Диагностика доступна только администраторам. Защищённые авторы задаются закрытым секретом сборки или переменной ZAVTRA_QUOTE_PROTECTED_USERNAMES; список не публикуется в коде, справке и отказах. Их сообщения могут цитировать только администраторы. Проверяется исходный автор пересылки; пересылки со скрытым автором доступны только администраторам при включённой защите. ID распознанного защищённого автора сохраняется с хешем настройки для защиты после смены ника. Бот создаёт PNG-карточку с градиентом, круглым аватаром, цитатой, именем автора и датой исходного сообщения по Москве. У пересланных сообщений используются исходные автор и дата; для скрытого автора сохраняется указанное Telegram имя. Аватар загружается из доступных размеров фото; при неудаче бот пробует текущую фотографию из карточки пользователя/чата. Для файла используется тот же сетевой клиент, что и для Bot API, включая его прокси/TLS. Поддерживаются пути общего каталога локального Bot API. Все попытки ограничены общими 8 секундами. Если аватар всё же недоступен, используются инициалы и личная подпись с причиной без токенов, URL и идентификаторов. Скрытый автор пересылки не ищется по имени. Шрифты встроены в бинарник; обычные эмодзи рисуются монохромно. Для каждой карточки случайно выбирается один из четырёх сохранённых фонов в духе Telegram: светлый, ночной, сиреневый или мятный. Все исходные фоны — 1200×660 px. Текст белый на ночном фоне и тёмный на светлых; выбор не зависит от автора, повторы допустимы. Ширина карточки — 1200 px, высота адаптируется к тексту (от 660 px); фон заполняет её с сохранением пропорций. Аргументы команды не подменяют цитату. Длинные слова и абзацы переносятся, текст не обрезается; если сообщение не помещается в карточку высотой до 6000 px, бот предлагает выбрать более короткое. Подсказки видны только вызвавшему команду, сама карточка видна только администратору, вызвавшему /quote. В группе используется персональное сообщение, в личке — обычный личный ответ. Ошибка персональной доставки не вызывает публичную отправку. Отрисовка использует общую ограниченную очередь медиа. Новые настройки, внешние сервисы генерации и миграции не требуются.

В карточке — крупные кавычки, жирный текст, толстое подчёркивание под цитатой, аватар 160 px, подпись автора 40 px и дата 28 px. Короткие цитаты получают шрифт до 100 px; по мере увеличения текста он уменьшается до 36 px, без обрезки слов.

Одиночные слова до четырёх букв и варианты «пошёл на хуй» / «иди нахуй» отклоняются. Проверка учитывает регистр, ё/е, слитное/раздельное написание, распространённые опечатки, пунктуацию и повторяющиеся буквы. Проверяется выбранный фрагмент, если он задан, иначе всё сообщение. Отказ виден только вызвавшему администратору и содержит ровно: «Хехе, попа, а не цитата, выбери другое.»

Утилиты
Команда Что делает
/get, /getall, /set, /del, /setgetowner «геты» — сохраняемые ответы бота (текст/медиа), инлайн-выбор через @botname
/sed, /khaleesi, /say замена текста по образцу, мем-кхалиси, заставить бота сказать текст
/stats топы чата: likes/reactions (лайки на сообщения), mutes/blessings (смерти), mutetime/blesstime (время в муте), messages, words; период day/week, без периода — за всё время
/advice, /anekdot, /releases совет, анекдот, список релизов игр
/cur курсы валют (нужен currency_key)
/google ссылка на поиск по тексту
/date, /ping, /getid, /testrandom, /tldr дата сервера, понг, ID юзера, проверка рандома, подсказка про кнопку TLDR
/restart os.Exit(0) — перезапуск бота (нужен Restart=always в systemd)
Ставки

/bet ДД.ММ.ГГГГ текст сохраняет прогноз на дату начиная с завтрашнего дня по Москве. Записываются автор, текст, дата прогноза и время исходного сообщения. /allbets показывает ставки начиная с сегодняшнего дня, /allbets all — также прошедшие; в списке видна дата создания. /delbet ДД.ММ.ГГГГ текст удаляет свою ставку и отменяет ещё не отправленное напоминание.

В день прогноза в 12:00 МСК бот публикует в основном чате (ZAVTRA_CHAT) отдельное обычное сообщение для каждой ставки: «31.10.2026 @автор сказал: … Ну что, сбылось?». Имя — кликабельное упоминание по Telegram ID; используется актуальный известный боту ник, при его отсутствии — имя. Без записи пользователя остаётся сохранённое при создании имя. Результат прогноза автоматически не определяется.

Проверка запускается при старте и на границе каждой минуты. После запуска между 12:00 и полуночью МСК бот отправляет пропущенные сегодняшние напоминания. Ставки прошедших дней не рассылаются. Успешная отправка отмечается в PostgreSQL; обычный перезапуск не повторяет её. Ошибки отправки повторяются на следующей проверке. Блокировки строк защищают от параллельной отправки несколькими экземплярами. Telegram и PostgreSQL не образуют общую транзакцию: при аварии после доставки, но до фиксации отметки, или неоднозначном сетевом тайм-ауте возможен повтор.

Миграция добавляет поля без изменения ключей и дат старых ставок. Историческая дата создания неизвестна: в списке это указано явно, напоминание начинается со слова «Ранее». Новые тексты ограничены 3500 единицами UTF-16 для запаса под оформление; длинные исторические ставки делятся на несколько сообщений.

Измерение производительности

/perf показывает отчёт в личке пользователям из ZAVTRA_ADMINS и ZAVTRA_MODERS. В группе команда предлагает перейти в личку сообщением, видимым только вызвавшему; остальным пользователям отчёт недоступен. /perf сохраняет отчёт с запуска и добавляет краткие сводки за 15 и 60 минут. /perf 15 и /perf 60 показывают подробный отчёт за выбранное окно. Окна состоят из текущей неполной минуты и 14/59 предыдущих минут; вызовы относятся к минуте завершения. Хранятся 60 поминутных агрегатов, без истории событий.

Дополнительно показаны память Go (занятые объекты и память, полученная средой Go от ОС), число горутин и текущие активные/ожидающие медиа-задачи с лимитами. Память Go не равна RSS всего процесса и не включает память отдельных FFmpeg/yt-dlp. Счётчики медиа отражают состояние на момент запроса, независимо от выбранного окна. С момента запуска собираются число завершённых вызовов, возвращённых ошибок и медленных вызовов (от 5 секунд), среднее и максимальное время. В отчёте — до 10 обработчиков с наибольшим суммарным временем, команды-алиасы учитываются отдельно. Дополнительно измеряются все запросы через основной GORM-клиент: количество, ошибки, среднее, максимум и число запросов от 200 мс, включая служебные запросы запуска и фоновых задач. record not found и сигналы продолжения/завершения диспетчера не считаются ошибками.

Хранятся только агрегаты по фиксированным именам обработчиков, без истории отдельных сообщений, аргументов и идентификаторов пользователей. Перезапуск обнуляет показатели; отдельная база и миграции не нужны. Время обработчика включает синхронные обращения к Telegram, но не работу горутин после его возврата. Обработанные внутри команды ошибки не видны как ошибки обработчика; SQL-ошибки учитываются отдельно. Это не измерение очереди обновлений или времени доставки ответа пользователю. BEGIN/COMMIT драйвера, MTProto-хранилища и сторонние SQL-клиенты в счётчик GORM не входят.

Медленные и завершившиеся ошибкой обработчики и SQL-запросы пишутся в существующий журнал с длительностью и признаком ошибки. Новый SQL-логгер не раскрывает запросы, параметры или текст ошибок. Старые диагностические сообщения других частей бота не меняются. Владелец сервера может посмотреть эти события через journalctl -u telegram-go-chatbot --since today | grep -E 'handler performance|database performance'.

Обработчики без команд
  • reserve_chat — удаляет системные сообщения о входе/выходе и добавляет юзеров в БД.
  • channel — пересылает посты канала в чат; при заданном stream_channel дублирует туда.
  • Inline-режим — @botname <текст> отдаёт сохранённые геты.
  • Реакции в основном чате.
  • Глобальный middleware — debug-лог каждого апдейта + антиспам: больше ZAVTRA_ANTISPAM_COUNT сообщений за ZAVTRA_ANTISPAM_WINDOW секунд → эфемерное предупреждение ZAVTRA_SYSADMIN в основном чате.

Требования

Зависимость Где нужна
Go 1.26+ (go.mod), в CI/Dockerfile база golang:1.25 + автозамена тулчейна
PostgreSQL обязателен, таблицы создаются AutoMigrate при старте
ffmpeg, ffprobe для /convert, /distort, /invert, /loop, /mp3
yt-dlp для /mp3, /download (в Docker-образе не установлен)
Self-hosted Bot API опционально: ZAVTRA_BOT_API_URL (применяется ко всем API-вызовам); ZAVTRA_BOT_API_DATADIR — чтобы GetMedia читал файлы напрямую с диска (для /convert, /distort, /invert, /loop)
Прямой доступ к датацентрам Telegram для MTProto-команд: TCP на 149.154.* / 91.108.*:443 + app_id/app_hash

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

Всё конфигурирование — переменные окружения ZAVTRA_*. Бот читает файл .env из рабочей директории (ZAVTRA_ENV_FILE указывает на другой путь), а переменные окружения процесса его перекрывают: файл — это настройки по умолчанию, окружение — точечные переопределения. Шаблон — .env.example, сам .env в .gitignore: в нём токен бота и пароль базы, держите права 0600.

Docker: docker run --env-file .env, в compose env_file: .env, в systemd EnvironmentFile=. В образ .env не попадает (.dockerignore).

Переменная Назначение
ZAVTRA_TOKEN токен бота — обязательна
ZAVTRA_CHAT ID основного чата — обязательна
ZAVTRA_DSN PostgreSQL DSN
ZAVTRA_BOT_API_URL базовый URL Bot API (по умолчанию https://api.telegram.org)
ZAVTRA_BOT_API_DATADIR datadir self-hosted Bot API для прямого чтения файлов
ZAVTRA_APP_ID, ZAVTRA_APP_HASH учётные данные MTProto-приложения для /meow, /woof, /meme
ZAVTRA_ALLOWED_UPDATES какие апдейты запрашивать (по умолчанию message,channel_post,callback_query,chat_member)
ZAVTRA_LISTEN, ZAVTRA_ENDPOINT_PUBLIC_URL, ZAVTRA_MAX_CONNECTIONS webhook-режим; если пусто — long polling
ZAVTRA_RESERVE_CHAT чат, где удаляются сообщения о входе/выходе
ZAVTRA_CHANNEL, ZAVTRA_STREAM_CHANNEL канал для пересылки постов и куда их дублировать
ZAVTRA_ADMINS, ZAVTRA_MODERS, ZAVTRA_SYSADMIN ID с правами модерации (списки через запятую); ZAVTRA_SYSADMIN получает уведомления о старте, ошибках и антиспаме эфемерными сообщениями в основном чате (видит только он, Telegram удаляет их по настройке «эфемерные сообщения» чата; нужен self-hosted Bot API)
ZAVTRA_CURRENCY_KEY CoinMarketCap API key для /cur
ZAVTRA_RELEASES_URL календарь для /releases (по умолчанию — PS/PS5/Switch/Xbox PAL)
ZAVTRA_PROXY HTTP-прокси для исходящих запросов: /advice, /anekdot, /releases, скачивание файлов (таймаут 10 с)
ZAVTRA_ANTISPAM_WINDOW, ZAVTRA_ANTISPAM_COUNT окно в секундах и порог сообщений для антиспама
ZAVTRA_AI_URL, ZAVTRA_AI_TOKEN, ZAVTRA_AI_MODEL, ZAVTRA_AI_SYSTEM, ZAVTRA_AI_ADMIN_ONLY_TOOLS AI-модуль (ai_*.go), в main.go пока выключен
ZAVTRA_FONTPATH, ZAVTRA_COMMENT_CHAT объявлены в конфиге, в коде сейчас не используются
ZAVTRA_ENV_FILE путь к .env (по умолчанию .env в рабочей директории)

Списки — через запятую (пробел и ; тоже разделители). Значения с пробелами, $, # и переводами строк берутся в двойные кавычки: ZAVTRA_AI_SYSTEM="первая строка\nвторая".

Права .env — 600: в файле токен бота и пароль базы.

Запуск

go build -o zavtrachat_bot
cp .env.example .env && chmod 600 .env   # заполните токен и чат
./zavtrachat_bot            # из каталога, где лежит .env

Docker:

docker build -t zavtrachat .
docker run -d --name zavtrachat --env-file .env zavtrachat

В образе WORKDIR /, ENTRYPOINT /goapp, установлен ffmpeg; конфигурация — --env-file .env (или отдельные -e ZAVTRA_TOKEN=...). Секреты в образ не копируются (.dockerignore). yt-dlp в образ не входит — /mp3 и /download в контейнере работать не будут.

CI (.github/workflows/):

  • deploy.yml — на изменение **.go, go.mod/go.sum и самого workflow: Test гоняет go vet и go test -race -count=1 на ubuntu-latest с отдельным сервисом PostgreSQL 18 (покрытие складывается в артефакт), затем на push в master Deploy собирает telegram-go-chatbot на self-hosted раннере, копирует его в /home/telegram/bot/ и делает systemctl restart telegram-go-chatbot. На pull request тесты идут, прод не перезапускается; параллельные деплои блокируются concurrency-группой;

    На сервере в рабочей директории сервиса должен лежать .env (/home/telegram/bot/.env) — конфигурация читается только из окружения, без файла сервис не стартует. Права 0600, владелец — пользователь сервиса;

  • docker-publish_yml — сборка и публикация образа в ghcr.io + cosign-подпись. Файл назван с _yml вместо .yml, поэтому GitHub Actions его не подхватывает (workflows ищут *.yml/*.yaml).

Тесты

go test -race -count=1 ./...                                   # PostgreSQL-тесты опциональны локально
PROTO_PROBE=1 go test -count=1 -run TestMtprotoConnectProbe -v .   # проверить доступ к датацентрам

В CI устанавливаются ffmpeg, ffprobe, ImageMagick (convert) и yt-dlp версии 2026.07.04. При ZAVTRA_REQUIRE_MEDIA_TESTS=1 отсутствие инструмента или невозможность создать медиа-фикстуру — ошибка, а не пропуск. В итоговом отчёте видны пропущенные тесты; разрешён только отдельный тест живого MTProto-соединения. Артефакт coverage содержит профиль покрытия и test-results.jsonl. Локально медиа-проверки без инструментов остаются опциональными.

Дуэли проверяются с управляемыми случайностью и паузами: повторные и параллельные кнопки, устаревшие приглашения, рикошет, ничья и ошибки Telegram. Рабочие вероятности и задержки анимации сохранены. Приглашение привязано к сообщению и участникам; завершённые кнопки не запускают игру повторно. При перезапуске незавершённые приглашения устаревают.

Получение котиков и мемов проверяется через подменённый MTProto-транспорт с настоящими кодированием ответов и загрузчиком файлов. Проверяются байты и тип отправленного медиа, пустые и ошибочные ответы, отсутствие имени файла и одновременный доступ к кэшу каналов. Учётные данные и соединение с живыми каналами этим тестам не нужны.

Проверки команд включают параллельные предупреждения и игровые смерти, ошибки записи, отказ Telegram и сбой итогового сообщения. Смерти сохраняют прежний смысл попыток игры: общий и дневной счётчики меняются одной транзакцией до обращения к Telegram; время мута прибавляется только после подтверждённого ограничения. Ошибки счётчиков возвращаются обработчиком. Успешный мут не откатывается из-за ошибки отправки сообщения. У /warn неуспешное третье предупреждение откатывается, чтобы новый запрос мог повторить попытку; уже достигнутый порог не вызывает повторного мута. Новые /kill и /blessing остаются новыми игровыми действиями, а не повторами старого запроса. Общей гарантии exactly-once для Bot API и БД нет.

routing_scenarios_test.go использует рабочие middleware и регистрацию обработчиков: обычные/эфемерные команды, адресация боту, права, callbacks и chat_member. remaining_postgres_test.go проверяет полную и повторную миграцию, переход со старой схемы, чтение после переподключения и параллельные игровые команды в отдельных схемах. Дополнительно проверяются все шесть медиа-типов гетов, целые подписи с форматированием, права владельца, повреждённые данные, недельная граница предупреждений и полночь/DST розыгрыша. FuzzSedPattern содержит начальный корпус; дополнительный прогон: go test -run '^$' -fuzz '^FuzzSedPattern$' -fuzztime 20s ..

Инфраструктура тестов — testutils_test.go:

  • фейковый Bot API-клиент, записывающий все вызовы бота (проверяется отправленный текст/действия); хук Observe даёт заглянуть в файл, который обработчик закрывает и удаляет сразу после отправки;
  • in-memory SQLite (github.com/glebarez/sqlite, чистый Go, CGO не нужен) вместо PostgreSQL;
  • хелперы setupTestEnv, testMessage, testContext, testCallbackContext, seedUser, waitForCalls.

Сохранение альбомов в личку проверяется при одновременном поступлении сообщений и 128 запросах сохранения: каждая отправка сортирует свою копию списка, не меняет общий кэш и не блокирует его на время запроса к Telegram. Отдельно проверяются пустой кэш и пустой результат копирования.

Основной набор использует локальные заглушки и SQLite. TestStatsPostgresConcurrent дополнительно проверяет на настоящей PostgreSQL одновременное создание слов и обновление счётчиков сообщений, слов и времени мута: 32 параллельных обработчика, по 20 обновлений каждого счётчика. В CI база предоставляется отдельным контейнером; локально тест запускается, если задан ZAVTRA_TEST_POSTGRES_DSN для тестовой базы. Данные создаются в случайной отдельной схеме, которая удаляется после теста. Миграции, история мутов и параллельные игровые команды также проверяются на PostgreSQL; остальные сценарии используют SQLite.

TestMessageStatsQueryCount считает SQL-операторы в обработчике сообщения. После прогрева словаря сообщение из 10 разных слов требует 1 оператор вместо 15, а из 10 повторений одного слова — 1 вместо 6, если данные отправителя также закэшированы. Служебные BEGIN/COMMIT в этот замер не входят. Все счётчики одного сообщения записываются единым атомарным пакетом без дополнительной транзакции GORM; ошибка не оставляет частично применённые счётчики. Ключи упорядочены для параллельных пакетов. Повторы и словоформы объединяются по существующему ID слова; первые написания, периоды статистики и исторические строки сохраняются. FIFO-кэш хранит не больше 4096 корней длиной до 128 байт, привязан к базе и объединяет одновременные загрузки одного корня внутри процесса. Ошибки и нулевые ID не кэшируются. Изменение структуры базы или объединение старых дубликатов слов не требуется.

Тяжёлые медиа-команды используют общую очередь: 2 выполняются, до 8 ждут не более 30 секунд. Обработка ограничена 5 минутами с момента получения места; переполненная очередь возвращает ошибку сразу. На Unix таймаут завершает группу процессов, включая дочерний FFmpeg у yt-dlp. /distort дополнительно сохраняет ограничение в одну такую задачу. Обычные команды не ждут эту очередь. Временные результаты удаляются и при ошибках; общий исходный файл Telegram конвертации не удаляют. Лимит относится к задачам, а не к числу потоков внутри FFmpeg.

Данные пользователей сохраняются при изменении любого поля. Ограниченный FIFO-кэш на 4096 пользователей пропускает повторные записи, привязан к пулу базы и объединяет одновременные записи одного пользователя. Через 5 минут после успешного сохранения выполняется проверочный upsert: PostgreSQL обновляет строку только при отличии полей. Ошибки не кэшируются, удалённая строка восстанавливается после истечения срока. Пустые имена и выключенные флаги тоже сохраняются. Тот же путь используется для сообщений и событий chat_member.

Зависимости от окружения: четырнадцать тестов гоняют настоящие ffmpeg/ffprobe по сгенерированным секундным файлам и пропускаются, если бинарников нет (TestFFmpegConvert, TestFFmpegConvertConcurrentSameName, TestConvert, TestInvert, TestDistort, TestLoopAnimation, TestDownloadFile, TestFFmpegRemoteMedia, TestGetImageForLLM, TestGetImageForLLMRemote, TestGetImageForLLMConcurrentSameFile, TestAIWithPhoto, TestMp3LocalSource, TestDownloadLocalSource); TestMtprotoConnectProbe пропускается без PROTO_PROBE=1, TestYtdlpGetVer — без установленного yt-dlp.

Временные файлы конвертаций (/convert, /mp3, /download, картинка для /ai) и рабочие папки /distort получают уникальные имена — os.CreateTemp, chat_id + message_id, DistortTempDir для тестов. Поэтому несколько прогонов набора на одной машине не перетирают друг другу файлы, а параллельные запросы на один и тот же файл не читают чужую копию; это проверяют TestFFmpegConvertConcurrentSameName и TestGetImageForLLMConcurrentSameFile.

/mp3 и /download работают с реальным yt-dlp на локальных медиафикстурах: ytdlpFixtureServer генерирует секундный m4a-файл и HLS-поток с отдельными видео- и аудиодорожками, отдаёт их по HTTP на 127.0.0.1, а TestMain выписывает loopback из прокси (NO_PROXY), чтобы yt-dlp шёл к фикстуре напрямую. TestMp3LocalSource и TestDownloadLocalSource проверяют, что фильтр форматов выбранного качества действительно находит фикстуру, ffmpeg склеивает/конвертирует дорожки, а отправленный файл — это готовый mp3 и готовый mp4 с обеими дорожками.

Наружу тесты не ходят: весь внешний трафик уходит в локальные заглушки. mitm_test.go поднимает MITM-прокси — TestMain генерирует одноразовый CA, пишет его в SSL_CERT_FILE и публикует прокси через HTTP_PROXY/HTTPS_PROXY, поэтому в заглушку попадают даже клиенты с дефолтным транспортом (клиент CoinMarketCap); сертификаты для хостов подписываются на лету. Два пути перехвата: stubTLSProxy (прокси для httpClient()) и stubEnvRoute (маршрут поверх прокси процесса). Локальные httptest-серверы: заглушка Bot API (TestBotInit — getMe, удаление вебхука, polling и отчёт сисадмину; TestBotInitWebhook — сервер вебхука поднимается до setWebhook, запрос без secret-токена получает 401), OpenAI-формат для /ai (TestAIStream, TestAIStreamError, TestAIWithPhoto). Особые приёмы: TestRestart запускает процесс заново (os.Exit внутри обработчика нельзя проверить из того же процесса), TestConfigInit работает в t.TempDir() и с чистым окружением (clearZavtraEnv), чтобы не трогать .env чекаута. TestStartupReport проверяет отчёт о старте на секреты: в нём нет токена, а из DSN, прокси, app hash и API-ключей вырезаны пароли.

Структура

main.go              конфигурация обработчиков и запуск
utils_bot.go         инициализация бота, polling/webhook, middleware+антиспам
utils_bot_proto.go   MTProto-клиент: подключение, переподключение, таймауты
utils_config.go      Configuration + переменные окружения ZAVTRA_* (.env)
utils_db.go          модели gorm, AutoMigrate, текстовые пулы «да/нет»
utils_handlers.go    обработчики апдейтов: апдейт юзера, альбом-кэш, проверка банов, реакции
utils_user.go        имена и меншены юзеров, списки админов, поиск юзера в сообщении и в БД
utils_stats.go       счётчики статистики сообщений, слов и времени в муте, начало суток
utils_reply.go       Reply, ReplyAndRemove и SysAdminReply — чем команды отвечают пользователю и сисадмину
utils_startup.go     отчёт о старте: сборка, хост, соединение, БД, чаты, версии инструментов (без кредов)
utils_common.go      RandInt, StringInSlice, MarshalOmitEmptyAll
utils_http.go        HTTP-клиент с прокси и таймаутом, DownloadFile
utils_forward.go     пересылка постов канала
utils_files.go       go:embed стикеров и картинки /megamlem (assets/*.png, assets/megamlem.jpg)
utils_plural.go      русские числительные (ruPlural)
media.go             медиа сообщения: тип, файл, где он лежит на диске
media_ffmpeg.go      FFmpegConvert — конвертация для /convert, /distort, /invert, /loop
command_*.go         команды
duel_*.go            дуэль: request/accept/stats
pidor_*.go           игра «Пидор Дня!»
bets_*.go            ставки
ai_*.go              AI-модуль (выключен)
*_test.go            тесты + harness
assets/              стикеры, картинка /megamlem (go:embed) и CREDITS.md

MTProto-клиент

/meow, /woof и /meme работают через MTProto (gotgproto + gotd), а не через Bot API:

  • клиент поднимается в фоновой горутине, подключение ограничено protoConnectTimeout (45 с), при неудаче — повтор каждые protoReconnectDelay (30 с); в логе видно MTProto client connected или причину (MTProto client is not connected (attempt=N, error=…));
  • команды при неподключённом клиенте отвечают «MTProto-клиент ещё не подключился», а не падают;
  • на каждый запрос свой дедлайн (RPC 60 с, скачивание 3 мин), read-only запросы ретраятся;
  • NoUpdates: true — бот не забирает апдейты у long polling Bot API, MTProto используется только для RPC;
  • ZAVTRA_PROXY на MTProto не влияет: датацентры диалсятся прямым TCP, поэтому для этих команд нужен прямой доступ к *:443 (или self-hosted MTProto-транзит).

Известные особенности

  • ZAVTRA_PROXY применяется только к обычным HTTP-запросам бота (advice/anekdot/releases/скачивание файлов); вызовы Bot API и MTProto его не используют.
  • /restart делает os.Exit(0) — перезапуск должен обеспечивать внешний супервизор (Restart=always в systemd, restart: always в compose).
  • Ошибки хендлеров диспетчер отправляет в чат («Ошибка: …» и контекст: текст команды, юзер, чат), токен в тексте подменяется на TOKEN.
  • AI-команды (/ai, /restartai, /setaisystem) закомментированы в main.go.

Разработка

С 16 октября 2026 года разработкой занимается ИИ: правки пишет модель, человек ставит задачи, принимает решения и проверяет результат. Каждый такой коммит помечен в сообщении (Made with the … model.) и соавтором (Co-authored-by: … <noreply@opencode.ai>), поэтому вклад ИИ виден прямо в истории git.

Модели локального стека Strata — конфигурации в ~/.strata/*.json, движок ~/.strata/engine/strata, веса в ~/Strata-data/models:

Модель (model_name) Что это В этом репозитории
swift-1.5-iq3_xxs Swift-1.5 Qwen3.8-Flash-Next GSQ RCO, квант IQ3_XXS (ukisai/Swift-1.5-Qwen3.8-Flash-Next-GSQ-RCO-GGUF) текущая: тест-харнес, фиксы MTProto/числительных, тесты команд, фиксы старта бота и /ai, тесты меню команд, тесты /mp3//download на реальном yt-dlp, конфигурация через .env, README
qwen3.8-flash-next-iq3_xxs Qwen3.8-Flash-Next GSQ RCO, квант IQ3_XXS (ISTA-DASLab/Qwen3.8-Flash-Next-GSQ-RCO-GGUF) e55e505 (go:embed), 30415bd (тесты хелперов + фикс RandInt)
qwen3.8-flash-next она же, до отдельного кванта 4ab27c5 — первый ИИ-коммит (bot_api_url для всех API-вызовов)
orcarouter-qwen3.8-flash-next-uncensored-iq3_xxs Qwen3.8-Flash-Next Uncensored, IQ3_XXS настроена в ~/.strata, к этому коду не применялась

Рядом с ними в стеке: vision-проекции mmproj-Qwen3.8-Flash-Next-BF16.gguf и mmproj-Swift-Qwen3.8-Flash-Next-BF16.gguf, черновик для спекулятивного декодирования mtp-q2_0.gguf, а в ~/.strata/data/gguf_fingerprints.json описаны и другие кванты этих же моделей (Q2_0, IQ2_XS, IQ3_XXS, IQ3_S, coder IQ1_M, unsloth UD-IQ4_XS / UD-Q4_K_XL).

Лицензия

MIT (см. LICENSE), © 2026 NexonSU. Лицензия покрывает исходники бота.

Что она не покрывает:

  • assets/bonk.png и assets/hug.png — сторонние мемы, права на них не передаются; подробности в assets/CREDITS.md. При публичной публикации их стоит убрать или заменить на свои.
  • ffmpeg и yt-dlp — внешние GPL-программы, бот вызывает их отдельным процессом; лицензия бота на них не распространяется, условия их лицензий — на их стороне.
  • Почти все Go-зависимости — MIT / BSD-3 / Apache-2.0, они с MIT совместимы. Исключение — github.com/celestix/gotgproto под GPL-3.0: он линкуется в бинарник, поэтому при распространении собранного бота условия GPL-3 действуют на работу целиком (придётся раскрыть исходники). Для запуска бота на своём сервере обязательств нет: GPL срабатывает при распространении, а не при использовании.

Команды фраз спасения доступны администраторам и модераторам (ZAVTRA_ADMINS, ZAVTRA_MODERS). Фразы хранятся отдельно от блессингов; управление ими не меняет вероятность блесса или статистику. Последнюю фразу удалить нельзя.

/blessing, /suicide и /isekai дают 33% шанс спасения: имя и случайная фраза из /salvationlist, затем «Живи. Пока что.». Спасение не добавляет смерть или время мьюта и не участвует в серии блессов. Админский рикошет всегда даёт блесс. При пустом наборе спасений работает обычный блесс. Спасения видны в /duelstats и /stats saves [day|week].

Documentation

The Go Gopher

There is no documentation for this package.

Source Files

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL