# Техническое задание: персональный YouTube-клиент с категориями подписок и интеграцией MeTube **Статус:** MVP specification **Дата:** 2026-09-16 **Режим:** single-user **Целевая VM приложения:** `hermesVM` **Media/download backend:** существующий MeTube на `mediaVM` --- ## 1. Цель проекта Разработать self-hosted веб-сервис, который работает как персональная лента YouTube-подписок, но добавляет отсутствующую в YouTube удобную категоризацию каналов. Сервис должен: 1. Авторизоваться в Google/YouTube от имени одного владельца. 2. Получать реальные подписки YouTube-аккаунта. 3. Позволять вручную распределять подписанные каналы по пользовательским категориям, например: - Linux - Шахматы - Научпоп - IT - Игры 4. Показывать ленту последних видео: - со всех подписок; - из выбранной категории; - из каналов без категории. 5. Позволять смотреть ролик: - с YouTube, если локальной копии нет; - напрямую с MeTube/mediaVM, если ролик был скачан. 6. Позволять нажать `Скачать`, после чего backend отправляет URL ролика в существующий MeTube. 7. Не хранить сами видео на `hermesVM`. 8. Иметь API, который в дальнейшем можно использовать для PWA/мобильного приложения. Главная идея: ```text 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`. Проверено: ```text MeTube host: mediaVM MeTube IP: 192.168.8.177 MeTube port: 8081 MeTube API base URL: http://192.168.8.177:8081 ``` Docker container: ```text name: metube image: ghcr.io/alexta69/metube:latest port mapping: 0.0.0.0:8081 -> 8081/tcp ``` Compose: ```text /home/vrubel/services/meTube/compose.yml ``` Media storage на `mediaVM`: ```text host: /home/vrubel/hpstorage/meTube container: /downloads ``` ### 3.2. Проверка MeTube API С `hermesVM` успешно выполнен запрос: ```http POST http://192.168.8.177:8081/add Content-Type: application/json {} ``` Ответ: ```text HTTP/1.1 400 missing 'url', 'download_type', or 'quality' ``` Это подтверждает: - endpoint `/add` доступен с `hermesVM`; - MeTube сейчас не требует отдельный API token; - сетевой маршрут `hermesVM -> mediaVM:8081` работает. ### 3.3. Проверка отдачи локального видео Проверенный URL вида: ```text http://192.168.8.177:8081/download/ ``` вернул: ```text HTTP/1.1 200 OK Content-Type: video/webm Accept-Ranges: bytes ``` Следовательно, MeTube можно использовать как media server для HTML5 video playback без монтирования media storage на `hermesVM`. --- ## 4. Архитектура ```text 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**. Правильный путь: ```text 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 может состоять всего из: ```text app postgres ``` Frontend можно собирать на этапе Docker build и раздавать из backend-контейнера как static assets. --- ## 6. Google OAuth и YouTube Data API ### 6.1. OAuth Использовать OAuth 2.0 Web Application. Минимальный scope: ```text https://www.googleapis.com/auth/youtube.readonly ``` При необходимости идентифицировать Google account дополнительно: ```text openid email profile ``` Приложение строго однопользовательское. В `.env` должен быть параметр: ```env ALLOWED_GOOGLE_EMAIL=user@example.com ``` После OAuth backend обязан отклонить другой аккаунт. Refresh token должен храниться зашифрованно. В репозиторий нельзя коммитить: - Google Client Secret; - refresh token; - session secret; - encryption key. --- ## 7. Получение подписок Для получения подписок авторизованного пользователя использовать: ```http GET https://www.googleapis.com/youtube/v3/subscriptions ``` Параметры: ```text part=snippet,contentDetails mine=true maxResults=50 ``` Обязательно обрабатывать `nextPageToken`. Для каждого subscription сохранить минимум: ```text youtube_channel_id title description thumbnail_url subscribed=true ``` После полного sync канал, который был в БД, но исчез из актуального списка подписок, не удалять физически, а установить: ```text subscribed=false ``` Это сохранит пользовательские категории и историю. --- ## 8. Получение uploads playlist каналов После sync подписок получить channel metadata через: ```http GET https://www.googleapis.com/youtube/v3/channels ``` Использовать: ```text part=snippet,contentDetails id= ``` Channel IDs следует отправлять batch-запросами, а не по одному. Из: ```text contentDetails.relatedPlaylists.uploads ``` сохранить: ```text uploads_playlist_id ``` для каждого канала. --- ## 9. Синхронизация новых видео Для каждого активного подписанного канала читать его uploads playlist через: ```http GET https://www.googleapis.com/youtube/v3/playlistItems ``` Параметры: ```text part=snippet,contentDetails playlistId= maxResults=10 ``` Затем новые `video_id` группировать и получать детали через: ```http GET https://www.googleapis.com/youtube/v3/videos ``` Например: ```text part=snippet,contentDetails,status id= ``` Хранить локальный cache метаданных. ### Default sync intervals ```env SUBSCRIPTIONS_SYNC_INTERVAL_HOURS=6 VIDEOS_SYNC_INTERVAL_MINUTES=60 VIDEOS_PER_CHANNEL_SYNC=10 ``` Также нужна кнопка: ```text Обновить сейчас ``` Нельзя при каждом открытии 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` ```text id key value updated_at ``` ### `oauth_credentials` Single row. ```text id google_email encrypted_refresh_token access_token_expires_at created_at updated_at ``` Access token можно не сохранять постоянно, если библиотека умеет обновлять его через refresh token. ### `channels` ```text 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` ```text id name UNIQUE slug UNIQUE sort_order created_at updated_at ``` ### `channel_categories` Many-to-many: ```text channel_id category_id PRIMARY KEY(channel_id, category_id) ``` Один канал может входить в несколько категорий. ### `videos` ```text 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` ```text 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: ```text queued downloading postprocessing completed failed unknown ``` --- ## 12. Категории UI обязан иметь виртуальные категории: ```text Все Без категории ``` Они не обязаны храниться в таблице `categories`. Пользовательские категории: - создать; - переименовать; - удалить; - изменить порядок; - назначить каналу; - убрать канал из категории. Удаление категории **не должно удалять канал**. Канал может одновременно находиться, например, в: ```text Linux IT Self-hosted ``` --- ## 13. Основная лента Главная страница: ```text [Все] [Linux] [Шахматы] [Научпоп] ... ``` или sidebar на desktop. После выбора категории показывать последние ролики всех каналов этой категории. Сортировка: ```text published_at DESC ``` Карточка ролика должна содержать минимум: - thumbnail; - title; - channel title; - publish date/time; - duration, если известна; - local/download status; - кнопку `Смотреть`; - кнопку `Скачать`, если локальной копии нет. Желательные фильтры после базового MVP: - только новые; - только локальные; - только нескачанные; - скрыть просмотренные; - Shorts. Эти фильтры не являются blocker для первой версии. --- ## 14. Просмотр видео ### 14.1. Если локальной копии нет Использовать YouTube player/embed. Например: ```text https://www.youtube.com/embed/ ``` или privacy-enhanced: ```text https://www.youtube-nocookie.com/embed/ ``` ### 14.2. Если локальная копия есть Использовать обычный HTML5: ```html ``` `media_url` должен указывать напрямую на MeTube/mediaVM. Проверено, что MeTube отвечает: ```text Accept-Ranges: bytes ``` поэтому browser seeking должен работать. ### 14.3. Fallback Если `media_url` сохранён в БД, но MeTube отвечает `404/5xx`, UI не должен ломаться. Нужно: 1. пометить local state как stale/unknown; 2. предложить YouTube playback; 3. дать возможность скачать заново. --- ## 15. Интеграция с MeTube ### 15.1. Конфигурация Backend config: ```env 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 ``` Разделять: ```text METUBE_API_BASE_URL ``` и: ```text METUBE_PUBLIC_BASE_URL ``` обязательно. Причина: позже internal API может остаться HTTP/IP, а media playback перейти на HTTPS domain через Nginx Proxy Manager. ### Важное замечание по HTTPS Если наше приложение позже будет открываться через: ```text https://... ``` браузер не должен получать video URL вида: ```text http://192.168.8.177:8081/... ``` из-за mixed-content policy. Тогда `METUBE_PUBLIC_BASE_URL` должен стать HTTPS URL, например через reverse proxy. Внутренний `METUBE_API_BASE_URL` при этом может остаться: ```text http://192.168.8.177:8081 ``` --- ## 16. Добавление загрузки в MeTube Использовать: ```http POST /add ``` Полный URL: ```text http://192.168.8.177:8081/add ``` Пример payload для MVP: ```json { "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 должен: 1. проверить, что `youtube_video_id` существует в нашей БД; 2. построить canonical URL: ```text https://www.youtube.com/watch?v= ``` 3. не принимать произвольный URL из frontend; 4. отправить POST в MeTube; 5. сохранить возвращённый MeTube identifier/status, если он присутствует; 6. создать/обновить `download_jobs`. Повторное нажатие `Скачать` не должно создавать несколько одинаковых активных jobs. --- ## 17. Получение статуса MeTube download Текущий MeTube использует Socket.IO и публикует события, включая: ```text added updated completed canceled cleared ``` Наш backend должен держать server-to-server Socket.IO connection к MeTube. Никакой Socket.IO MeTube напрямую в browser нашего приложения не нужен. При событиях: ```text added/updated ``` обновлять: ```text status progress_percent ``` При: ```text completed ``` сохранить точное поле `filename`, пришедшее от MeTube. Это важно: нельзя пытаться восстановить имя файла по YouTube title. --- ## 18. Построение media URL При `completed` MeTube сообщает финальное имя/путь файла. Если MeTube вернул: ```text /downloads/some file.mp4 ``` backend должен: 1. безопасно проверить, что путь находится под: ```text METUBE_CONTAINER_DOWNLOAD_DIR=/downloads ``` 2. получить relative path: ```text some file.mp4 ``` 3. URL-encode каждый path segment; 4. сформировать: ```text /download/ ``` Пример: ```text http://192.168.8.177:8081/download/some%20file.mp4 ``` 5. выполнить `HEAD` перед переводом job в окончательный `completed`, если это не создаёт проблем с конкретной версией MeTube; 6. сохранить media URL в БД. Нельзя строить путь на основе title из YouTube API: yt-dlp/MeTube могут санитизировать, сокращать или менять filename. --- ## 19. Recovery при рестарте приложения Backend не должен считать download завершённым только потому, что когда-то отправил `/add`. После рестарта: - `completed` jobs с рабочим `media_url` остаются completed; - media URL можно выборочно проверять HEAD-запросом; - незавершённые `queued/downloading/postprocessing` jobs переводятся в `unknown`, если их состояние невозможно достоверно восстановить; - если текущая версия MeTube позволяет получить persistent queue/completed state через Socket.IO/API, использовать это для reconciliation; - если нет — UI должен показывать `Состояние неизвестно` и позволять повторную проверку/загрузку. Не вносить изменения в исходники MeTube ради этого MVP. --- ## 20. Наш backend REST API Минимальный API: ### Health ```http GET /api/health ``` Ответ: ```json { "status": "ok", "database": "ok", "metube": "ok" } ``` Google API не обязательно проверять на каждом healthcheck. ### Auth ```http GET /api/auth/status GET /api/auth/google/start GET /api/auth/google/callback POST /api/auth/logout ``` ### Sync ```http POST /api/sync/subscriptions POST /api/sync/videos GET /api/sync/status ``` Запретить параллельные одинаковые sync jobs. ### Categories ```http GET /api/categories POST /api/categories PATCH /api/categories/{id} DELETE /api/categories/{id} POST /api/categories/reorder ``` ### Channels ```http GET /api/channels GET /api/channels/{id} PUT /api/channels/{id}/categories ``` Опциональные query params: ```text subscribed=true category_id= uncategorized=true ``` ### Feed ```http GET /api/feed ``` Query params: ```text category_id limit cursor local_only downloaded ``` Для `Все` category_id не передавать. ### Videos ```http 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. Сортировка: ```text published_at DESC, id DESC ``` Default: ```text limit=30 ``` Maximum: ```text limit=100 ``` --- ## 22. Frontend screens ### 22.1. First-run / Connect YouTube Если OAuth ещё не выполнен: ```text Подключить YouTube ``` После успешного OAuth автоматически запустить initial subscriptions sync. ### 22.2. Feed Основной экран. Desktop: ```text sidebar categories + video grid/feed ``` Mobile: ```text 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: ```text download_job.completed + media_url reachable -> MeTube local player else -> YouTube player ``` --- ## 23. Download UX Карточка должна показывать состояния примерно так: ```text Скачать ↓ В очереди ↓ Скачивается 42% ↓ Обработка ↓ На сервере ``` При ошибке: ```text Ошибка загрузки [Повторить] ``` После completed кнопка меняется на: ```text На сервере ``` Можно добавить отдельную ссылку: ```text Открыть файл ``` --- ## 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` Пример: ```env 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 Ожидаемая структура: ```text youtube-app/ ├── backend/ ├── frontend/ ├── migrations/ ├── tests/ ├── Dockerfile ├── compose.yml ├── .env ├── .env.example ├── README.md └── AGENTS.md ``` Production compose примерно: ```text 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 ```text 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 должен различать: ```text 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 должна быть документирована. Например: ```text 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 допускается против реального: ```text 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 считается готовым, когда выполняются все пункты: 1. Пользователь открывает сервис на `hermesVM`. 2. Подключает один разрешённый Google account. 3. Сервис импортирует все доступные YouTube subscriptions. 4. Список каналов отображается с thumbnails и названиями. 5. Можно создать категорию `Linux`. 6. Можно назначить в неё несколько каналов. 7. При открытии `Linux` отображаются свежие видео только этих каналов. 8. `Все` отображает свежие видео всех подписанных каналов. 9. Feed работает из нашей БД и не вызывает полный YouTube sync на каждый page load. 10. Нескачанное видео можно посмотреть через YouTube. 11. На карточке работает `Скачать`. 12. Backend отправляет корректный POST в: `http://192.168.8.177:8081/add`. 13. Download отображает хотя бы состояния `queued/downloading/completed/failed`. 14. После completion точный filename сохраняется в БД. 15. Сервис строит корректный MeTube `/download/...` URL. 16. Скачанное видео проигрывается напрямую с `mediaVM`. 17. Seeking внутри локального видео работает. 18. При исчезновении локального файла UI корректно fallback-ится на YouTube. 19. Приложение переживает restart Docker containers без потери categories и cached feed. 20. Секреты отсутствуют в git. 21. Проект имеет README с инструкцией запуска. 22. `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. Важные архитектурные ограничения для агента 1. **Не форкать Tube Archivist.** 2. **Не встраивать yt-dlp в наш сервис.** 3. **Не дублировать функции скачивания MeTube.** 4. **Не монтировать media storage на hermesVM.** 5. **Не получать YouTube recommendations.** 6. **Не делать multi-user architecture в MVP.** 7. **Не привязывать frontend напрямую к Google API.** 8. **Не привязывать frontend напрямую к MeTube write API.** 9. **Не определять локальный filename из YouTube title.** 10. **Не удалять существующие MeTube-файлы.** 11. **Не менять исходный код MeTube.** 12. Все внешние base URLs должны задаваться через config/env. 13. 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. Интеграцию вынести в отдельный класс: ```text MeTubeClient ``` Например: ```python 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.