Telegram-бот, который присылает утренние гороскопы подписчикам и в группах/каналах. Тексты гороскопов парсятся со страниц Рамблера. Всё на TypeScript.
- TypeScript (strict, ESM) —
tscсборка,tsxdev-запуск - grammY — Telegram Bot API
- axios + cheerio — парсинг Рамблера
- zod + dotenv — валидация конфигурации
- pino — логирование
- Vitest — тесты
- ESLint + Prettier — качество кода
- Node.js ≥ 20
- Postgres (локально — через
docker compose)
cp .env.example .env # заполнить BOT_TOKEN (получить у @BotFather)
npm install
docker compose up -d # локальный Postgres на порту 5433
npm run db:migrate # применить миграции
npm run dev| Скрипт | Назначение |
|---|---|
npm run dev |
Запуск в режиме разработки (tsx watch) |
npm run build |
Сборка в dist/ |
npm start |
Запуск собранной версии |
npm run db:migrate |
Применить миграции БД |
npm run typecheck |
Проверка типов без сборки |
npm run lint |
ESLint |
npm run format |
Prettier |
npm test |
Тесты (Vitest) |
npm run test:watch |
Тесты в watch-режиме |
src/
index.ts # точка входа (bootstrap + graceful shutdown)
config/env.ts # типизированный конфиг (zod)
core/logger.ts # логгер (pino)
bot/ # ядро бота на grammY + подписка (готов)
parser/ # парсер Рамблера (готов)
db/ # Postgres: пул, миграции, репозитории (готов)
services/ # гороскоп-сервис (кэш) + утренняя рассылка
scheduler/ # node-cron: ежечасный запуск рассылки
migrations/ # SQL-миграции
tests/ # тесты (+ fixtures/ — сохранённый HTML Рамблера)
Postgres. Слой доступа — src/db: пул (pg), runner миграций (migrations/*.sql)
и репозитории subscribers / chats / cache (тонкие функции, принимают
Executor, параметризованные запросы). Тесты — offline на pg-mem.
import { getPool, upsertSubscriber, getCachedHoroscope } from './db/index.js';
const db = getPool();
await upsertSubscriber(db, { userId: 123, sign: 'aries', notifyHour: 9 });Модуль src/parser вытягивает гороскопы по 12 знакам зодиака со страниц
horoscopes.rambler.ru. Основной источник —
JSON-состояние страницы (__PRELOADED_STATE__), с fallback на SSR-разметку.
import { getHoroscope, getAllHoroscopes } from './parser/index.js';
const leo = await getHoroscope('leo', 'today'); // один знак
const all = await getAllHoroscopes('today'); // все 12 знаковПоддерживаемые периоды: today, tomorrow. Знаки задаются slug'ом
(aries, taurus, …); соответствие русским названиям — в src/parser/signs.ts.
Планировщик (node-cron) раз в час запускает runMorningBroadcast. Подписчику
уходит гороскоп, если его локальный час (по таймзоне) совпадает с notify_hour
и сегодня ему ещё не отправляли (last_notified_date — идемпотентность за день,
устойчиво к рестартам). Гороскоп на каждый знак тянется один раз за прогон
(кэш в БД + мемоизация).
Бот реагирует на my_chat_member: при добавлении в группу/супергруппу/канал
создаёт запись чата, при удалении — отключает. Команда /signs (только в
группах, только для админов) открывает мультивыбор знаков — их гороскопы
публикуются в чат в утренней рассылке. Каждый выбранный знак — отдельное
сообщение.
В канале команд с клавиатурой нет, поэтому знаки задаются текстом в посте канала:
/signs овен, лев, рыбы (принимаются слаги и русские названия).
/settings открывает выбор времени рассылки и таймзоны:
- в личке — для подписчика, в группе (только админ) — для чата;
- час из утреннего диапазона + курированный список таймзон РФ/СНГ, текущее значение помечено ✅, изменение применяется сразу.
- Запросы к Рамблеру повторяются с экспоненциальным бэкоффом при сетевых ошибках, таймаутах, 5xx и 429; если разметка сменилась — fallback на SSR.
- В рассылке недоставляемые адресаты (бот заблокирован/кикнут, чат удалён) автоматически деактивируются, чтобы не копить ошибки.
- При сбоях рассылки уходит алерт в
ADMIN_CHAT_ID(если задан).
Образ собирается multi-stage Dockerfile (миграции применяются на старте в
bootstrap).
VPS (Docker Compose):
BOT_TOKEN=<токен> docker compose -f compose.production.yml up -d --buildKubernetes + Jenkins (k8s/ + Jenkinsfile, топология как у reminder-bot —
1 реплика, стратегия Recreate, т.к. long polling допускает только один инстанс).
Образ — приватный GHCR ghcr.io/1t1scool/horoscope-bot.
Секрет применяется один раз (вручную), деплой его не сбрасывает:
cp k8s/secret.example.yaml k8s/secret.yaml # заполнить BOT_TOKEN/DATABASE_URL
kubectl apply -f k8s/secret.yamlДальше катит Jenkins (Jenkinsfile): тесты (typecheck+lint+build+test в node-контейнере)
→ build+push образа в GHCR → sed тега в k8s/deployment.yaml → kubectl apply
→ kubectl rollout status. Нужны в Jenkins: credential ghcr (GHCR-токен) и доступ
kubectl к кластеру. В кластере заранее заведены ghcr-secret (pull) и общий Postgres.
Ручной деплой: kubectl apply -f k8s/deployment.yaml.
Проект ведётся по майлстоунам: каркас → парсер Рамблера → БД → ядро бота → подписки → утренняя рассылка → группы/каналы → настройки → надёжность → деплой.