Files
redairsoft_vk_gt_max_sender/README.md
T
exostring 20135263c4 Add multi-route donor->recipient support, fix VK hashtag truncation
Posts can now be sourced from multiple VK groups, each routed to its own
Telegram/MAX destination(s) with independent on/off switches, configured
via data/routes.json (supports // line comments). Falls back to a single
route auto-generated from the legacy VK_SOURCE/TG_CHAT_ID/MAX_CHAT_ID env
vars if routes.json doesn't exist yet, so existing deployments keep working.

Routes are processed strictly sequentially within a cycle (no concurrency)
to keep flood control on VK/TG/MAX correct, since bot tokens are shared
across routes. DB schema gains route_id in the posts uniqueness key so the
same VK donor can safely feed multiple routes without status collisions.

Also removes the trailing-hashtag-stripping logic in text_formatter, which
was silently deleting VK posts' own hashtags whenever COMMON_TAGS wasn't
configured (it always wasn't) - posts are now forwarded unchanged.
2026-08-18 14:48:22 +05:00

163 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# RedAirsoft VK to Telegram & MAX Messenger Poster
Автоматизированный Docker-сервис для периодического парсинга новых постов из сообщества ВКонтакте (**Red Airsoft | Страйкбол**) и их одновременной публикации в **Telegram** и мессенджер **МАКС** (MAX Messenger) с отчётами администраторам.
---
## 📌 Инфраструктура и Деплой
**Актуальный способ деплоя — Coolify**, автодеплой по пушу в `gitea/main`. Старый ручной способ (checkout в `/opt/redairsoft_poster/` + `docker-compose` руками через Proxmox, описанный ниже архивно) больше не используется — тот контейнер (`redairsoft-vk-poster`) был удалён 2026-08-15 как заброшенный.
| Параметр | Значение |
|---|---|
| **Деплой** | Coolify, приложение "RedAirsoft Poster", сервер `redairsoft-poster-lxc` |
| **URL** | `http://hjbmqj1ohievdrebpltnzex0.192.168.1.106.sslip.io` |
| **Хост контейнера** | Тот же LXC 107 (`192.168.1.106`) — Coolify управляет им напрямую, не через `/opt/redairsoft_poster/` |
| **Имя Docker-контейнера** | Динамическое, вида `<coolify-app-id>-<deploy-id>`, тег образа = хэш закоммиченного коммита. Смотреть через `docker ps` — актуальный это `Up`, с тегом = последний `git log` хэш |
| **Gitea Репозиторий** | `http://192.168.1.135:3000/exostring/redairsoft_vk_gt_max_sender` |
| **Хост Proxmox** (для ручной диагностики) | `192.168.1.222` (root SSH через ключ `~/.ssh/id_ed25519_proxmox`) |
| **Локальный Telegram Bot API** | Встроен в контейнер (`http://127.0.0.1:8081`), поддерживает загрузку видео до 2 ГБ |
| **База данных** | SQLite `/app/data/poster.db` внутри контейнера (персистентный том) |
### Диагностика через SSH (read-only)
```bash
# Найти актуальный контейнер (смотреть на тег образа = свежий commit hash):
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- docker ps -a'
# Логи актуального контейнера:
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- docker logs --tail 100 <имя-контейнера>'
```
Деплой новой версии — просто `git push` в `gitea/main`, Coolify подхватывает автоматически.
---
## ⚙️ Как работает сервис
0. **Донор→рецепиент маршруты (`data/routes.json`)**:
- Сервис может опрашивать сразу несколько групп ВК ("доноров"), и для каждой отдельно настроено, в какие Telegram и МАКС чаты публиковать ("рецепиенты"), плюс независимые вкл/выкл для TG и МАКС на каждом маршруте.
- Список маршрутов хранится в `data/routes.json` (персистентный том, не в git). Если файла нет — при первом запуске он создаётся автоматически из старых `.env`-переменных `VK_SOURCE`/`TG_CHAT_ID`/`MAX_CHAT_ID` (один маршрут с `id: "default"`), так что апгрейд с однo-группового режима ничего не ломает.
- Пример формата — [`routes.json.example`](routes.json.example) в корне репозитория. Правка `routes.json` требует перезапуска контейнера, чтобы изменения подхватились.
- Маршруты в рамках одного цикла опроса обрабатываются **строго последовательно** (без параллелизма) — это защищает от флуда на VK API, загрузке медиа в Telegram/локальный Bot API и отправке в МАКС, поскольку токены ботов общие на все маршруты.
1. **Опрос стены ВКонтакте**:
- Каждые 15 минут (`CHECK_INTERVAL_MINUTES`) делает запрос к методу `wall.get` для каждой группы из `routes.json`.
- Фильтрует посты автора (без репостов и рекламы сторонних сообществ).
2. **Защита от спама и режим запуска (`BOOTSTRAP_MODE`)**:
- Установлен режим `BOOTSTRAP_MODE=skip_existing`.
- При первом запуске или перезапуске сервис запоминает текущие посты стены и **ничего не публикует**, пока в ВК не выйдет новый пост.
3. **Загрузка медиа и поддержка длинных видео**:
- Картинки скачиваются в максимальном разрешении.
- Видео загружается через `yt-dlp` в качестве до 720p (длительностью до 2 часов и размером до 2000 МБ).
- Встроенный в контейнер локальный `telegram-bot-api` загружает тяжелые видеофайлы напрямую в Telegram без ограничений облачного API (20-50 МБ).
4. **Форматирование текста**:
- Ссылки ВКонтакте вида `[club123|Название]` и `[id123|Имя]` преобразуются в корректные кликабельные HTML-ссылки.
- Первый абзац / заголовок выделяется жирным шрифтом `<b>...</b>`.
- Удаляются разделительные полосы (`━━━`, `───`) и лишние переносы строк.
5. **Публикация в мессенджер МАКС**:
- Загрузка медиа в МАКС через `/uploads?type=photo` и `/uploads?type=video`.
- Поллинг готовности видео (`/videos/{token}`) перед публикацией сообщения.
6. **Отчёты администраторам**:
- После успешной публикации или при ошибке бот отправляет отчёт со ссылками на оригинальный пост в ВК, Telegram и МАКС в личные сообщения администраторам (`TG_ADMIN_IDS`).
7. **Очистка диска и кэша**:
- Временные файлы удаляются сразу после публикации.
- Фоновый воркер `cleaner.py` каждые 15 минут удаляет старые файлы из `/tmp/poster_cache`, гарантируя защиту активных скачиваний через реестр локов.
---
## 🔀 Маршруты донор→рецепиент (`data/routes.json`)
Формат одного маршрута:
```json
{
"id": "redairsoft_main",
"name": "Red Airsoft (основная группа)",
"vk_source": "public36860851",
"tg_chat_id": "-1001303630155",
"tg_enabled": true,
"max_chat_id": "123456",
"max_enabled": true
}
```
| Поле | Описание |
|---|---|
| `id` | Уникальный технический идентификатор маршрута (используется в БД и логах) |
| `name` | Человекочитаемое имя для отчётов администраторам |
| `vk_source` | Группа-донор ВК: screen name, URL или owner_id |
| `tg_chat_id` / `max_chat_id` | Куда публиковать (chat/channel ID) |
| `tg_enabled` / `max_enabled` | Переключатель — публиковать ли в эту платформу для этого маршрута (только TG, только МАКС, или оба) |
Можно завести несколько маршрутов с разными `vk_source`, каждый — в свои TG/МАКС чаты. Токены ботов (`TG_BOT_TOKEN`, `MAX_BOT_TOKEN`, `VK_ACCESS_TOKEN`) общие на все маршруты — один бот пишет в разные чаты.
`vk_source` принимает screen name, полный URL (`vk.com` и `vk.ru`) или `owner_id` — можно указывать как есть, без ручной нормализации.
Файл — обычный JSON, но допускает построчные комментарии `// текст`, чтобы подписывать, где какой маршрут (полноценных JSON-комментариев не существует, здесь это добавлено отдельно — строка, у которой после пробелов идёт `//`, вырезается перед парсингом):
```jsonc
[
// redairsoft
{
"id": "redairsoft",
"vk_source": "public36860851",
"tg_chat_id": "-1001303630155",
"max_chat_id": "-69722432869632"
},
// strike_expo
{
"id": "strike_expo",
"vk_source": "https://vk.ru/strike_expo",
"tg_chat_id": "-1003099077190",
"max_chat_id": "-77898705339648"
}
]
```
---
## 🔑 Конфигурация (`.env`)
| Переменная | Описание |
|---|---|
| `VK_ACCESS_TOKEN` | Сервисный токен приложения ВК (`d0a64...`), общий на все маршруты |
| `ROUTES_CONFIG_PATH` | Путь к файлу маршрутов, по умолчанию `data/routes.json` |
| `VK_SOURCE` / `TG_CHAT_ID` / `MAX_CHAT_ID` | Легаси fallback: используются только для авто-генерации первого маршрута, если `routes.json` ещё не существует |
| `TG_BOT_TOKEN` | Токен Telegram бота (от `@BotFather`), общий на все маршруты |
| `TG_MEDIA_CHANNEL_ID` | Скрытый канал-хранилище (опционально) |
| `TG_ADMIN_IDS` | Telegram ID админов через запятую (например `442509142`) |
| `LOCAL_BOT_API_URL` | `http://127.0.0.1:8081` (встроен в контейнер) |
| `MAX_BOT_TOKEN` | Токен бота в мессенджере МАКС, общий на все маршруты |
| `BOOTSTRAP_MODE` | `skip_existing` |
| `CHECK_INTERVAL_MINUTES` | `15` |
| `VIDEO_MAX_DURATION_SEC` | `7200` (2 часа) |
| `VIDEO_MAX_SIZE_MB_LOCAL` | `2000` (2 ГБ) |
---
## 📁 Структура исходного кода
```
├── src/
│ ├── config.py # Настройки Pydantic Settings
│ ├── routes.py # Загрузка/валидация data/routes.json (донор->рецепиент маршруты)
│ ├── database.py # Хранилище SQLite (посты, статусы по route_id, даты)
│ ├── vk_client.py # Клиент VK API (wall.get, извлечение медиа)
│ ├── media_processor.py # Загрузка фото/видео (yt-dlp, aiohttp)
│ ├── text_formatter.py # Конвертер ссылок ВК, очистка текста, HTML
│ ├── tg_poster.py # Отправка в Telegram (Rich Message + Local API)
│ ├── max_poster.py # Отправка в MAX Messenger (Uploads + Polling)
│ ├── cleaner.py # Фоновая очистка временных файлов
│ ├── admin_notifier.py # Агрегированные отчёты администраторам по всем маршрутам
│ └── main.py # Точка входа, цикл опроса по всем маршрутам
├── Dockerfile # Multi-stage образ: aiogram/telegram-bot-api + python:3.12-slim + ffmpeg
├── docker-compose.yml # Конфигурация запуска сервиса
├── docker-entrypoint.sh # Автозапуск локального Telegram Bot API + постера
├── requirements.txt # Python зависимости (aiogram, yt-dlp, loguru, aiohttp, pydantic)
├── routes.json.example # Пример формата data/routes.json
└── .env # Переменные окружения
```