Files
NewTube/README.md
T
bruno df53445efe feat(oauth): import favoris/abos Google (YouTube) et Twitch vers abonnements/likes
- Backend: server/oauth.mjs (auth URL, echange code, refresh, fetch
  subscriptions/likes Google, follows Twitch), table oauth_connections
  + migration, routes /api/oauth/:provider/{url,callback,preview,import},
  /api/oauth/{status,connections}
- Front: /library/import (connect, preview, import), service
  oauth-import, lien depuis Abonnements
- Config: GOOGLE_CLIENT_ID/SECRET/REDIRECT_URI, TWITCH_REDIRECT_URI,
  OAUTH_APP_BASE_URL documentes (.env.example, docker-compose),
  passthrough docker-compose.local.yml, roadmap README cochee
2026-09-26 18:47:02 -04:00

340 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `@`
<!-- TODO: add docs/search-ux.gif (capture des chips, du picker Ctrl+K et du deep-link) -->
### 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 <registre>/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/<provider>.mjs` qui exporte `default` avec:
- `id`, `label`
- `async search(q, { limit, page })` → `Promise<Suggestion[]>`
- 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.