exostring f8842f8c07 Fix black-screen-with-audio videos: faststart remux after download
Root cause of a real report (post #343474, video-36860851_456244054):
_select_format's video-only+bestaudio path merges VK's separate DASH video
and audio streams via ffmpeg, which leaves the moov atom at the end of the
file by default (confirmed: mdat at offset 44, moov near EOF). Streaming
players - Telegram's mobile clients in particular - can play the audio
track immediately but can't render video without seeking to the index
first, showing black video with working audio until the whole file has
downloaded.

Adds a fast, lossless -c copy -movflags +faststart remux after every video
download (cheap - just repositions the atom, no re-encode) so the moov atom
is always at the front regardless of which yt-dlp code path produced the
file. Falls back to the original file if the remux fails for any reason,
rather than dropping the video. Verified on the actual failing video:
moov now at offset 36 instead of past 2MB in.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 18:05:52 +05:00

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)

# Найти актуальный контейнер (смотреть на тег образа = свежий 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 подхватывает автоматически.


⚙️ Как работает сервис

  1. Донор→рецепиент маршруты (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 требует перезапуска контейнера, чтобы изменения подхватились.
    • Маршруты в рамках одного цикла опроса обрабатываются строго последовательно (без параллелизма) — это защищает от флуда на VK API, загрузке медиа в Telegram/локальный Bot API и отправке в МАКС, поскольку токены ботов общие на все маршруты.
  2. Опрос стены ВКонтакте:
    • Каждые 15 минут (CHECK_INTERVAL_MINUTES) делает запрос к методу wall.get для каждой группы из routes.json.
    • Фильтрует посты автора (без репостов и рекламы сторонних сообществ).
  3. Защита от спама и режим запуска (BOOTSTRAP_MODE):
    • Установлен режим BOOTSTRAP_MODE=skip_existing.
    • При первом запуске или перезапуске сервис запоминает текущие посты стены и ничего не публикует, пока в ВК не выйдет новый пост.
  4. Загрузка медиа и поддержка длинных видео:
    • Картинки скачиваются в максимальном разрешении.
    • Видео загружается через yt-dlp в качестве до 720p (длительностью до 2 часов и размером до 2000 МБ).
    • Встроенный в контейнер локальный telegram-bot-api загружает тяжелые видеофайлы напрямую в Telegram без ограничений облачного API (20-50 МБ).
  5. Форматирование текста:
    • Ссылки ВКонтакте вида [club123|Название] и [id123|Имя] преобразуются в корректные кликабельные HTML-ссылки.
    • Первый абзац / заголовок выделяется жирным шрифтом <b>...</b>.
    • Удаляются разделительные полосы (━━━, ───) и лишние переносы строк.
  6. Публикация в мессенджер МАКС:
    • Загрузка медиа в МАКС через /uploads?type=photo и /uploads?type=video.
    • Поллинг готовности видео (/videos/{token}) перед публикацией сообщения.
  7. Отчёты администраторам:
    • После успешной публикации или при ошибке бот отправляет отчёт со ссылками на оригинальный пост в ВК, Telegram и МАКС в личные сообщения администраторам (TG_ADMIN_IDS).
  8. Очистка диска и кэша:
    • Временные файлы удаляются сразу после публикации.
    • Фоновый воркер cleaner.py каждые 15 минут удаляет старые файлы из /tmp/poster_cache, гарантируя защиту активных скачиваний через реестр локов.

🔀 Маршруты донор→рецепиент (data/routes.json)

Формат одного маршрута:

{
  "id": "redairsoft_main",
  "name": "Red Airsoft (основная группа)",
  "vk_source": "public36860851",
  "tg_chat_id": "-1001303630155",
  "tg_enabled": true,
  "max_chat_id": "123456",
  "max_enabled": true,
  "stale_alert_enabled": true,
  "stale_alert_ids": [123456789]
}
Поле Описание
id Уникальный технический идентификатор маршрута (используется в БД и логах)
name Человекочитаемое имя для отчётов администраторам
vk_source Группа-донор ВК: screen name, URL или owner_id
tg_chat_id / max_chat_id Куда публиковать (chat/channel ID)
tg_enabled / max_enabled Переключатель — публиковать ли в эту платформу для этого маршрута (только TG, только МАКС, или оба)
stale_alert_enabled Оповещать ли, если донор давно не постил (см. ниже). По умолчанию false
stale_alert_ids Кому слать оповещение о простое (список TG ID). Пусто/не указано → шлётся TG_ADMIN_IDS

Можно завести несколько маршрутов с разными vk_source, каждый — в свои TG/МАКС чаты. Токены ботов (TG_BOT_TOKEN, MAX_BOT_TOKEN, VK_ACCESS_TOKEN) общие на все маршруты — один бот пишет в разные чаты.

Оповещение о "молчащем" доноре

Глобальный порог — STALE_DONOR_ALERT_DAYS в .env (0 = функция выключена целиком). Если у конкретного маршрута stale_alert_enabled: true и донор не постил дольше порога — уходит одно сообщение получателям из stale_alert_ids (или TG_ADMIN_IDS, если список пуст). Не спамит: пока донор молчит, повторно не напоминает; как только появляется новый пост — оповещение автоматически "перевзводится" для следующего простоя. Если бот не может достучаться до кого-то из stale_alert_ids (человек ни разу не писал боту) — отдельным сообщением получают TG_ADMIN_IDS.

Узнать свой Telegram ID

Написать боту /id в личку — ответит вашим ID в копируемом виде (<code>...</code>), чтобы вставить в TG_ADMIN_IDS или stale_alert_ids.

vk_source принимает screen name, полный URL (vk.com и vk.ru) или owner_id — можно указывать как есть, без ручной нормализации.

Файл — обычный JSON, но допускает построчные комментарии // текст, чтобы подписывать, где какой маршрут (полноценных JSON-комментариев не существует, здесь это добавлено отдельно — строка, у которой после пробелов идёт //, вырезается перед парсингом):

[

// 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                    # Переменные окружения
S
Description
No description provided
Readme 382 KiB
Languages
Python 97.7%
Dockerfile 1.4%
Shell 0.9%