- FastAPI + PostgreSQL + Alembic + React/Vite skeleton, Docker Compose, healthcheck - Google OAuth (single allowed account), encrypted refresh token storage - Subscriptions sync with pagination, uploads playlist batch fetch - Categories CRUD, many-to-many channel assignment, category filtering - Video sync (playlistItems + videos.list batching), cached feed with cursor pagination, background scheduler (APScheduler) - Video detail page with YouTube embed player - SPA fallback routing, optimistic UI updates, client-side query caching 40 backend tests covering OAuth allow-list, sync idempotency, cascade deletes, cursor pagination, and category filtering. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
39 KiB
Техническое задание: персональный YouTube-клиент с категориями подписок и интеграцией MeTube
Статус: MVP specification
Дата: 2026-09-16
Режим: single-user
Целевая VM приложения: hermesVM
Media/download backend: существующий MeTube на mediaVM
1. Цель проекта
Разработать self-hosted веб-сервис, который работает как персональная лента YouTube-подписок, но добавляет отсутствующую в YouTube удобную категоризацию каналов.
Сервис должен:
- Авторизоваться в Google/YouTube от имени одного владельца.
- Получать реальные подписки YouTube-аккаунта.
- Позволять вручную распределять подписанные каналы по пользовательским категориям, например:
- Linux
- Шахматы
- Научпоп
- IT
- Игры
- Показывать ленту последних видео:
- со всех подписок;
- из выбранной категории;
- из каналов без категории.
- Позволять смотреть ролик:
- с YouTube, если локальной копии нет;
- напрямую с MeTube/mediaVM, если ролик был скачан.
- Позволять нажать
Скачать, после чего backend отправляет URL ролика в существующий MeTube. - Не хранить сами видео на
hermesVM. - Иметь API, который в дальнейшем можно использовать для PWA/мобильного приложения.
Главная идея:
YouTube = источник подписок, метаданных и online-видео
Наш сервис = каталогизация, лента, UI и бизнес-логика
MeTube = download engine + media server
2. Что НЕ входит в MVP
В MVP не требуется:
- воспроизводить персональные YouTube Recommendations/Home;
- копировать интерфейс youtube.com один в один;
- комментарии;
- лайки/дизлайки;
- управление подписками на YouTube;
- загрузка видео на YouTube;
- несколько пользователей;
- социальные функции;
- полноценное мобильное приложение;
- автоматическое скачивание всех новых роликов;
- функции Tube Archivist;
- перенос видеофайлов с
mediaVMнаhermesVM.
Позже архитектура не должна мешать добавлению PWA/мобильного приложения.
3. Проверенная инфраструктура
3.1. MeTube
MeTube уже работает на mediaVM.
Проверено:
MeTube host: mediaVM
MeTube IP: 192.168.8.177
MeTube port: 8081
MeTube API base URL: http://192.168.8.177:8081
Docker container:
name: metube
image: ghcr.io/alexta69/metube:latest
port mapping: 0.0.0.0:8081 -> 8081/tcp
Compose:
/home/vrubel/services/meTube/compose.yml
Media storage на mediaVM:
host:
/home/vrubel/hpstorage/meTube
container:
/downloads
3.2. Проверка MeTube API
С hermesVM успешно выполнен запрос:
POST http://192.168.8.177:8081/add
Content-Type: application/json
{}
Ответ:
HTTP/1.1 400 missing 'url', 'download_type', or 'quality'
Это подтверждает:
- endpoint
/addдоступен сhermesVM; - MeTube сейчас не требует отдельный API token;
- сетевой маршрут
hermesVM -> mediaVM:8081работает.
3.3. Проверка отдачи локального видео
Проверенный URL вида:
http://192.168.8.177:8081/download/<URL-encoded-filename>
вернул:
HTTP/1.1 200 OK
Content-Type: video/webm
Accept-Ranges: bytes
Следовательно, MeTube можно использовать как media server для HTML5 video playback без монтирования media storage на hermesVM.
4. Архитектура
Google / YouTube
│
OAuth2 + Data API
│
▼
┌──────────────────────────────────────────────────────┐
│ hermesVM │
│ │
│ Browser │
│ │ │
│ ▼ │
│ Web UI ◄──────────── REST API ───────────► Backend │
│ │ │
│ ▼ │
│ PostgreSQL │
│ │ │
│ │ │
└───────────────────────────────────────────────┼──────┘
│
POST /add │
▼
┌────────────────────────┐
│ mediaVM │
│ │
│ MeTube :8081 │
│ │ │
│ ├─ yt-dlp download │
│ │ │
│ └─ /download/... │
│ │
│ /home/vrubel/ │
│ hpstorage/meTube │
└────────────────────────┘
▲
│
local video playback
│
Browser
Ключевой принцип
Frontend не должен напрямую вызывать POST /add MeTube.
Правильный путь:
Browser
-> our backend on hermesVM
-> MeTube API on mediaVM
Это позволяет:
- не раскрывать write API MeTube клиенту;
- валидировать YouTube URL;
- хранить историю download jobs;
- централизованно обрабатывать ошибки;
- позже добавить авторизацию/ACL без изменения frontend.
После завершения скачивания сам видеофайл воспроизводится напрямую с MeTube/mediaVM.
5. Рекомендуемый стек
Backend
- Python 3.13+
- FastAPI
- SQLAlchemy 2.x
- Alembic
- Pydantic Settings
- httpx
- google-auth
- google-auth-oauthlib
- google-api-python-client либо прямые HTTP-вызовы к YouTube Data API
- APScheduler для простых фоновых sync jobs
- python-socketio client для получения событий MeTube
Frontend
- React
- TypeScript
- Vite
- React Router
- TanStack Query
- обычный responsive CSS либо Tailwind CSS
Не использовать Next.js без необходимости: серверный рендеринг для этого проекта не нужен.
Database
- PostgreSQL 16+
Redis для MVP не нужен.
Deployment
Docker Compose на hermesVM.
Production может состоять всего из:
app
postgres
Frontend можно собирать на этапе Docker build и раздавать из backend-контейнера как static assets.
6. Google OAuth и YouTube Data API
6.1. OAuth
Использовать OAuth 2.0 Web Application.
Минимальный scope:
https://www.googleapis.com/auth/youtube.readonly
При необходимости идентифицировать Google account дополнительно:
openid
email
profile
Приложение строго однопользовательское.
В .env должен быть параметр:
ALLOWED_GOOGLE_EMAIL=user@example.com
После OAuth backend обязан отклонить другой аккаунт.
Refresh token должен храниться зашифрованно.
В репозиторий нельзя коммитить:
- Google Client Secret;
- refresh token;
- session secret;
- encryption key.
7. Получение подписок
Для получения подписок авторизованного пользователя использовать:
GET https://www.googleapis.com/youtube/v3/subscriptions
Параметры:
part=snippet,contentDetails
mine=true
maxResults=50
Обязательно обрабатывать nextPageToken.
Для каждого subscription сохранить минимум:
youtube_channel_id
title
description
thumbnail_url
subscribed=true
После полного sync канал, который был в БД, но исчез из актуального списка подписок, не удалять физически, а установить:
subscribed=false
Это сохранит пользовательские категории и историю.
8. Получение uploads playlist каналов
После sync подписок получить channel metadata через:
GET https://www.googleapis.com/youtube/v3/channels
Использовать:
part=snippet,contentDetails
id=<channel ids>
Channel IDs следует отправлять batch-запросами, а не по одному.
Из:
contentDetails.relatedPlaylists.uploads
сохранить:
uploads_playlist_id
для каждого канала.
9. Синхронизация новых видео
Для каждого активного подписанного канала читать его uploads playlist через:
GET https://www.googleapis.com/youtube/v3/playlistItems
Параметры:
part=snippet,contentDetails
playlistId=<uploads_playlist_id>
maxResults=10
Затем новые video_id группировать и получать детали через:
GET https://www.googleapis.com/youtube/v3/videos
Например:
part=snippet,contentDetails,status
id=<up to batch limit>
Хранить локальный cache метаданных.
Default sync intervals
SUBSCRIPTIONS_SYNC_INTERVAL_HOURS=6
VIDEOS_SYNC_INTERVAL_MINUTES=60
VIDEOS_PER_CHANNEL_SYNC=10
Также нужна кнопка:
Обновить сейчас
Нельзя при каждом открытии frontend делать запросы YouTube отдельно для каждого канала.
UI должен читать прежде всего нашу локальную БД.
10. YouTube API quota
YouTube Data API имеет quota.
Default quota проекта обычно составляет 10 000 units/day, но это значение следует считать конфигурируемым/проверяемым в Google Cloud Console.
Нужно:
- минимизировать повторные вызовы;
- использовать локальный cache;
- batch-ить
channels.listиvideos.list; - не запускать новый полный sync одновременно с уже идущим;
- логировать quota-related errors;
- показывать UI сообщение при quota exhaustion;
- не превращать пользовательский refresh страницы в полный YouTube sync.
11. Модель данных
app_settings
id
key
value
updated_at
oauth_credentials
Single row.
id
google_email
encrypted_refresh_token
access_token_expires_at
created_at
updated_at
Access token можно не сохранять постоянно, если библиотека умеет обновлять его через refresh token.
channels
id UUID/BigInt
youtube_channel_id UNIQUE NOT NULL
title
description
thumbnail_url
uploads_playlist_id
subscribed BOOLEAN
last_synced_at
created_at
updated_at
categories
id
name UNIQUE
slug UNIQUE
sort_order
created_at
updated_at
channel_categories
Many-to-many:
channel_id
category_id
PRIMARY KEY(channel_id, category_id)
Один канал может входить в несколько категорий.
videos
id
youtube_video_id UNIQUE NOT NULL
channel_id
title
description
thumbnail_url
published_at
duration_seconds NULL
youtube_url
created_at
updated_at
download_jobs
id
video_id
status
metube_job_id NULL
metube_filename NULL
media_url NULL
progress_percent NULL
error_message NULL
requested_at
started_at NULL
completed_at NULL
updated_at
status enum:
queued
downloading
postprocessing
completed
failed
unknown
12. Категории
UI обязан иметь виртуальные категории:
Все
Без категории
Они не обязаны храниться в таблице categories.
Пользовательские категории:
- создать;
- переименовать;
- удалить;
- изменить порядок;
- назначить каналу;
- убрать канал из категории.
Удаление категории не должно удалять канал.
Канал может одновременно находиться, например, в:
Linux
IT
Self-hosted
13. Основная лента
Главная страница:
[Все] [Linux] [Шахматы] [Научпоп] ...
или sidebar на desktop.
После выбора категории показывать последние ролики всех каналов этой категории.
Сортировка:
published_at DESC
Карточка ролика должна содержать минимум:
- thumbnail;
- title;
- channel title;
- publish date/time;
- duration, если известна;
- local/download status;
- кнопку
Смотреть; - кнопку
Скачать, если локальной копии нет.
Желательные фильтры после базового MVP:
- только новые;
- только локальные;
- только нескачанные;
- скрыть просмотренные;
- Shorts.
Эти фильтры не являются blocker для первой версии.
14. Просмотр видео
14.1. Если локальной копии нет
Использовать YouTube player/embed.
Например:
https://www.youtube.com/embed/<VIDEO_ID>
или privacy-enhanced:
https://www.youtube-nocookie.com/embed/<VIDEO_ID>
14.2. Если локальная копия есть
Использовать обычный HTML5:
<video controls src="<media_url>"></video>
media_url должен указывать напрямую на MeTube/mediaVM.
Проверено, что MeTube отвечает:
Accept-Ranges: bytes
поэтому browser seeking должен работать.
14.3. Fallback
Если media_url сохранён в БД, но MeTube отвечает 404/5xx, UI не должен ломаться.
Нужно:
- пометить local state как stale/unknown;
- предложить YouTube playback;
- дать возможность скачать заново.
15. Интеграция с MeTube
15.1. Конфигурация
Backend config:
METUBE_API_BASE_URL=http://192.168.8.177:8081
METUBE_PUBLIC_BASE_URL=http://192.168.8.177:8081
METUBE_CONTAINER_DOWNLOAD_DIR=/downloads
Разделять:
METUBE_API_BASE_URL
и:
METUBE_PUBLIC_BASE_URL
обязательно.
Причина: позже internal API может остаться HTTP/IP, а media playback перейти на HTTPS domain через Nginx Proxy Manager.
Важное замечание по HTTPS
Если наше приложение позже будет открываться через:
https://...
браузер не должен получать video URL вида:
http://192.168.8.177:8081/...
из-за mixed-content policy.
Тогда METUBE_PUBLIC_BASE_URL должен стать HTTPS URL, например через reverse proxy.
Внутренний METUBE_API_BASE_URL при этом может остаться:
http://192.168.8.177:8081
16. Добавление загрузки в MeTube
Использовать:
POST /add
Полный URL:
http://192.168.8.177:8081/add
Пример payload для MVP:
{
"url": "https://www.youtube.com/watch?v=VIDEO_ID",
"download_type": "video",
"codec": "auto",
"format": "mp4",
"quality": "best",
"auto_start": true,
"custom_name_prefix": "VIDEO_ID"
}
url, download_type и quality обязательны в текущем API MeTube.
format=mp4 выбран для более предсказуемой browser compatibility.
custom_name_prefix рекомендуется устанавливать равным YouTube Video ID. Это значительно упрощает correlation между нашим video и событиями MeTube и не требует менять глобальный OUTPUT_TEMPLATE существующего MeTube.
Backend должен:
- проверить, что
youtube_video_idсуществует в нашей БД; - построить canonical URL:
https://www.youtube.com/watch?v=<VIDEO_ID>
- не принимать произвольный URL из frontend;
- отправить POST в MeTube;
- сохранить возвращённый MeTube identifier/status, если он присутствует;
- создать/обновить
download_jobs.
Повторное нажатие Скачать не должно создавать несколько одинаковых активных jobs.
17. Получение статуса MeTube download
Текущий MeTube использует Socket.IO и публикует события, включая:
added
updated
completed
canceled
cleared
Наш backend должен держать server-to-server Socket.IO connection к MeTube.
Никакой Socket.IO MeTube напрямую в browser нашего приложения не нужен.
При событиях:
added/updated
обновлять:
status
progress_percent
При:
completed
сохранить точное поле filename, пришедшее от MeTube.
Это важно: нельзя пытаться восстановить имя файла по YouTube title.
18. Построение media URL
При completed MeTube сообщает финальное имя/путь файла.
Если MeTube вернул:
/downloads/some file.mp4
backend должен:
- безопасно проверить, что путь находится под:
METUBE_CONTAINER_DOWNLOAD_DIR=/downloads
- получить relative path:
some file.mp4
- URL-encode каждый path segment;
- сформировать:
<METUBE_PUBLIC_BASE_URL>/download/<encoded-relative-path>
Пример:
http://192.168.8.177:8081/download/some%20file.mp4
- выполнить
HEADперед переводом job в окончательныйcompleted, если это не создаёт проблем с конкретной версией MeTube; - сохранить media URL в БД.
Нельзя строить путь на основе title из YouTube API: yt-dlp/MeTube могут санитизировать, сокращать или менять filename.
19. Recovery при рестарте приложения
Backend не должен считать download завершённым только потому, что когда-то отправил /add.
После рестарта:
completedjobs с рабочимmedia_urlостаются completed;- media URL можно выборочно проверять HEAD-запросом;
- незавершённые
queued/downloading/postprocessingjobs переводятся вunknown, если их состояние невозможно достоверно восстановить; - если текущая версия MeTube позволяет получить persistent queue/completed state через Socket.IO/API, использовать это для reconciliation;
- если нет — UI должен показывать
Состояние неизвестнои позволять повторную проверку/загрузку.
Не вносить изменения в исходники MeTube ради этого MVP.
20. Наш backend REST API
Минимальный API:
Health
GET /api/health
Ответ:
{
"status": "ok",
"database": "ok",
"metube": "ok"
}
Google API не обязательно проверять на каждом healthcheck.
Auth
GET /api/auth/status
GET /api/auth/google/start
GET /api/auth/google/callback
POST /api/auth/logout
Sync
POST /api/sync/subscriptions
POST /api/sync/videos
GET /api/sync/status
Запретить параллельные одинаковые sync jobs.
Categories
GET /api/categories
POST /api/categories
PATCH /api/categories/{id}
DELETE /api/categories/{id}
POST /api/categories/reorder
Channels
GET /api/channels
GET /api/channels/{id}
PUT /api/channels/{id}/categories
Опциональные query params:
subscribed=true
category_id=<id>
uncategorized=true
Feed
GET /api/feed
Query params:
category_id
limit
cursor
local_only
downloaded
Для Все category_id не передавать.
Videos
GET /api/videos/{youtube_video_id}
POST /api/videos/{youtube_video_id}/download
GET /api/videos/{youtube_video_id}/download-status
POST /api/videos/{youtube_video_id}/recheck-local
21. Pagination
Ленту не возвращать целиком.
Использовать cursor pagination.
Сортировка:
published_at DESC, id DESC
Default:
limit=30
Maximum:
limit=100
22. Frontend screens
22.1. First-run / Connect YouTube
Если OAuth ещё не выполнен:
Подключить YouTube
После успешного OAuth автоматически запустить initial subscriptions sync.
22.2. Feed
Основной экран.
Desktop:
sidebar categories + video grid/feed
Mobile:
top horizontal category selector / drawer
22.3. Channels
Страница всех подписок.
Для каждого канала:
- avatar;
- title;
- category chips;
- возможность добавить/убрать категории.
Нужен поиск по названию канала.
22.4. Category management
- create;
- rename;
- delete;
- reorder.
22.5. Video view
Показывать player.
Логика выбора source:
download_job.completed + media_url reachable
-> MeTube local player
else
-> YouTube player
23. Download UX
Карточка должна показывать состояния примерно так:
Скачать
↓
В очереди
↓
Скачивается 42%
↓
Обработка
↓
На сервере
При ошибке:
Ошибка загрузки
[Повторить]
После completed кнопка меняется на:
На сервере
Можно добавить отдельную ссылку:
Открыть файл
24. Безопасность
MeTube
Сейчас MeTube API доступен без token authentication.
Поэтому:
- не публиковать
mediaVM:8081напрямую в интернет без reverse proxy/auth; - наш browser не должен иметь возможность отправлять download POST напрямую;
- write calls к MeTube идут только с backend;
- по возможности firewall должен разрешать MeTube API только из доверенных LAN/VLAN адресов.
Наш сервис
Нужно:
- single-user access;
- secure session cookie;
HttpOnly;SameSite=Laxили строже;Secure=trueпри HTTPS;- OAuth state validation;
- CSRF protection для mutating browser requests или эквивалентная same-origin защита;
- allow-list Google email;
- secrets только через environment/secrets;
- никакого логирования OAuth tokens;
- URL validation;
- timeout на запросы к Google/MeTube;
- ограниченный размер request body;
- понятные error messages без stack trace пользователю.
25. .env.example
Пример:
APP_ENV=production
APP_BASE_URL=http://hermesVM:8080
APP_PORT=8080
APP_SECRET_KEY=CHANGE_ME
TOKEN_ENCRYPTION_KEY=CHANGE_ME
DATABASE_URL=postgresql+psycopg://youtube_app:CHANGE_ME@postgres:5432/youtube_app
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://hermesVM:8080/api/auth/google/callback
ALLOWED_GOOGLE_EMAIL=
METUBE_API_BASE_URL=http://192.168.8.177:8081
METUBE_PUBLIC_BASE_URL=http://192.168.8.177:8081
METUBE_CONTAINER_DOWNLOAD_DIR=/downloads
METUBE_REQUEST_TIMEOUT_SECONDS=30
SUBSCRIPTIONS_SYNC_INTERVAL_HOURS=6
VIDEOS_SYNC_INTERVAL_MINUTES=60
VIDEOS_PER_CHANNEL_SYNC=10
LOG_LEVEL=INFO
Не хардкодить секреты или Google account в source code.
26. Docker Compose на hermesVM
Ожидаемая структура:
youtube-app/
├── backend/
├── frontend/
├── migrations/
├── tests/
├── Dockerfile
├── compose.yml
├── .env
├── .env.example
├── README.md
└── AGENTS.md
Production compose примерно:
services:
app:
build: .
restart: unless-stopped
env_file:
- .env
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: youtube_app
POSTGRES_USER: youtube_app
POSTGRES_PASSWORD: ...
volumes:
- postgres_data:/var/lib/postgresql/data
Никаких media volumes на hermesVM не требуется.
27. Healthchecks
App
GET /api/health
Docker healthcheck должен использовать его.
PostgreSQL
pg_isready.
MeTube
Backend health может выполнить недорогой HTTP request на MeTube.
Нельзя считать весь app unhealthy, если Google API временно недоступен: это external dependency.
28. Logging
Структурированные или как минимум единообразные logs.
Логировать:
- app startup/shutdown;
- OAuth login без token values;
- subscription sync start/end;
- video sync start/end;
- количество полученных/обновлённых объектов;
- YouTube quota/rate errors;
- MeTube request/result;
- MeTube event state changes;
- download failures;
- DB migration version.
Не логировать:
- access token;
- refresh token;
- client secret;
- session secret.
29. Error handling
UI должен различать:
YouTube API temporarily unavailable
YouTube quota exhausted
OAuth expired/revoked
MeTube unavailable
MeTube rejected download
Download failed
Local media missing
Database error
Не показывать пользователю generic blank page.
30. Migration strategy
Использовать Alembic с первого commit.
Приложение не должно создавать/изменять schema ad-hoc при старте без migrations.
Команда production migration должна быть документирована.
Например:
alembic upgrade head
Можно выполнять migration из entrypoint перед запуском app, если используется advisory lock/безопасная single-instance схема.
31. Tests
Минимум:
Backend unit tests
- category CRUD;
- assigning channel to multiple categories;
- feed filtering;
- pagination;
- OAuth allowed-email check;
- Google API pagination;
- sync idempotency;
- MeTube payload generation;
- duplicate download protection;
- filename -> media URL encoding;
- local media fallback.
Integration tests
Использовать mocked Google API и mocked MeTube.
Отдельный opt-in test допускается против реального:
http://192.168.8.177:8081
но он не должен запускаться в обычном CI.
Frontend
Проверить минимум:
- category switch;
- cards rendering;
- download state transitions;
- local/YouTube player selection;
- error state.
32. MVP acceptance criteria
MVP считается готовым, когда выполняются все пункты:
- Пользователь открывает сервис на
hermesVM. - Подключает один разрешённый Google account.
- Сервис импортирует все доступные YouTube subscriptions.
- Список каналов отображается с thumbnails и названиями.
- Можно создать категорию
Linux. - Можно назначить в неё несколько каналов.
- При открытии
Linuxотображаются свежие видео только этих каналов. Всеотображает свежие видео всех подписанных каналов.- Feed работает из нашей БД и не вызывает полный YouTube sync на каждый page load.
- Нескачанное видео можно посмотреть через YouTube.
- На карточке работает
Скачать. - Backend отправляет корректный POST в:
http://192.168.8.177:8081/add. - Download отображает хотя бы состояния
queued/downloading/completed/failed. - После completion точный filename сохраняется в БД.
- Сервис строит корректный MeTube
/download/...URL. - Скачанное видео проигрывается напрямую с
mediaVM. - Seeking внутри локального видео работает.
- При исчезновении локального файла UI корректно fallback-ится на YouTube.
- Приложение переживает restart Docker containers без потери categories и cached feed.
- Секреты отсутствуют в git.
- Проект имеет README с инструкцией запуска.
docker compose up -dявляется основным способом production deployment наhermesVM.
33. Приоритет реализации
Рекомендуемый порядок для coding agent:
Phase 1 — Skeleton
- FastAPI;
- PostgreSQL;
- Alembic;
- React;
- Docker Compose;
- healthcheck.
Phase 2 — Google OAuth + subscriptions
- OAuth;
- subscriptions sync;
- channels table;
- channels UI.
Phase 3 — Categories
- CRUD;
- many-to-many;
- category filter.
Phase 4 — Video sync/feed
- uploads playlists;
- playlistItems;
- videos metadata;
- cached feed;
- background scheduler.
Phase 5 — YouTube playback
- video detail page/modal;
- YouTube embed.
Phase 6 — MeTube
/add;- download_jobs;
- Socket.IO consumer;
- progress;
- exact filename;
- media URL;
- local playback.
Phase 7 — Hardening
- retry/recovery;
- logging;
- quotas;
- tests;
- README;
- HTTPS/reverse-proxy notes.
Не реализовывать последующие фазы заранее, если предыдущая не работает.
34. Важные архитектурные ограничения для агента
- Не форкать Tube Archivist.
- Не встраивать yt-dlp в наш сервис.
- Не дублировать функции скачивания MeTube.
- Не монтировать media storage на hermesVM.
- Не получать YouTube recommendations.
- Не делать multi-user architecture в MVP.
- Не привязывать frontend напрямую к Google API.
- Не привязывать frontend напрямую к MeTube write API.
- Не определять локальный filename из YouTube title.
- Не удалять существующие MeTube-файлы.
- Не менять исходный код MeTube.
- Все внешние base URLs должны задаваться через config/env.
- Backend REST API должен быть пригоден для будущего mobile client.
35. Возможные улучшения после MVP
Не включать в первую реализацию без отдельного решения:
- PWA;
- Android/iOS app;
- просмотрено/не просмотрено;
- Watch Later внутри нашего приложения;
- автоматическое удаление локальной копии через N дней;
- pin/keep forever;
- поиск по подпискам;
- полнотекстовый поиск по videos;
- фильтр Shorts;
- notification о новых видео в категории;
- автоматическое скачивание выбранных каналов;
- SponsorBlock при скачивании;
- выбор качества перед download;
- subtitles;
- отдельный TV UI;
- Chromecast;
- импорт существующих скачанных MeTube файлов;
- offline metadata snapshots.
36. Известные риски
YouTube API quota
При большом числе подписок частый polling может расходовать quota. Интервалы должны быть configurable.
YouTube/yt-dlp changes
MeTube зависит от yt-dlp, поэтому загрузка YouTube может периодически ломаться после изменений YouTube. Наш сервис не должен пытаться самостоятельно чинить extraction layer.
MeTube API stability
POST /add и Socket.IO относятся к API существующего сервиса, но это не формально versioned enterprise API. Интеграцию вынести в отдельный класс:
MeTubeClient
Например:
class MeTubeClient:
async def enqueue_video(...)
async def health(...)
async def check_media(...)
async def run_event_listener(...)
Нельзя размазывать MeTube-specific HTTP calls по всему backend.
Mixed content
Если frontend станет HTTPS, local video тоже должен идти по HTTPS.
Existing MeTube downloads
MVP обязан надёжно отслеживать загрузки, инициированные нашим сервисом.
Автоматическое распознавание всех старых файлов, ранее скачанных вручную через MeTube, в MVP не требуется.
37. Источники/документация
YouTube Data API:
- https://developers.google.com/youtube/v3/docs/subscriptions/list
- https://developers.google.com/youtube/v3/docs/channels
- https://developers.google.com/youtube/v3/docs/playlistItems/list
- https://developers.google.com/youtube/v3/docs/videos/list
- https://developers.google.com/youtube/v3/getting-started
MeTube:
- https://github.com/alexta69/metube
- https://github.com/alexta69/metube/blob/master/README.md
- https://github.com/alexta69/metube/blob/master/app/main.py
- https://github.com/alexta69/metube/blob/master/app/ytdl.py
Итоговая формулировка для coding agent
Нужно создать single-user self-hosted приложение на hermesVM, которое через Google OAuth читает реальные подписки YouTube пользователя, сохраняет их в PostgreSQL, позволяет распределять каналы по пользовательским категориям и показывает cached chronological feed последних видео по всем подпискам или выбранной категории.
Видео, которых нет локально, проигрываются через YouTube embed.
По кнопке Скачать backend отправляет video URL в существующий MeTube на mediaVM (http://192.168.8.177:8081/add), отслеживает состояние загрузки, получает точный filename после завершения и сохраняет media URL. После этого видео должно проигрываться напрямую с MeTube /download/..., а не с YouTube и не через storage hermesVM.
Не реализовывать рекомендации YouTube, multi-user, собственный yt-dlp downloader или Tube Archivist.