myYouTube/youtube_categories_metube_TZ.md
vrubelroman 0ed20bb838 Implement Phases 1-5: skeleton, OAuth, categories, video sync/feed, playback
- 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>
2026-09-16 18:44:30 +00:00

39 KiB
Raw Blame History

Техническое задание: персональный 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/мобильного приложения.

Главная идея:

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 не должен ломаться.

Нужно:

  1. пометить local state как stale/unknown;
  2. предложить YouTube playback;
  3. дать возможность скачать заново.

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 должен:

  1. проверить, что youtube_video_id существует в нашей БД;
  2. построить canonical URL:
https://www.youtube.com/watch?v=<VIDEO_ID>
  1. не принимать произвольный URL из frontend;
  2. отправить POST в MeTube;
  3. сохранить возвращённый MeTube identifier/status, если он присутствует;
  4. создать/обновить 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 должен:

  1. безопасно проверить, что путь находится под:
METUBE_CONTAINER_DOWNLOAD_DIR=/downloads
  1. получить relative path:
some file.mp4
  1. URL-encode каждый path segment;
  2. сформировать:
<METUBE_PUBLIC_BASE_URL>/download/<encoded-relative-path>

Пример:

http://192.168.8.177:8081/download/some%20file.mp4
  1. выполнить HEAD перед переводом job в окончательный completed, если это не создаёт проблем с конкретной версией MeTube;
  2. сохранить 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

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 считается готовым, когда выполняются все пункты:

  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. Интеграцию вынести в отдельный класс:

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:

MeTube:


Итоговая формулировка для 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.