YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

🕵️ Dialog Spy Bot

Telegram-бот — полный аналог «Dialog Spy Bot»: архивирует личные чаты пользователя через официальный функционал Telegram Business («Автоматизация чатов» / business_connection), возвращает удалённые сообщения и показывает правки в формате «Было → Стало».

Никаких юзерботов (Pyrogram/Telethon), никакого чтения чужих аккаунтов в обход API — только Bot API 7.1+ / aiogram 3.x.


1. Как это работает

┌──────────────────────┐   Business Connection   ┌───────────────────────┐
│ Пользователь Premium │ ───────────────────────►│  Telegram Bot API      │
│ Настройки → Telegram │                         │  updates:              │
│ Business → Автомати- │                         │   business_connection  │
│ зация чатов → бот    │                         │   business_message     │
└──────────────────────┘                         │   edited_business_msg  │
                                                 │   deleted_business_msgs│
                                                 └───────────┬───────────┘
                                                             │ polling / webhook
                                                             ▼
                                            ┌────────────────────────────────┐
                                            │        Dialog Spy Bot          │
                                            │  aiogram Router (business.py)  │
                                            │   ├─ архив → SQLite            │
                                            │   ├─ TTL-медиа → диск сразу    │
                                            │   ├─ edit → diff → алерт       │
                                            │   └─ delete → оригинал → алерт │
                                            └────────────────────────────────┘
  1. Пользователь с Telegram Premium подключает бота к своим личным чатам.
  2. Бот получает business_message (и входящие, и исходящие) и складывает их в БД.
  3. deleted_business_messages → находим сообщения в БД → отправляем владельцу оригинал (текст + скачанный файл).
  4. edited_business_message → сравниваем с версией из БД → отправляем diff.

2. Структура проекта

dialog-spy-bot/
├── bot/
│   ├── main.py                     # точка входа: Bot + Dispatcher + polling/webhook
│   ├── config.py                   # настройки (pydantic-settings, .env)
│   ├── logging_config.py           # формат логов
│   ├── db/
│   │   ├── schema.sql              # DDL: owners, connections, chats, messages,
│   │   │                           #      message_versions, events_log
│   │   └── database.py             # aiosqlite: подключение + все запросы (репозиторий)
│   ├── filters/
│   │   └── business.py             # HasBusinessConnection / ConnectionKnown / IsOutgoingBusiness
│   ├── handlers/
│   │   ├── business.py             # ★ 4 business-хэндлера (основная логика)
│   │   ├── user.py                 # /start /status /settings /search /export /help
│   │   └── errors.py               # глобальный error-handler
│   ├── middlewares/
│   │   └── throttling.py           # антиспам на команды пользователя
│   ├── services/
│   │   ├── media_extractor.py      # content_type / file_id / текст из Message
│   │   ├── ttl_media.py            # ★ политика и немедленное скачивание сгорающих медиа
│   │   ├── text_diff.py            # пословный + unified diff
│   │   ├── notifier.py             # ★ доставка алертов владельцу (ретраи, flood-wait)
│   │   └── background.py           # RetentionWorker: чистка БД и диска
│   └── utils/
│       └── formatting.py           # имена, даты, HTML-экранирование, truncate
├── tests/
│   ├── test_units.py               # diff + извлечение медиа
│   ├── test_business_flow.py       # сквозной сценарий на реальном SQLite
│   └── test_smoke_main.py          # реальный Bot + фейковый Telegram API (aiohttp)
├── data/                           # создаётся автоматически: spy.db + media/
├── .env.example
├── requirements.txt
├── pyproject.toml
└── Dockerfile

3. Схема БД (SQLite, WAL)

Таблица Назначение Ключевые колонки
owners владелец бизнес-аккаунта + его настройки алертов user_id, notify_chat_id, alert_on_deleted/edited/outgoing, retention_days
connections связка business_connection_id → user_id id (PK), user_id, user_chat_id, status, is_enabled, can_reply, rights_json, connected_at, last_event_at
chats кэш метаданных диалога (имя собеседника для алертов) chat_id (PK), chat_type, title, username, first_name, last_name
messages ядро архива PK (chat_id, message_id), business_connection_id, owner_user_id, is_outgoing, sender_id, sender_name, content_type, text, caption, file_id, file_unique_id, file_size, mime_type, media_group_id, local_path, is_ttl_media, has_protected_content, media_download_state, edit_count, is_deleted, deleted_at, date, raw_json
message_versions история версий для diff (chat_id, message_id, version), text, file_id, edited_at
events_log аудит всех событий и ошибок event_type, owner_id, chat_id, message_id, payload

Индексы: по business_connection_id, owner_user_id+created_at, is_deleted+deleted_at, media_download_state+created_at, created_at (для retention).

Полный DDL — bot/db/schema.sql.


4. Запуск

git clone <repo> && cd dialog-spy-bot
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env      # вписать BOT_TOKEN
python -m bot.main        # long polling

Подключение к аккаунту (на телефоне пользователя)

  1. Нужен Telegram Premium.
  2. Настройки → Telegram BusinessАвтоматизация чатов (Chat automation).
  3. Выбрать нашего бота, разрешить доступ к личным чатам.
  4. Бот получит business_connection и пришлёт подтверждение в личку.

⚠️ Бот должен быть создан у @BotFather как обычный бот. Право «Business bots» включается автоматически при первом business_connection. Для продакшена рекомендуется BOT_MODE=webhook (HTTPS обязателен).

Тесты

pip install pytest pytest-asyncio
pytest -q          # 12 тестов, сеть не нужна (фейковый Telegram API)

5. TTL-медиа (сгорающие фото/видео) — главное место, где все ошибаются

Если сохранить только file_id, после срабатывания таймера самоуничтожения getFile вернёт ошибку и контент будет потерян навсегда. Поэтому в services/ttl_media.py файл выкачивается в момент получения апдейта.

Важная деталь API. В business-событиях Telegram не отдаёт поле таймера самуничтожения: в схеме Message (проверено на aiogram 3.31 / Bot API 9.x) нет self_destruct / message_auto_delete_timer. Доступные маркеры риска:

Маркер Что означает
has_protected_content = True запрет пересылки/копирования — типично для одноразового контента
has_media_spoiler = True скрытое медиа, косвенный признак
отсутствие file_id у медиа контент уже недоступен боту

Политика настраивается через MEDIA_DOWNLOAD_MODE:

Значение Поведение
protected качать только при has_protected_content
smart (по умолчанию) has_protected_content или has_media_spoiler
always качать всё входящее медиа — максимальная надёжность, расходует диск

Для гарантированного перехвата именно секретных фото/видео используйте always: это единственный режим, который не зависит от эвристик.

Ограничения Bot API:

  • файл больше 20 МБ скачать нельзя (getFile не отдаёт) → статус too_large, владельцу уходит текст сообщения и пометка;
  • обход — собственный local Bot API server (is_local=True, лимит 2 ГБ).

6. Логика по событиям

business_connection

upsert_owner + upsert_connection (сохраняем business_connection_id → user_id и user_chat_id — цель для алертов), пишем права из BusinessBotRights, отправляем владельцу подтверждение/уведомление об отключении.

business_message

  1. Фильтр HasBusinessConnection отбрасывает апдейты неизвестных/выключенных подключений и инжектит в хэндлер объект conn.
  2. media_extractor определяет content_type, file_id (для фото — крупнейший PhotoSize), text/caption, entities.
  3. should_download_now() → при риске TTL немедленно bot.download() на диск (сначала пишем строку со статусом pending, чтобы не потерять факт получения).
  4. upsert_message + message_versions(version=0) + events_log.
  5. Опционально (alert_on_outgoing) — уведомление о собственных исходящих.

edited_business_message

  1. Достаём старую версию из БД. Если её нет (сообщение старше подключения) — архивируем текущую и честно сообщаем, что оригинала нет.
  2. text_diff.compare_texts() → пословный diff ([− было −] → [+ стало +]) и unified.
  3. apply_edit() + новая строка в message_versions.
  4. Алерт владельцу: «✏️ Изменено сообщение от [Имя]. Было: … → Стало: …» + разница.

deleted_business_messages

  1. Пачка message_ids в рамках одного чата → get_messages().
  2. Для каждого найденного: mark_deleted() → алерт с оригинальным текстом → отправка файла (локальная копия → иначе file_id → иначе «медиа недоступно»).
  3. Для ненайденных id — отдельное сообщение «Удаление без архива» (важно для отладки: значит, сообщение пришло до подключения бота).

6.5 Монетизация, квоты и роли

Подписка за Telegram Stars (/subscribe): неделя = 50⭐, месяц = 120⭐, год = 500⭐ (значения в config.py: SUB_STARS_WEEK/MONTH/YEAR). Оплата через sendInvoice(currency="XTR"), активация по message.successful_payment, хранение в таблице subscriptions.

Бесплатный лимит восстановлений удалённых сообщений:

  1. первые FREE_RESTORE_TOTAL (10) восстановлений — бесплатно;
  2. после исчерпания — FREE_RESTORE_DAILY (1) раз в сутки;
  3. дальше вместо контента приходит тизер «🙈 Кто-то в вашем чате удалил сообщение… подключите подписку» с кнопками тарифов. Квота списывается только при фактической выдаче контента (services/access.py).

Роли: ADMIN (id из ADMIN_IDS, по умолчанию 7603742317) → TESTER (безлимит на N дней, выдаёт админ: /testers add <id|@username> <дней>, /testers remove, /testers list) → SUBSCRIBERFREE. Админская статистика: /stats.

Копия сгорающего медиа по ответу: владелец отвечает любым текстом на фото/видео/ГС/кружок в личном чате → бот сохраняет файл на сервер (скачивает немедленно, если копии ещё нет) и присылает её в личку бота. Работает через business_message с reply_to_message.

7. Надёжность

  • Retry/flood-wait в notifier._send_with_retry: TelegramRetryAfter (сон на retry_after), TelegramServerError/TelegramNetworkError (3 попытки), TelegramForbiddenError (пользователь не нажимал /start → логируем, не спамяем).
  • Нарезка текста по 4096 символов с экранированием HTML.
  • Пауза между отправками (SEND_INTERVAL) — Telegram может прислать до 100 удалённых id одним апдейтом, а лимит в личку ~1 msg/sec.
  • Идемпотентность: upsert_message при повторной доставке апдейта не «воскрешает» удалённое сообщение и не затирает local_path.
  • RetentionWorker: фоновая задача чистит БД и файлы старше N дней (настраивается MESSAGE_RETENTION_DAYS / MEDIA_RETENTION_DAYS).
  • Error-handler (handlers/errors.py) логирует любое необработанное исключение и пишет его в events_log — polling не падает.

8. Что доработать для продакшена (roadmap)

Задача Как
PostgreSQL заменить Database._connect() на asyncpg/SQLAlchemy; схема в schema.sql диалекто-независима (кроме strftime в DEFAULT)
Webhook + несколько воркеров BOT_MODE=webhook, nginx + HTTPS; RetentionWorker вынести в отдельный процесс/cron
Очередь на отправку алертов вместо asyncio.sleep — per-chat очередь (Redis/aiostream)
Группировка media_group messages.media_group_id уже сохраняется — склеивать в sendMediaGroup
Поиск LIKE → FTS5 / PostgreSQL FTS
Кэширование настроек Redis вместо запроса в БД на каждый апдейт
Авто-ответ от имени аккаунта bot.send_message(..., business_connection_id=...), если rights.can_reply
Пометка прочитанным readBusinessMessage (нужно право can_read_messages)

9. Юридическая заметка

Бот обрабатывает личные сообщения третьих лиц. Перед запуском:

  • добавьте политику конфиденциальности и явное согласие пользователя при /start;
  • храните данные в юрисдикции, соответствующей GDPR (право на удаление — /export + полная очистка по запросу);
  • ограничьте срок хранения (MESSAGE_RETENTION_DAYS) и шифруйте диск;
  • не перепродавайте и не передавайте архив третьим лицам.

10. Развёртывание 24/7

GitHub ≠ сервер. GitHub хранит код и может триггерить деплой (Actions), но сам процесс бота должен жить на машине, которая не выключается.

Вариант Цена Подходит
VPS (Timeweb, Aeza, RuVDS…) ~150–300 ₽/мес ✅ рекомендуется: polling, без домена
Домашний ПК / Raspberry Pi 0 ✅ то же самое, канал стабильный
Render.com Free (см. deploy/render/) 0, без карты ✅ демо: Docker + keep-alive пингом /healthz; диск эфемерный
Hugging Face Spaces PRO $9/мес ❌ с 2025 Docker Spaces только платные (free — Static)
PaaS (Fly.io, Railway) от 0–5 $ ⚠️ бесплатные тарифы усыпляют процесс
GitHub Actions как «хост» ❌ раннеры эфемерны, 6 ч лимит

Быстрый старт на VPS (5 команд)

scp -r dialog-spy-bot root@<HOST>:/opt/
ssh root@<HOST>
cd /opt/dialog-spy-bot
nano .env.production.example   # вписать BOT_TOKEN → сохранить как .env
sudo bash deploy/install_vps.sh

Скрипт создаст пользователя spybot, venv, systemd-юнит (deploy/dialog-spy-bot.service, Restart=always, харденинг) и поднимет бота. Лог: journalctl -u dialog-spy-bot -f.

Автодеплой через GitHub Actions

  1. Создайте приватый репозиторий, запушьте проект (.env в git не попадает — он в .gitignore).
  2. Settings → Secrets and variables → Actions: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY.
  3. Каждый git push в main → workflow .github/workflows/deploy.yml скопирует код на VPS и перезапустит systemd-сервис (git-авторизация на сервере не нужна).

Чек-лист безопасности

  • .env и data/ никогда не коммитятся (проверьте .gitignore);
  • токен бота живёт только на сервере; после передачи токена кому-либо — /revoke в @BotFather;
  • SSH-ключ деплоя — отдельный, не ваш основной;
  • резервная копия: sqlite3 data/spy.db ".backup spy-backup.db" + tar data/media.
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support