Telegram-бот, уведомляющий о свободных ЭЗС всех трёх сетей, агрегированных под customer.example.com: Primary network, Evika, Battery-fly.
Гибридная модель источников статуса:
| Сеть | Способ | Частота |
|---|---|---|
| Evika / Battery-fly | REST bulk /{op}/locations/map |
раз в POLL_INTERVAL_SEC (10 c) |
| Primary network | SSE /devices/status-stream?deviceNumber=N |
push real-time |
- Поллер держит:
- REST-дифф-воркер для Evika/Battery-fly: сравнивает предыдущее состояние с новым, пишет дельты в Redis Stream
charger:events. - Catalog sync: раз в
CATALOG_SYNC_INTERVAL_SECподтягивает список локаций Primary network (для поиска/подписок). - SSE-manager: каждые
SSE_SYNC_INTERVAL_SECчитает активные подписки из БД, открывает SSE на каждыйdeviceNumberподписанной Primary network-локации, закрывает стримы на бесхозные устройства. Lazy: нет подписок → нет SSE-коннектов.
- REST-дифф-воркер для Evika/Battery-fly: сравнивает предыдущее состояние с новым, пишет дельты в Redis Stream
- Бот (aiogram 3) принимает команды + читает
charger:events. - Приоритет уведомлений: paid — мгновенно; free — через
FREE_TIER_NOTIFY_DELAY_SECсек (по умолчанию 120). - Лимиты подписок: free —
FREE_TIER_MAX_SUBSCRIPTIONS, paid —PAID_TIER_MAX_SUBSCRIPTIONS. - Оплата: Telegram Stars (
XTR), без провайдера —/upgrade.
- Python 3.12
- aiogram 3 — Telegram bot
- httpx — HTTP
- SQLAlchemy 2 async + asyncpg, Alembic для миграций
- PostgreSQL 16, Redis 7
- docker-compose для локального стека
- Сервер в Беларуси (VPS в BY или прокси через BY-узел). API-шлюз провайдера отвечает только с BY-IP — извне таймаутит. Если поднимаешь не в РБ, укажи
HTTP_PROXY_URLв.env. - Python 3.12+, Docker, Docker Compose.
- Telegram-бот, созданный через @BotFather.
git clone https://github.com/devAsmodeus/Charger-Watcher.git
cd Charger-Watcher
cp .env.example .env
# впиши TG_BOT_TOKEN; если сервер не в РБ — HTTP_PROXY_URL
docker compose up -d postgres redis
# один раз — миграции
docker compose run --rm poller alembic upgrade head
# запуск
docker compose up -d poller bot
docker compose logs -f poller botЛокально без Docker:
python -m venv .venv && . .venv/Scripts/activate # PowerShell: .venv\Scripts\Activate.ps1
pip install -e ".[dev]"
# подними PG+Redis любым способом, укажи DATABASE_URL / REDIS_URL в .env
alembic upgrade head
# src должен быть на PYTHONPATH (alembic уже знает через prepend_sys_path):
# PowerShell: $env:PYTHONPATH = "src"
# bash: export PYTHONPATH=src
python -m poller.main # терминал 1
python -m bot.main # терминал 2/start— регистрация и шпаргалка/nearby— запрашивает геолокацию, возвращает 10 ближайших станций с inline-кнопкой «🔔 Подписаться»/find <запрос>— поиск по названию/адресу, 10 совпадений с той же кнопкой/list— мои подписки/unsubscribe <id>— снять подписку/status— мой тариф и срок действия/upgrade— купить платный тариф (Telegram Stars)
См. .env.example. Ключевое:
| Переменная | Значение |
|---|---|
TG_BOT_TOKEN |
Токен бота от @BotFather |
DATABASE_URL |
PG URL в формате asyncpg |
REDIS_URL |
Redis URL |
POLL_INTERVAL_SEC |
Интервал опроса bulk-статусов, сек (по умолчанию 10) |
HTTP_PROXY_URL |
Опциональный прокси к API (если хост не в РБ) |
FREE_TIER_NOTIFY_DELAY_SEC |
Задержка нотификации free-юзерам, сек |
FREE_TIER_MAX_SUBSCRIPTIONS |
Лимит подписок free |
PAID_TIER_MAX_SUBSCRIPTIONS |
Лимит подписок paid |
Charger-Watcher/
├── src/
│ ├── api/ # httpx-клиент + pydantic-модели ответов
│ ├── bot/ # aiogram-бот + notifier consumer
│ ├── poller/ # долго живущий поллер статусов
│ ├── db/ # SQLAlchemy-модели и сессия
│ ├── config.py # pydantic-settings из .env
│ └── logging_setup.py
├── alembic/ # миграции
├── docker-compose.yml
├── Dockerfile
└── pyproject.toml
От коммита до прода без ручных шагов на сервере — три GitHub Actions workflow-а:
- CI (
.github/workflows/ci.yml) — на каждый PR/push вmain:Lint(ruff),Test(pytest),Dependency audit(pip-audit поpyproject.toml). Branch protection требует зелёные чеки, иначе мерж заблокирован. - Release Please (
.github/workflows/release-please.yml) — на push вmainпарсит conventional-commits с момента последнего тега и открывает / обновляет PRchore(main): release X.Y.Zс бампом версии вpyproject.tomlи автогенерированнымCHANGELOG.md. После мержа этого PR создаёт git-тегvX.Y.Zи GitHub Release. - Deploy (
.github/workflows/deploy.yml) — слушаетrelease.published(иworkflow_dispatchдля ручного запуска). SSH на прод-сервер →git checkout --detach <tag>→alembic upgrade head→docker compose up -d --build. Detached HEAD на тег обходит расхождение server-localmainсorigin/main.
Ручной деплой любого ref-а:
gh workflow run deploy.yml -f ref=v0.1.0 # конкретный тег
gh workflow run deploy.yml -f ref=main # текущий main
gh workflow run deploy.yml -f ref=<sha> # коммит| Префикс | Bump | В CHANGELOG |
|---|---|---|
feat: … |
minor | Features |
fix: … |
patch | Bug Fixes |
feat!: … или BREAKING CHANGE: в теле |
major | ⚠ Breaking |
chore:, docs:, refactor:, ci:, test: |
— | не попадает |
Если коммит не подходит под префикс, release-please его проигнорит — для технического долга это норма, для фичи это баг (не попадёт в релиз).
| Secret | Назначение |
|---|---|
RELEASE_PLEASE_TOKEN |
fine-grained PAT (contents: write, pull-requests: write). Без него релиз-PR создаётся от GITHUB_TOKEN, на который CI не запускается → встанет колом под branch protection |
DEPLOY_HOST / DEPLOY_USER / DEPLOY_SSH_KEY |
SSH-доступ к прод-серверу. Ключ — отдельный deploy-key, не личный SSH-ключ |
- MVP: Evika + Battery-fly bulk-мониторинг, подписки на локации
- Primary network через публичный SSE
/devices/status-stream(lazy per-device subs) - Геопоиск
/nearby(bounding-box + haversine, сортировка по расстоянию) - Платный тариф через Telegram Stars (
/upgrade,/status) - Дедуп + cooldown через
notification_log - Отмена delayed-уведомлений free-юзерам, если локация снова стала не-AVAILABLE
- Auto-downgrade paid → free по истечении
paid_until - Rate-limit Telegram (
aiolimiter) против Telegram flood - Отписка inline-кнопкой прямо из уведомления
-
/privacyи/delete_me - SSE backoff с экспоненциальной реконнект-стратегией
- Фильтр по типу коннектора в подписке (через subscribe-wizard)
- Информативные уведомления — типы свободных коннекторов в пуше
- CI + автоматический релиз и деплой на тег
- Адресный поиск через Nominatim (
/nearby <адрес>) - Webhook-режим бота и тесты (pytest + respx)
MIT