Skip to content

Repository files navigation

horoscope-bot

Telegram-бот, который присылает утренние гороскопы подписчикам и в группах/каналах. Тексты гороскопов парсятся со страниц Рамблера. Всё на TypeScript.

Стек

  • TypeScript (strict, ESM) — tsc сборка, tsx dev-запуск
  • 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-скрипты

Скрипт Назначение
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 --build

Kubernetes + 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.yamlkubectl applykubectl rollout status. Нужны в Jenkins: credential ghcr (GHCR-токен) и доступ kubectl к кластеру. В кластере заранее заведены ghcr-secret (pull) и общий Postgres.

Ручной деплой: kubectl apply -f k8s/deployment.yaml.

Дорожная карта

Проект ведётся по майлстоунам: каркас → парсер Рамблера → БД → ядро бота → подписки → утренняя рассылка → группы/каналы → настройки → надёжность → деплой.

About

Telegram-бот утренних гороскопов на TypeScript: подписка, рассылка по расписанию, группы/каналы, парсер Рамблера. grammY + Postgres.

Resources

Stars

16 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages