Телеграм-бот напоминаний (Bun + grammY + LiteLLM)
  • TypeScript 99.5%
  • Shell 0.3%
  • Dockerfile 0.2%
Find a file
2026-10-16 12:00:34 +03:00
docs/specs docs: спек — путь кнопки мини-аппа и e2e-прогон 2026-10-16 12:00:34 +03:00
scripts chore: обвязка запуска e2e на сервере 2026-10-16 11:59:07 +03:00
src fix: поиск не тонет в нечётких совпадениях, e2e 19/19 2026-10-16 11:58:55 +03:00
test feat: бот как Telegram-интерфейс к Vikunja 2026-10-16 11:24:23 +03:00
.env.example feat: прод-хардening и новые фичи 2026-07-21 14:05:53 +03:00
.gitignore feat: телеграм-бот напоминаний (grammY + bun:sqlite + LiteLLM парсинг) 2026-07-21 10:36:42 +03:00
bun.lock feat: телеграм-бот напоминаний (grammY + bun:sqlite + LiteLLM парсинг) 2026-07-21 10:36:42 +03:00
bunfig.toml feat: бот как Telegram-интерфейс к Vikunja 2026-10-16 11:24:23 +03:00
docker-compose.yml feat: телеграм-бот напоминаний (grammY + bun:sqlite + LiteLLM парсинг) 2026-07-21 10:36:42 +03:00
Dockerfile feat: прод-хардening и новые фичи 2026-07-21 14:05:53 +03:00
package.json feat: локальный парсер первым эшелоном + диагностика 2026-09-05 19:57:02 +03:00
README.md chore: обвязка запуска e2e на сервере 2026-10-16 11:59:07 +03:00
tsconfig.json refactor: бот собирается фабрикой createApp + e2e-прогон кнопок 2026-10-16 11:55:14 +03:00

reminder-bot

Телеграм-интерфейс к личному трекеру Vikunja (@bysaw_remind_bot). Пишешь или наговариваешь в свободной форме — бот заводит задачу или напоминание в Vikunja, присылает напоминания, сводки изменений и просрочки.

  • «через 20 минут снять чай» → «Напоминания», напомнит через 20 минут
  • «сфера: смержить !858 завтра в 10» → проект «Сфера», срок и напоминание
  • «каждый понедельник в 10 планёрка» → повтор (ведёт бот)
  • «шлюз 404 ждёт devops», «срочно …» → метки и приоритет
  • «сделал отчёт», «перенеси планёрку на 11», «перенеси блокеры в сферу», «пометь шлюз ждёт бэк», «коммент к шлюзу: всё ещё 404»
  • пересланное сообщение (можно с подписью «завтра в 10») → задача с текстом и ссылкой на источник; фото, файлы, альбомы → вложения
  • «что на сегодня», «что ждёт других», «что по сфере», «найди шлюз»

Архитектура

Telegram ⇄ bot (grammY, polling) ──REST v1──▶ Vikunja (http://vikunja-<uuid>:3456)
                 ▲                                   │
                 └── :8080/vikunja ◀── webhooks ─────┘  (HMAC X-Vikunja-Signature)
  • src/vikunja.ts — клиент API v1 (ретраи чтений, таймаут 10 с, read-modify-write правок: POST /tasks/{id} в v1 — полная замена).
  • src/store.ts — доменный слой: создание, done, перенос, snooze, проекты, метки, списки (сегодня/неделя/ждёт/мне/входящие/проект/поиск), поиск кандидатов.
  • src/webhook.ts — приёмник вебхуков (Bun.serve, /vikunja, /health), проверка HMAC.
  • src/changes.ts — эхо-фильтр своих правок (60 с), снимки задач для диффа, debounce-сводка «🔔 Изменения» (2 мин тишины, максимум 10 мин; «Напоминания» и перестановки в канбане не попадают).
  • src/cards.ts — карточки, списки, клавиатуры (HTML).
  • src/parse.ts, src/localparse.ts, src/domain.ts — интенты: локальный парсер первым, LLM (LiteLLM) фолбэком; проекты по ключевым словам, метки «ждёт: …», приоритет.
  • src/migrate.ts — разовый перенос старых напоминаний SQLite → Vikunja («Напоминания»), meta migrated_vikunja.
  • src/db.ts — SQLite: meta, parse_log, repeat_rules (повторы ведёт бот, в Vikunja repeat_after = 0), task_snapshots, pending_ops (очередь создания, когда Vikunja лежит), cb_state (состояние кнопок). Таблица reminders — только для отката.

Вебхуки Vikunja:

  • пользовательский (id 1) → task.reminder.fired, tasks.overdue (user-level принимает только такие события);
  • проектные на всех корневых проектах → task.created/updated/deleted, task.comment.created. Их заводит и перепроверяет раз в час сам бот (WEBHOOK_TARGET_URL), подпроекты покрываются родителем.

Повторы: правило в repeat_rules; при срабатывании напоминания бот сразу переставляет срок на следующее вхождение; «Готово» у повторяющейся не закрывает её; закрыли в UI Vikunja — бот откроет со следующей датой; snooze повторяющейся — отдельное разовое напоминание.

Команды: /today /week /waiting /mine /inbox /overdue /p <проект> /find <текст> /list /app /health /unparsed, /t_<id> — карточка задачи. Кнопка меню чата открывает Mini App (TMA_URL).

Тесты: bun test (парсер, проекты/метки, клиент на моках fetch, повторы ↔ даты, карточки и клавиатуры, HMAC, debounce и эхо, миграция на SQLite-фикстуре), bun run typecheck.

E2E-прогон кнопок (scripts/e2e.ts)

Настоящий код бота (createApp()) против настоящей Vikunja, Telegram подменён: апдейты идут через bot.handleUpdate, все вызовы Bot API перехватываются bot.api.config.use и записываются (в Telegram ничего не уходит), скачивание файлов — фикстура. Все записи — во временное дерево проектов «E2E …» под «Напоминаниями» (прод-бот это поддерево в сводку не берёт), в конце удаляется. 19 сценариев: создание с проектом/меткой/сроком, 📁/🏷/✅/↩️/+1ч/Завтра/Отменить, ⏰ из подписанного вебхука (+10 мин, Готово, повтор), «сделал …» с выбором, /today /waiting /p /find с ◀ ▶ и ✅n, пересылка с подписью, альбом с вложениями, /app и меню, /t_.

Запуск на сервере рядом с прод-ботом (его образ и env, токен с правом создавать/удалять проекты — E2E_ADMIN_TOKEN, лежит в /root/.config/vikunja-hooks/e2e.env):

rsync -a src scripts hermes-server:/tmp/rb-e2e/ && ssh hermes-server 'sudo bash /tmp/rb-e2e/scripts/e2e-server.sh'

scripts/e2e-server.sh берёт образ и env прод-контейнера, добавляет e2e.env и запускает bun scripts/e2e.ts. Выход 0 — всё PASS.

Env

Var Что
BOT_TOKEN токен от @BotFather
ALLOWED_USER_IDS CSV user id (whitelist)
ADMIN_CHAT_ID / OWNER_CHAT_ID алерты / куда слать напоминания и сводки (default: первый из whitelist)
VIKUNJA_URL адрес API, в Coolify — http://vikunja-02adxvhzdteuziuuz7arzaml:3456 (внутренняя сеть coolify)
VIKUNJA_TOKEN API-токен «reminder-bot»
VIKUNJA_PUBLIC_URL для ссылок «🔗 Открыть» (default https://tasks.openbomber.com)
VIKUNJA_WEBHOOK_SECRET HMAC-секрет вебхуков
WEBHOOK_PORT порт приёмника (8080, наружу не публикуется)
WEBHOOK_TARGET_URL как Vikunja достучится до бота: http://cgv2d141nfvy73p8kqbtaxgl:8080/vikunja; пусто — бот не трогает проектные вебхуки
TMA_URL Mini App (https://tasks-app.openbomber.com)
NO_PROXY должен включать хост Vikunja, иначе запросы уйдут в HTTP_PROXY
LLM_BASE_URL / LLM_API_KEY / LLM_MODEL / LLM_FALLBACK_MODELS / LLM_TIMEOUT_MS парсер
ASR_BASE_URL / ASR_API_KEY / ASR_MODEL голосовые
WATCHDOG_MINUTES проверка LLM-цепочки (0 — выкл)
TZ Europe/Moscow (как у пользователя в Vikunja)
DB_PATH sqlite (/data/reminders.db)

Деплой

Coolify (MS-02), app cgv2d141nfvy73p8kqbtaxgl: push в main на git.openbomber.com/bysawec/reminder-bot → Forgejo webhook → автодеплой. Контейнеру нужен HTTPS_PROXY (killswitch сервера режет прямой WAN) и NO_PROXY с хостом Vikunja.

Откат на версию до Vikunja

  1. git revert мержа feat/vikunja (или деплой коммита 2d16df5) — старая версия снова читает таблицу reminders (она не тронута миграцией) и в TZ вернуть Asia/Yekaterinburg, DIGEST_TIME=09:00.
  2. Вебхук пользователя Vikunja id 1 вернуть на http://10.0.1.1:8790 со старым секретом (SQL в таблице webhooks, секрет — в /root/.config/vikunja-hooks/), проектные вебхуки бота удалить (target_url = адрес бота).
  3. sudo systemctl enable --now vikunja-tg-hook.