Все кейсы
Кейс · Telegram-бот

Бот, который пишет вовремя

Дуа-бот присылает нужное дуа в момент, когда оно уместно: перед дорогой, в пятницу, в дождь, в затмение, перед постом. Не рассылка по расписанию, а два десятка поводов, каждый из которых нужно уметь распознать — и не ошибиться.

TypeScriptNode 24PostgreSQL 17grammYDrizzle

И что он умеет?

Двадцать поводов и ни одного спама

Человек выбирает, насколько плотно с ним разговаривать: три пресета — от коротких утренних и вечерних азкаров до редких поводов вроде затмения. Дальше бот молчит, пока повод не наступит.

Внутри — 31 дуа с огласовками, транслитерацией, русским переводом, источником и честной пометкой о степени достоверности хадиса. Времена намаза внутри Дагестана берутся из 27 таблиц муфтията на каждый день года, за его пределами считаются по координатам и помечаются как приблизительные — потому что расчёт и таблица расходятся на минуты, и делать вид, что это одно и то же, нельзя.

Дуа в подборке
31, с источником и огласовками
Города
5754 с часовым поясом
Тесты
77 файлов, гоняются против живого Postgres
От первого коммита до боя
2 дня. Дальше правки шли уже на живом боте

А что там сложного? Бот же.

Сложное начинается там, где никто не смотрит

  • Не отправить дважды важнее, чем не пропустить. Сообщение помечается «отправляется» до обращения к Telegram, а не после. Если процесс упадёт в этот момент, запись останется висеть — и уборщик пометит её неудачной, а не отправит заново. Дубль в личной переписке раздражает сильнее, чем пропуск.
  • Очередь живёт в PostgreSQL, Redis нет. Задачи разбираются порциями, и база сама не даёт двум процессам взять одну и ту же. Повторный запуск планировщика за те же сутки ничего не задваивает — упирается в ограничение базы. Одна база вместо двух — меньше того, что может разъехаться ночью.
  • Погода: решает первый ответивший источник. Метеосводки аэропортов, норвежская модель и открытая модель опрашиваются по очереди. Голосование «видит ли хоть кто-нибудь дождь» отвергли сразу: 5 августа над Махачкалой шла настоящая гроза, а расчётные модели показывали десятые доли миллиметра мороси — большинство было бы против дождя.
  • Затмения считаются локально, без единого внешнего запроса. Глобальный календарь кандидатов считается один раз на четыре года, и только рядом с датой запускается точный локальный расчёт — иначе пришлось бы перебирать новолуния для каждого человека каждые сутки.

А кто это всё потом обслуживает?

Выкатка без моего участия

Каждый push проходит проверку типов, линтер, миграции и тесты против настоящего Postgres — не заглушек. Только после зелёного прогона собирается образ. Сервер забирает его сам, таймером раз в пять минут: входящий доступ на машину не нужен, а значит нечего ломать и некуда терять ключи.

Так же собран и сайт, который ты сейчас читаешь.