# NewTube — Votre hub vidéo multi-plateformes 🎬🌐 Agrégez, explorez et regardez des vidéos depuis - **YouTube** 🔴 - **Dailymotion** 🔵 - **Twitch** 🟣 - **PeerTube** 🟢 (multi-instances) - **Odysee** 🟡 - **Rumble** 🟠 --- ## ✨ Pourquoi NewTube ? (Objectifs clés) * 🔎 **Recherche unifiée & tendances** : un seul champ de recherche, des sections par fournisseur, filtres cohérents. * 🧭 **Navigation claire** : thèmes (Trending, Live, Gaming, News…), onglets Shorts, pages Playlists/History/Liked. * 👥 **Multi-utilisateur** : playlists **publiques/privées**, préférences, région/qualité par défaut. * ⚙️ **Prod-ready** : API Node/Express, SQLite intégré, rate-limit, logs, tests ciblés. * 📦 **Ops friendly** : image Docker, script `maj.sh`, variables d’env, **astuces `daemon.json`** pour registres HTTP. --- ## 🧩 Stack * 🅰️ **Angular 20** (standalone), **RxJS**, **TailwindCSS** * 🟩 **Node/Express** (sert `dist/` + API) * 🐳 **Docker/Compose** (déploiement) * 💾 **SQLite** (par défaut) – simple, portable --- ## 🎥 Fournisseurs supportés | Plateforme | Recherche | Lecture | Shorts | Live | Playlists\* | | ----------------------------- | :-------: | :-----: | :----: | :--: | :---------: | | YouTube 🔴 | ✅ | ✅ | ✅ | ✅ | ✅ | | Dailymotion 🔵 | ✅ | ✅ | ⏳ | ✅ | ⏳ | | Twitch 🟣 | ✅ | ✅ | N/A | ✅ | ⏳ | | PeerTube 🟢 (multi-instances) | ✅ | ✅ | ⏳ | ⏳ | ⏳ | | Odysee 🟡 | ✅ | ✅ | ⏳ | ⏳ | ⏳ | | Rumble 🟠 | ✅ | ✅ | ⏳ | ⏳ | ⏳ | \* Playlists = intégration locale NewTube (création/gestion); la synchro native dépend de l’API publique de chaque fournisseur. --- ## 🔎 Recherche unifiée (multi-providers) Un seul champ, tous les fournisseurs — avec filtres, raccourcis et deep-links : * **Chips providers** dans la barre : `All / YT / DM / TW / PT / OD / RU` (raccourcis **Alt+1..6**) * **Autocomplete `@`** : tapez `@yt` dans le champ pour filtrer sur YouTube (flèches + Enter, Esc pour fermer) * **Provider picker** : bouton `@` ou **Ctrl/⌘+K** — modal avec filtre texte, sélection multiple et option **« Remember as default »** (persistée dans vos préférences utilisateur) * **Quick menu Ctrl/⌘+K** : historique des recherches + toggles providers en un écran * **Deep-links** : `/#/search?q=…&providers=yt,ru` relance la recherche filtrée — partageable * **Fallback préférence** : URL sans `providers` → préférence `defaultProviders` de l’utilisateur → provider actif * **Accessibilité** : focus trap dans les modals, Esc pour fermer, aria-combobox sur le champ, focus restauré à la fermeture * **Typeahead requête** : sous l’input, suggestions de requêtes (recherches récentes 🕘 + groupes par provider `YT/DM/…`, sous-chaîne surlignée) — `GET /api/search/suggest?q=…&providers=…&limit=…` (min 2 caractères, debounce 250 ms, cache 5 min, dégradation `[]` par provider) ; ↑/↓/Enter/Tab/Esc, priorité au popover `@` ### Endpoints API concernés * `GET /api/search?q=…&providers=yt,dm` — fan-out parallèle, réponse groupée par provider (`page`/`pageSize`/`sort` ; YT sans quota via InnerTube + continuations) * `GET /api/search/suggest?q=…&providers=yt,dm&limit=10` — typeahead `{ q, groups: { yt: string[], … } }` (cache 5 min, rate-limit) * `GET /api/details/youtube/:videoId` — métadonnées + `related[]` (watch-next InnerTube, `?related=0` pour désactiver) * `GET /api/trending?provider=yt&limit=…` — tendances YT sans clé * `GET /healthz` (alias `/api/healthz`) — mode YT, binaire yt-dlp `binOk`, cache, métriques quota/jour, clés * `GET /api/transcript/:provider/:videoId?lang=&instance=&slug=&sourceUrl=` — transcript `{ lang, available, languages, lines: [{ t, dur, text }] }` (cache 24 h, rate-limit 10/min ; absent → 200 `{ available: false }`, échec → 502 ; YouTube : découverte des pistes via InnerTube, `YT_TRANSCRIPT_SOURCE`) * `GET/PATCH /api/user/preferences` — `defaultProviders` (tableau JSON, sanitizé serveur) * `POST /api/telemetry/events` — événements UX anonymes (whitelist : `search_submit`, `provider_picker_open`, `provider_apply`, `at_autocomplete_use`, `quick_menu_open`, `suggest_shown`, `suggest_used`) ### Tests ```bash npm run test:search # unitaires SearchService + parsing @ + picker npm run test:search-e2e # scénarios e2e (serveur réel isolé) npm run test:suggest # typeahead : parsing/dédup + contrat /api/search/suggest npm run test:transcript # transcripts : parseurs json3/vtt + contrat /api/transcript npm run test:preferences # persistance defaultProviders npm run test:telemetry # télémétrie minimale ``` --- ## 🖥️ Fonctionnalités (vue d’ensemble) * 🧭 **Accueil “Tendances & Viral”** par fournisseurs * 🧩 **Thèmes** : Trending, Live, Gaming, Sports, News, Finance, Tech, Science, Health, Music, Podcasts, Movies/TV, Education, Travel, Food, DIY, Auto… * 🎯 **Shorts** : affichage dédié (séparé des vidéos longues) * ❤️ **Liked videos** avec recherche serveur * 🕘 **History** (recherches + visionnage) * 📚 **Playlists** publiques/privées (CRUD, compteurs, dates MAJ) * 🔐 **Auth légère** (JWT), **rate-limit** API * ⚙️ **Préférences** : langue, thème (système / dark / light / blue / black), région, qualité par défaut * 🧩 **PeerTube multi-instances** : activer/désactiver, choisir l’instance active --- ## 🗂️ Structure du repo * `docker-compose/` * `docker-compose.yml` — service `newtube` (image, ports, env, volumes, restart) * `.env.example` — modèle d’environnement * `maj.sh` — **pull** image + **restart** stack * `init.sh` — init des dossiers/volumes & `.env` * `docker/` * `Dockerfile` — build Node/Express (sert `dist/` + API) * `scripts/env-dump.sh` — génère `assets/config.js` depuis l’env (option NGINX) * `config/nginx.conf` — exemple (si variante NGINX) * `server/` * `index.mjs` — routes API, statiques `dist/`, downloads * `db.mjs` — SQLite + migrations légères * `tests/playlist_visibility.test.mjs` — tests playlists * `db/` — `schema.sql` + `migrations/` * `assets/` — `config.local.example.js` * `src/`, `app/`, `public/` — front Angular * `package.json` — scripts (`dev`, `build`, `api`, `api:watch`, `test:playlists`) --- ## 🧱 Prérequis * 🐧 Linux (recommandé) / macOS / Windows (WSL2 ok) * 🐳 Docker Engine ≥ 24, Compose v2 * 🟩 Node.js ≥ 20 (dev local) * 🔐 Accès au registre d’images (ex. `docker-registry.dev.home:5000`) --- ## 🚀 Installation ### Option A — Docker (recommandé) ```bash cp docker-compose/.env.example docker-compose/.env # Éditez docker-compose/.env (hostnames, clés API, secrets…) cd docker-compose docker compose up -d ``` Application : [http://localhost:8080](http://localhost:8080) (mappage `8080:4000`) ### Option B — Dev local ```bash npm install cp assets/config.local.example.js assets/config.local.js # (optionnel) npm run dev # front + api dev proxy # API seule : npm run api npm run api:watch # Build prod : npm run build ``` --- ## 🔁 Déploiement & mises à jour (`maj.sh`) ```bash chmod +x docker-compose/maj.sh ./docker-compose/maj.sh ``` Ce script fait : 1. `docker image pull /newtube-angular:latest` 2. `docker compose down` 3. `docker compose up -d` **Vérifications** : `docker compose ps`, `docker logs newtube --tail=200`, `curl -I http://localhost:8080` --- ## 🧰 Astuce Ops — `Docker /etc/docker/daemon.json` (registres HTTP/insecure) > À configurer **sur la machine qui exécute** `docker compose` / `maj.sh`. ```bash sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json >/dev/null <<'JSON' { "insecure-registries": ["docker-registry.dev.home:5000"], "registry-mirrors": [], "debug": false } JSON # debian linux sudo systemctl daemon-reload sudo systemctl restart docker # Alpine linux sudo rc-service docker restart sudo rc-update add docker docker info | grep -i registry ``` Ajoutez au besoin `log-driver`, `log-opts`, `default-address-pools`, etc. --- ## 🔐 Variables d’environnement (exemples) **Service / compose** * `NGINX_HOSTNAME`, `DIR_NEWTUBE`, `TZ` **App / serveur** * `PORT` (4000), `NODE_ENV`, `JWT_SECRET` * `ACCESS_TTL_MIN`, `REFRESH_TTL_DAYS`, `REMEMBER_TTL_DAYS` * `YT_CACHE_TTL_MS` * **Clés API** : `GEMINI_API_KEY`, `YOUTUBE_API_KEY` ou `YOUTUBE_API_KEYS` (CSV), `VIMEO_ACCESS_TOKEN`, `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET` * `NEWTUBE_DB_FILE` (chemin SQLite alternatif) > Voir `docker-compose/.env.example` pour un point de départ. --- ## 🩺 Troubleshooting (rapide) * 🧾 **Certif/registre** : `x509… unknown authority` → configurez `daemon.json` (ci-dessus) puis `systemctl restart docker` * 🔌 **Port 8080 occupé** : changez le mappage dans `docker-compose.yml` * 🗄️ **Permissions volumes** : vérifiez que `DIR_NEWTUBE` existe et est accessible par Docker * 🧩 **Variables manquantes** : complétez `.env` (clés API, `JWT_SECRET`, etc.) * 🌐 **CORS en dev** : utilisez le `proxy.conf.json` et `npm run dev` --- ## 🗺️ Roadmap * ✅ Dist statique + API Node/Express * ✅ Playlists **publiques/privées** (tests de visibilité) * ✅ Barre de recherche **Liked videos** (filtrage serveur) * ✅ PeerTube **multi-instances** (activer/désactiver, set active) * ✅ Thème (système / dark / light / blue / black) * ✅ Préférences (langue, thème, région, qualité par défaut) * ✅ Auth légère (JWT), rate-limit API * ✅ **Recherche unifiée multi-providers** — chips + autocomplete `@`, picker Ctrl/⌘+K, deep-links `?providers=…`, préférence `defaultProviders` persistée, télémétrie minimale, a11y (focus trap, Esc) * ✅ **Recherche unifiée multi-providers** — chips + autocomplete `@`, picker Ctrl/⌘+K, deep-links `?providers=…`, préférence `defaultProviders` persistée, télémétrie minimale, a11y (focus trap, Esc) * ✅ Navigation par thèmes (Trending, Live, Gaming, News, Finance, Tech, Science, Health, Music, Podcasts, Movies/TV, Education, Travel, Food, DIY, Auto…) * ⏳ **Abonnements** (routes + DB) * ⏳ **Tags** & recherche par tags * ⏳ **Page “Shorts”** unifiée (tous fournisseurs) + badges de durée * ✅ **Téléchargements** intégrés — file d'attente **persistée en SQLite** (survit aux redémarrages API), jobs **par utilisateur** (répertoires isolés, ownership sur status/fichier/cancel), **quota de stockage** configurable (`DOWNLOAD_STORAGE_QUOTA_BYTES`, fenêtre `DOWNLOAD_QUOTA_WINDOW_MS`), **reprise** des jobs échoués/interrrompus (bouton Réessayer), **page Bibliothèque > Téléchargements** (filtres par état, progression live, quota), nettoyage auto des fichiers orphelins au boot * 🔧 Variables : `DOWNLOAD_MAX_CONCURRENT` (2), `DOWNLOAD_STORAGE_QUOTA_BYTES` (5 GiB), `DOWNLOAD_QUOTA_WINDOW_MS` (30 j), `DOWNLOAD_PROVIDERS` (peertube,odysee) * ⏳ **Import/Export** playlists (JSON / OPML-like) * ✅ **Sous-titres & transcripts** — `GET /api/transcript/:provider/:videoId` (yt-dlp `subtitles`/`automatic_captions`, parsing `json3`/`vtt`, cache 24 h, rate-limit), panneau **Transcript** sur la page Watch (sélecteur de langue, dégradation propre si indisponible) * ✅ **YouTube sans quota (InnerTube façon SmartTube)** — `youtubei.js` pinné, chaîne `innertube → scrape yt-dlp → API officielle` (`YT_SEARCH_MODE`, défaut `innertube-first`), **pagination illimitée** via continuations (scroll infini, `page=2,3…`), **vidéos connexes** watch-next dans `GET /api/details/youtube/:videoId` → sidebar Watch, `GET /api/trending?provider=yt`, cache mémoire + SQLite, anti-ban (`YT_COOKIES_FILE`, `YT_PO_TOKEN`, `YT_EGRESS_PROXY`), observabilité `/healthz` (mode, binaire `binOk`, cache, quota jour, clés) * ⏳ **PWA** (installable, offline cache des métadonnées) * ⏳ **Chromecast / AirPlay** * ⏳ Mode “TV” * ⏳ **Observabilité** : healthcheck `/healthz`, métriques, page **Admin** (clés OK/KO, logs, versions) * ✅ **OAuth** (Google/Twitch) pour import favoris/abonnements — `/library/import` : abonnements + likes YouTube (lecture seule), follows Twitch → abonnements, preview + import, `GET /api/oauth/status|connections|preview`, `POST /api/oauth/:provider/import` * ⏳ **Cache serveur** configurable (TTL par provider) * ⏳ **Qualité vidéo** : sélecteur et “auto” intelligent * ⏳ **Traduction UI** (i18n) élargie * ⏳ **Theming avancé** (polices, densité, accents) --- ## 🔒 Notes de sécurité * ❌ Ne **jamais** versionner de secrets réels * 🔑 Utiliser `.env` hors VCS + restrictions par referer côté fournisseurs * 🚧 Limiter l’exposition publique de l’API, activer **rate-limit** (déjà en place) * 🧪 Ajouter des tests e2e/units pour les zones critiques (auth, playlists, downloads) --- ## 🤝 Contribuer Issues & PR bienvenues ! Proposez des connecteurs, des règles de parsing plus robustes ou des idées d’UX. Astuce : ouvrez une PR “Draft” tôt pour discussion/feedback. --- ## 📜 Licence MIT (voir `LICENSE`) --- > 💡 **Tip produit** : gardez l’UX simple — **Thèmes fixes sous le header**, **Shorts séparés**, **CTA clairs** (“View All”, “Add to playlist”), et utilisez les **états vides** (empty states) sur History/Liked/Playlists pour guider l’utilisateur. --- ## 🧩 Ajouter un provider en 5 minutes 1) Front — Registry - Éditez `src/app/core/providers/provider-registry.ts` et ajoutez une entrée `ProviderSpec` avec: - `id` (ex. `"yt"`), `displayName`, `shortLabel`, `icon`, `colorClass` - `supports` (search/shorts/live/playlists) - `buildSearchUrl(q)` (facultatif côté front) 2) Back — Handler - Créez un fichier `server/providers/.mjs` qui exporte `default` avec: - `id`, `label` - `async search(q, { limit, page })` → `Promise` - Enregistrez-le dans `server/providers/registry.mjs` pour être éligible au fan‑out `/api/search`. 3) API — Recherche multi‑providers - L’endpoint `GET /api/search?q=...&providers=yt,dm,ru&limit=...` valide les providers et déclenche les recherches en parallèle (timeouts), puis retourne: ```json { "q": "documentary", "providers": ["yt","dm","ru"], "groups": { "yt": [{ "title": "...", "id": "...", "duration": 192, "isShort": false }], "dm": [], "ru": [{ "title": "...", "id": "...", "isShort": true }] } } ``` 4) UI — Sélecteur & Chips - Le `SearchBoxComponent` (standalone) rend les chips `All / YT / DM / TW / PT / OD / RU` + menu rapide `@` (ProviderPicker). - Le composant émet `(submitted)` avec `{ q, providers }` et met à jour `SearchService` (RxJS state) pour l’appel API. 5) Deep‑link - Les URLs du type `?q=…&providers=yt,ru` relancent la même recherche. Assurez-vous de propager le paramètre `providers` lors des navigations. 6) Tests - Unit (`npm run test:search`) : parsing `@yt` dans l’input, toggles ProviderPicker, composition et fan-out de `SearchService`. - e2e (`npm run test:search-e2e`) : `providers=yt,dm` → réponse ciblée sur [yt,dm]; deep-link `providers=pt`; fallback registry complet; préférence persistée; route SPA. Astuce: l’ajout d’un provider ne nécessite pas de modifier les composants — il suffit d’ajouter une entrée dans le registry front + un handler API.