20135263c4
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.
163 lines
12 KiB
Markdown
163 lines
12 KiB
Markdown
# 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 # Переменные окружения
|
||
```
|