All checks were successful
CI/CD Pipeline / build-and-deploy (push) Successful in 2m36s
Возрастные YouTube-видео не скачивались: с cookies yt-dlp отбрасывает клиент
android и остаётся web, которому нужно решить n-challenge, а в образе стоял
Node 20 при требуемом минимуме 22 ("JS runtimes: node-20.19.2 (unsupported)").
Ставим Deno — рекомендованный yt-dlp рантайм и один статический бинарник.
Instagram падал с "empty media response" одинаково с cookies и без них —
дело было не в сессии, а в устаревшем экстракторе: слой pip был закеширован
на yt-dlp 2026.06.09. Поднимаем нижнюю границу до 2026.7.4.
/cookies/check помечал проблемой с cookies ЛЮБОЙ сбой, из-за чего на поломку
JS-рантайма прилетел алерт про протухшие cookies и увёл разбор не туда.
Теперь ответ содержит status: ok | cookies_invalid | extraction_failed |
no_cookies, и админ-бот шлёт разные сообщения. Разбор ответа в bot.py
сохраняет совместимость со старым форматом без поля status.
Добавлен smoke_test.py — гоняет реальные ссылки (включая обе регрессии выше)
через запущенные сервисы и печатает таблицу. Запускать после каждой правки.
Схема получения cookies переведена с крона на разовый ручной экспорт:
cookies-cron/ -> cookies/, удалены скрипты с анти-паттерном
`--cookies-from-browser BROWSER --cookies FILE`, который wiki yt-dlp прямо
запрещает и который сам ломал YouTube-сессию ротацией. Ключевые cookies живут
около года, поэтому обновление по расписанию не нужно — триггером служит алерт
health-check. deliver_cookies.sh только доставляет файлы по scp.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
254 lines
11 KiB
Markdown
254 lines
11 KiB
Markdown
# Архитектура системы
|
||
|
||
Документ описывает внутреннюю архитектуру и принципы работы Telegram Video Download Bot.
|
||
|
||
## Общая архитектура
|
||
|
||
Система состоит из микросервисной архитектуры с раздельными сервисами для каждого источника видео:
|
||
|
||
1. **Основной бот** (`bot.py`) — Telegram бот, обрабатывающий запросы пользователей
|
||
2. **YouTube Downloader Service** (`youtube-downloader/`) — порт 5557
|
||
3. **Instagram Downloader Service** (`instagram-downloader/`) — порт 5556
|
||
4. **VK Downloader Service** (`vk-downloader/`) — порт 5555
|
||
5. **Yapfiles Downloader Service** (`yapfiles-downloader/`) — порт 5558
|
||
6. **TikTok Downloader Service** (`tiktok-downloader/`) — порт 5559
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ Telegram User │
|
||
└────────┬────────┘
|
||
│
|
||
▼
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ Основной бот (bot.py) │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ Message Handler │ │
|
||
│ │ - URL extraction │ │
|
||
│ │ - Source detection (YouTube/Instagram/TikTok/VK/ │ │
|
||
│ │ Yapfiles) │ │
|
||
│ │ - Localization (ru/en) │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ HTTP API Clients (httpx) │ │
|
||
│ │ - YouTube, Instagram, TikTok, VK, Yapfiles │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
│ ┌────────────────────────────────────────────────────┐ │
|
||
│ │ SQLite Database (data/bot.db) │ │
|
||
│ │ - users (chat_id, username, locale, ...) │ │
|
||
│ │ - stats (total_downloads) │ │
|
||
│ └────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
│ HTTP POST /download/stream
|
||
▼
|
||
┌────────┬────────┬────────┬────────┬────────┐
|
||
│YouTube │Instagram│ TikTok │ VK │Yapfiles│
|
||
│ :5557 │ :5556 │ :5559 │ :5555 │ :5558 │
|
||
│ │ │ │ │ │
|
||
│yt-dlp │yt-dlp │yt-dlp │yt-dlp │requests│
|
||
│ │+cookies│ │ │+parsing│
|
||
└────────┴────────┴────────┴────────┴────────┘
|
||
```
|
||
|
||
## Компоненты системы
|
||
|
||
### 1. Основной бот (bot.py)
|
||
|
||
**Технологии:**
|
||
- `python-telegram-bot` — асинхронный фреймворк для Telegram Bot API
|
||
- `httpx` — асинхронный HTTP клиент
|
||
- `sqlite3` — база данных
|
||
|
||
**Ключевые функции:**
|
||
|
||
#### Локализация
|
||
```python
|
||
TEXTS = {
|
||
'ru': { 'start': '...', 'support': '...', ... },
|
||
'en': { 'start': '...', 'support': '...', ... }
|
||
}
|
||
|
||
def get_locale_from_language_code(language_code: str) -> str:
|
||
# 'ru*' → 'ru', иначе → 'en'
|
||
```
|
||
|
||
#### Определение источника
|
||
```python
|
||
def detect_video_source(url: str) -> str:
|
||
# youtube.com, youtu.be → 'youtube'
|
||
# instagram.com → 'instagram'
|
||
# tiktok.com → 'tiktok'
|
||
# vk.com, vkontakte.ru → 'vk'
|
||
# yapfiles.ru → 'yapfiles'
|
||
# иначе → 'unknown'
|
||
```
|
||
|
||
#### Команды
|
||
- `/start` — приветствие с локализацией
|
||
- `/stat` — статистика бота
|
||
- `/support` — информация о боте и контакт автора
|
||
|
||
### 2. Сервисы загрузчиков
|
||
|
||
Все сервисы имеют единый API:
|
||
|
||
| Endpoint | Метод | Описание |
|
||
|----------|-------|----------|
|
||
| `/health` | GET | Проверка работоспособности |
|
||
| `/download/stream` | POST | Скачивание видео |
|
||
|
||
**Request:**
|
||
```json
|
||
{"url": "https://..."}
|
||
```
|
||
|
||
**Response:** Бинарные данные видео или JSON с ошибкой.
|
||
|
||
#### YouTube Downloader (порт 5557)
|
||
- Использует `yt-dlp` с настройками для YouTube
|
||
- Поддержка shorts, плейлистов
|
||
|
||
#### Instagram Downloader (порт 5556)
|
||
- Использует `yt-dlp` с cookies
|
||
- Требует `instagram_cookies.txt`
|
||
|
||
#### TikTok Downloader (порт 5559)
|
||
- Использует `yt-dlp`
|
||
- Поддержка коротких и полных ссылок
|
||
|
||
#### VK Downloader (порт 5555)
|
||
- Использует `yt-dlp` с русскими заголовками
|
||
- Не требует VPN в РФ
|
||
|
||
#### Yapfiles Downloader (порт 5558)
|
||
- Парсит HTML страницу
|
||
- Извлекает прямую ссылку на видео
|
||
- Использует `requests` + `BeautifulSoup`
|
||
|
||
### 3. База данных
|
||
|
||
**Схема:**
|
||
|
||
```sql
|
||
CREATE TABLE users (
|
||
chat_id INTEGER PRIMARY KEY,
|
||
username TEXT,
|
||
first_name TEXT,
|
||
first_seen TEXT NOT NULL,
|
||
last_seen TEXT NOT NULL,
|
||
locale TEXT DEFAULT 'en' -- ru/en
|
||
);
|
||
|
||
CREATE TABLE stats (
|
||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||
total_downloads INTEGER DEFAULT 0
|
||
);
|
||
```
|
||
|
||
**Миграция:** При запуске бот автоматически добавляет колонку `locale` если её нет (для совместимости со старой базой).
|
||
|
||
### 4. Скрипт рассылки (broadcast.py)
|
||
|
||
Консольная утилита для отправки сообщений всем пользователям:
|
||
|
||
```bash
|
||
./broadcast.py -y "Текст сообщения"
|
||
./broadcast.py -y --html "<b>Жирный</b> текст"
|
||
./broadcast.py -y --file message.txt
|
||
./broadcast.py --list # показать пользователей
|
||
```
|
||
|
||
Автоматически читает `.env` для получения токена бота.
|
||
|
||
## Docker архитектура
|
||
|
||
### Порты
|
||
|
||
| Сервис | Внешний порт | Внутренний порт |
|
||
|--------|--------------|-----------------|
|
||
| VK | 5555 | 5000 |
|
||
| Instagram | 5556 | 5000 |
|
||
| YouTube | 5557 | 5000 |
|
||
| Yapfiles | 5558 | 5000 |
|
||
| TikTok | 5559 | 5000 |
|
||
|
||
### Volumes
|
||
|
||
**Основной бот:**
|
||
- `./video:/app/video` — скачанные видео
|
||
- `./data:/app/data` — база данных
|
||
|
||
**Сервисы загрузчиков:**
|
||
- `./downloads:/app/downloads` — временные файлы
|
||
- (YouTube) `./youtube_cookies.txt:/app/youtube_cookies.txt`
|
||
- (Instagram) `./instagram_cookies.txt:/app/instagram_cookies.txt`
|
||
|
||
Cookies монтируются с хоста (не read-only — файл обновляется вручную по `scp` с машины, где есть браузер), а не запекаются в образ — это позволяет обновлять их без пересборки/передеплоя сервиса. Процедура — в `cookies/README.md`.
|
||
|
||
## Потоки данных
|
||
|
||
```
|
||
User → Telegram → Bot
|
||
↓
|
||
detect_video_source(url)
|
||
↓
|
||
download_{source}_video()
|
||
↓
|
||
HTTP POST → Service → yt-dlp/parser
|
||
↓
|
||
binary video data
|
||
↓
|
||
save to video/ → send to User
|
||
↓
|
||
increment_downloads()
|
||
```
|
||
|
||
## Развертывание
|
||
|
||
### Текущая схема: единый docker-compose + CI/CD
|
||
|
||
Все 7 сервисов собираются и запускаются одним `docker-compose.yml` из корня репозитория (build-контексты на подпапки, единый `docker compose up -d --build`). Прод деплоится автоматически Forgejo Actions (`.forgejo/workflows/deploy.yml`) при пуше в `main`:
|
||
|
||
```
|
||
push в main → сборка всех 7 образов → push в Gitea registry
|
||
→ docker save | ssh | docker load на прод-хост
|
||
(registry недоступен напрямую с внешнего VPS)
|
||
→ docker compose up -d --remove-orphans на проде
|
||
```
|
||
|
||
На проде используется `docker-compose.prod.yml` (только `image:`, без `build:`).
|
||
|
||
### Гипотетический вариант: раздельное развёртывание
|
||
|
||
**Хост 1 (с VPN для YouTube/Instagram/TikTok):**
|
||
- Основной бот
|
||
- YouTube, Instagram, TikTok сервисы
|
||
|
||
**Хост 2 (без VPN):**
|
||
- VK, Yapfiles сервисы
|
||
|
||
В `.env` на хосте 1:
|
||
```env
|
||
VK_DOWNLOADER_URL=http://<host2_ip>:5555
|
||
YAPFILES_DOWNLOADER_URL=http://<host2_ip>:5558
|
||
```
|
||
|
||
## Безопасность
|
||
|
||
- Токен бота в `.env` (не коммитится)
|
||
- Cookies YouTube/Instagram закоммичены в репозиторий как fallback внутри образа; актуальные версии кладутся на хост вручную и монтируются поверх (см. `README.md#cookies-на-проде` и `cookies/README.md`)
|
||
- Сервисы доступны только по указанным URL
|
||
- Можно добавить API key между сервисами
|
||
|
||
## Мониторинг
|
||
|
||
- Все сервисы логируют в stdout
|
||
- Health check: `GET /health` на каждом сервисе
|
||
- Логи Docker: `docker compose logs -f`
|
||
|
||
## Будущие улучшения
|
||
|
||
1. **Аутентификация между сервисами** — API key/JWT
|
||
2. **Очередь задач** — Celery для фоновой обработки
|
||
3. **Prometheus метрики** — мониторинг производительности
|
||
4. **Кеширование** — Redis для частых запросов
|
||
5. **Поддержка новых источников** — Twitter, Facebook, etc.
|