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.
This commit is contained in:
@@ -6,47 +6,42 @@
|
||||
|
||||
## 📌 Инфраструктура и Деплой
|
||||
|
||||
**Актуальный способ деплоя — Coolify**, автодеплой по пушу в `gitea/main`. Старый ручной способ (checkout в `/opt/redairsoft_poster/` + `docker-compose` руками через Proxmox, описанный ниже архивно) больше не используется — тот контейнер (`redairsoft-vk-poster`) был удалён 2026-08-15 как заброшенный.
|
||||
|
||||
| Параметр | Значение |
|
||||
|---|---|
|
||||
| **Хост Proxmox** | `192.168.1.222` (root SSH через ключ `~/.ssh/id_ed25519_proxmox`) |
|
||||
| **LXC Контейнер** | **CT 107** (`redairsoft-poster`, IP: `192.168.1.106`, Debian 12) |
|
||||
| **Ресурсы LXC** | 2 vCPU, 1.5 GB RAM, 512 MB Swap, 16 GB Disk, `onboot=1` |
|
||||
| **Деплой** | 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` |
|
||||
| **Gitea SSH Remote** | `ssh://git@192.168.1.135:2222/exostring/redairsoft_vk_gt_max_sender.git` |
|
||||
| **Путь проекта на LXC 107** | `/opt/redairsoft_poster/` |
|
||||
| **Имя Docker контейнера** | `redairsoft-vk-poster` |
|
||||
| **Хост Proxmox** (для ручной диагностики) | `192.168.1.222` (root SSH через ключ `~/.ssh/id_ed25519_proxmox`) |
|
||||
| **Локальный Telegram Bot API** | Встроен в контейнер (`http://127.0.0.1:8081`), поддерживает загрузку видео до 2 ГБ |
|
||||
| **База данных** | SQLite `/opt/redairsoft_poster/data/poster.db` (персистентный том Docker `./data`) |
|
||||
| **База данных** | SQLite `/app/data/poster.db` внутри контейнера (персистентный том) |
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Управление проектом (Команды из консоли / PowerShell)
|
||||
|
||||
Все команды выполняются через Proxmox хост:
|
||||
### Диагностика через SSH (read-only)
|
||||
|
||||
```bash
|
||||
# 1. Посмотреть статус и логи бота:
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- docker logs --tail 50 redairsoft-vk-poster'
|
||||
# Найти актуальный контейнер (смотреть на тег образа = свежий commit hash):
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- docker ps -a'
|
||||
|
||||
# 2. Логи в реальном времени (follow):
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- docker logs -f redairsoft-vk-poster'
|
||||
|
||||
# 3. Запустить проект:
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- bash -c "cd /opt/redairsoft_poster && docker-compose up -d"'
|
||||
|
||||
# 4. Остановить проект:
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- bash -c "cd /opt/redairsoft_poster && docker-compose stop"'
|
||||
|
||||
# 5. Обновить из Gitea и пересобрать:
|
||||
ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- bash -c "cd /opt/redairsoft_poster && git pull origin main && docker-compose build && docker-compose up -d"'
|
||||
# Логи актуального контейнера:
|
||||
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 минут делает запрос к методу `wall.get` для группы `public36860851` (`owner_id: -36860851`).
|
||||
- Каждые 15 минут (`CHECK_INTERVAL_MINUTES`) делает запрос к методу `wall.get` для каждой группы из `routes.json`.
|
||||
- Фильтрует посты автора (без репостов и рекламы сторонних сообществ).
|
||||
2. **Защита от спама и режим запуска (`BOOTSTRAP_MODE`)**:
|
||||
- Установлен режим `BOOTSTRAP_MODE=skip_existing`.
|
||||
@@ -70,19 +65,72 @@ ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- bash -c "cd
|
||||
|
||||
---
|
||||
|
||||
## 🔀 Маршруты донор→рецепиент (`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...`) |
|
||||
| `VK_SOURCE` | `public36860851` |
|
||||
| `TG_BOT_TOKEN` | Токен Telegram бота (от `@BotFather`) |
|
||||
| `TG_CHAT_ID` | ID канала Telegram (например `-1001303630155`) |
|
||||
| `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` | Токен бота в мессенджере МАКС |
|
||||
| `MAX_CHAT_ID` | ID чата в МАКС |
|
||||
| `MAX_BOT_TOKEN` | Токен бота в мессенджере МАКС, общий на все маршруты |
|
||||
| `BOOTSTRAP_MODE` | `skip_existing` |
|
||||
| `CHECK_INTERVAL_MINUTES` | `15` |
|
||||
| `VIDEO_MAX_DURATION_SEC` | `7200` (2 часа) |
|
||||
@@ -95,18 +143,20 @@ ssh -i ~/.ssh/id_ed25519_proxmox root@192.168.1.222 'pct exec 107 -- bash -c "cd
|
||||
```
|
||||
├── src/
|
||||
│ ├── config.py # Настройки Pydantic Settings
|
||||
│ ├── database.py # Хранилище SQLite (посты, статусы, даты)
|
||||
│ ├── 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 # Точка входа и главный цикл опроса
|
||||
│ ├── 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 # Переменные окружения
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user