Commit Graph
21 Commits
Author SHA1 Message Date
bruno f1f6138eb9 feat(docs): documentation web des API (Swagger UI) liée depuis le menu
CI / build-and-test (push) Successful in 15m9s
Le lien « API » du menu ouvre /api/docs (et /proxy/api/docs) : Swagger UI
auto-hébergé (swagger-ui-dist, aucun CDN, thème sombre), spécification chargée
depuis `${base}/openapi.json`.

/openapi.json devient exhaustif :
- inventaire généré depuis l'arbre Express (collectApiRoutes) : `:param` →
  `{param}`, `*` → `{path}`, miroirs /proxy/* et métadonnées écartés, préfixe
  /api retiré (le document porte les serveurs /api et /proxy/api) ;
- `app.all()` réduit à GET — ses 35 méthodes HTTP (acl, propfind, m-search…)
  entraient dans le document et figaient Swagger au bout de 2 groupes ;
- authentification marquée automatiquement (middlewares de route ET de préfixe,
  ex. `r.use('/download', …)`), route publique laissée publique ;
- tags + descriptions de groupe, résumés FR pour les routes courantes ;
- 3 schémas d'auth : bearerAuth (JWT), apiKeyAuth (X-API-Key), cookieAuth ;
- description : méthodes d'auth avec exemples, codes d'erreur et seuils de
  débit, section « Serveur MCP » avec snippet `mcpServers` et la liste des
  outils LUE dans mcp/server.mjs au démarrage (aucune liste à maintenir).

Dockerfile : `mcp/` copié dans l'image (cette lecture doit exister en prod).
package.json → 1.0.61 (tag de cette livraison).

Tests : api_coverage +3 cas (page /docs + assets, exhaustivité/méthodes/tags/
sécurité, 3 schémas + outils MCP dans la description) → 51/51 verts.
Vérifs instance locale : lien menu = /proxy/api/docs, 114 opérations rendues
sur 12 groupes, bouton Authorize + section MCP visibles, zéro erreur console.
2026-10-03 00:33:46 -04:00
bruno 41a28dfccc feat(api): production-ready P0/P1 — secrets hors navigateur, clés d'API, métriques
CI / build-and-test (push) Successful in 14m57s
P0
- Aucun secret servi au navigateur : /assets/config.local.js est généré par le
  serveur AVANT les montages statiques (il l'emporte sur le fichier local) et
  ne contient plus YOUTUBE_API_KEY(S) ; le fichier local n'est plus copié dans
  l'image Docker ni embarqué dans dist (angular ignore config.local.js) ;
  youtube-api.service n'appelle plus googleapis directement (fetchYouTube =
  /api/yt -> /proxy/api/yt), gardes « pas de clé => écran vide », rotation et
  carte de bans côté client supprimées ; readiness YouTube via /healthz
  (youtube.keys.count) ; messages d'erreur orientés configuration serveur.
- CORS : origines pilotées par API_ALLOWED_ORIGINS (CSV), méthode PATCH
  ajoutée (requis par /user/preferences), header X-API-Key accepté.
- Clés d'API longue durée : table api_keys (empreinte SHA-256 + préfixe
  affichable), routes GET/POST /api/keys et DELETE /api/keys/:id, jeton
  ntk_… affiché une seule fois, last_used_at à chaque usage ; X-API-Key
  accepté par authMiddleware ET authMiddlewareCookieAware.

P1
- X-Request-Id renvoyé sur chaque réponse + journal JSON structuré en prod
  (ts, reqId, method, route, status, ms).
- GET /metrics : exposition Prometheus sans dépendance (http_requests_total
  par route/code, somme+nombre de durées, uptime/mémoire ; METRICS_TOKEN
  verrouille l'accès si défini).
- Rate-limit sur /api/details (DETAILS_RATE_LIMIT, 60/min, réponse JSON) —
  /transcript avait déjà le sien.
- Version unique package.json : menu du compte, info.version de l'OpenAPI
  (+ schéma apiKeyAuth et chemins /keys / /metrics documentés).

Tests : api_coverage +7 cas « production-ready » (sentinel de fuite de clé,
X-Request-Id, /metrics, CORS PATCH, contrat/version, cycle complet des clés
d'API), suggest, transcript, flags, filters, kind — verts. Build OK.
Vérifs instance locale : config servie sans secret, /api/yt 200 avec la clé
serveur, /metrics alimenté, menu 1.0.59 et 40 cartes rendues sans clé côté
client.
2026-10-02 23:24:50 -04:00
bruno 6add43e730 feat(ui): menu du compte refondu (icône + titre + sous-titre) et version officielle
CI / build-and-test (push) Successful in 15m4s
Refonte du panneau du bouton avatar (haut à droite du header) : lignes
« icône + titre gras + sous-titre gris » pour Thème (thème appliqué, pastilles
dépliées au clic), Administration, API (openapi.json), Préférences, Guide
d'utilisation, À propos (version + tagline + code source) et Sessions ;
Déconnexion isolée après séparateur et pied de page « Version ».

Ajout de src/app/version.ts (APP_VERSION, alignée sur le tag semver du
registre : 1.0.59 pour cette livraison) affichée dans le menu.

Clés i18n FR + EN ajoutées des deux côtés, ligne README « Menu du compte
refondu » dans le roadmap.
2026-10-02 21:34:05 -04:00
bruno 326455adfe feat: lives PeerTube/Rumble, page Admin, TTL par provider, import/export playlists
CI / build-and-test (push) Successful in 14m56s
#1 Live
- PeerTube: API native ?isLive=true (recherche sepiasearch + videos de chaine),
  mapPeertubeItem exporte la regle live (type/kind/isLive, viewers, jamais de
  duree), caps pt live:true, themes « En direct » branches pt/ru
- Rumble: mode 'live' de scrapeRumbleList (rumble.com/browse/live, SSR), items
  type:live + extraction channelId (/c/<slug>), route GET /api/rumble/live,
  onglet chaine live filtre par channelId (plafond page 1 = 50 directs)
- Odysee: N/A prouve (proxy na-backend sous jeton, probe 2026-10), caps inchangees
- Tests: npm run test:live (25 assertions), assertion adapter_contract mise a
  jour (seuls tw/pt/ru declarent live)

#2 Playlists natives
- Twitch: NON FAISABLE — probe avec token app valide: GET/POST /helix/playlists
  et /helix/playlists/{id}/videos -> 404 (controle /helix/users -> 400).
  Matrice README: Twitch/Odysee/Rumble en N/A avec preuves

#3 Quick wins
- Page Administration /#/admin (entree sidebar Informations + i18n nav.admin
  FR/EN): healthz + providers/metrics rendus (statut, yt-dlp, clés actives/
  bannies, anti-ban, cache par provider, compteurs 1h, quota du jour) ;
  healthz sert aussi sous /proxy/api/healthz
- TTL par provider: server/env-ttl.mjs (<BASE>_<P> -> <BASE> -> defaut, jamais
  nul), branche sur les caches details et transcripts ; suggest reste global
  (cle agregee multi-providers) ; tests npm run test:cache
- Import/Export playlists JSON: GET /playlists/export (newtube-playlists-v1,
  avant /playlists/:id) + POST /playlists/import (dedoublonnage par titre),
  boutons [Exporter] [Importer] sur la page listes de lecture

README: matrice fournisseurs (Live pt/ru OK, od N/A, playlists N/A prouves),
roadmap deplace en ✅ (Live, import/export, Admin, TTL) + 🟡 mis a jour.
2026-10-02 20:25:08 -04:00
bruno 5f1bb78c3b docs(readme): roadmap restructuré (livré / partiel / à faire) + plan des nouveaux fournisseurs
CI / build-and-test (push) Successful in 15m0s
- Légende ✅/🟡/⏳ et section « Pas encore démarré » regroupant les vrais restes
- Abonnements et page Shorts unifiée passés en ✅ (vérifiés câblés: routes /api/subscriptions + groupes, flux /shorts 6 fournisseurs)
- Partiellement livré: tags, observabilité (healthz+metrics faits, Admin non), cache TTL, qualité vidéo, i18n
- Matrice fournisseurs: Shorts ✅ pour Dailymotion/PeerTube/Odysee/Rumble, Twitch = Clips, notes de lecture (filtres durée par provider, rumble natif)
- Nouvelle section candidats Kick/Vimeo/SoundCloud/TikTok + chemin d'implémentation standard en 8 étapes
- Dédoublonnage du doublon « Recherche unifiée », cross-référence avec « Ajouter un provider en 5 minutes »
2026-10-02 17:25:27 -04:00
bruno d1acd1b5b5 style(ui): harmonisation des couleurs par thème + contour fournisseur 1 px
CI / build-and-test (push) Successful in 15m1s
Balayage de la couche d'overrides de index.css : les 75 jetons neutres
slate/zinc/gray qui n'étaient pas couverts (36 fichiers) sont mappés sur les
variables du thème — fonds, textes, bordures, anneaux, divide, placeholders,
dégradés + variants hover/focus/focus-within/disabled/group-hover, alpha via
color-mix. Barre de recherche, menus, panneaux, cartes et résultats de
recherche suivent donc le thème appliqué (dark/light/blue/black) au lieu de la
palette slate figée. Volontairement exclus : bg-white/text-black (boutons
inversés), pastilles « inversées » conditionnelles au thème clair, chrome
au-dessus des vidéos, rouges sémantiques.

Panneau d'informations ⓘ des Shorts re-colorié dans son propre scope
(fond/bordures/textes/pastilles d'action → panel/text/muted), les mêmes tokens
restant blancs ailleurs (rail, méta). Marqueur border-l-4 des titres de page
sur --accent.

Cartes vidéo harmonisées : titre text-sm font-semibold leading-snug
(résultats de recherche, chaîne, thèmes), méta text-xs, rayon rounded-lg sur les
cadres, pastille de durée sans font-mono, titre « Abonnements » avec le
marqueur d'accent.

Contour fournisseur 1 px autour de chaque cadre vidéo, à la couleur du premier
stop du dégradé de la pastille (table unique en CSS, ids longs + courts) :
résultats de recherche, grille chaîne, pages thèmes, watch (lecteur + « à
suivre »), shorts, historique, aimés/à regarder plus tard, détail de playlist.

Tests verts (search, filters, kind, section, highlight, shorts-catalog,
api 41/41) ; vérif UI Playwright desktop 1280x900 + mobile 390x844 sur 4
thèmes : 250 assertions + 13 couleurs de contour au vert.
2026-10-02 16:30:28 -04:00
bruno fe4a0f7f31 docs(readme): le panneau ⓘ des Shorts enrichit description/vues à la demande
CI / build-and-test (push) Successful in 14m58s
2026-10-02 15:01:40 -04:00
bruno c45e04af42 perf(details): Odysee par l'API LBRY + cache des métadonnées (45 s → 0,2 s)
Mesuré avant : `/api/details/odysee/…` = 44,7 s puis « No video formats
found! » — la page Watch restait une minute sans titre, vues, description ni
vignette. L'extracteur `lbry` de yt-dlp n'est pas utilisable ici.

- Nouvelle branche Odysee dans /api/details : `resolve` de l'API LBRY
  (`server/providers/odysee.mjs` → `resolveOdyseeVideo`) = 229 ms mesurés,
  titre/description/vignette/durée/date/canal + avatar. Claim introuvable →
  404 `odysee_claim_not_found` (le client garde les données du flux), panne →
  502 `odysee_details_failed`. Jamais de repli yt-dlp (45 s pour échouer).
- `views` volontairement OMIS pour Odysee : ni LBRY ni lighthouse n'exposent
  de compteur pour un claim isolé (l'`effective_amount` est un montant LBC).
- Cache mémoire de /api/details : 6 h, LRU 500 (`DETAILS_CACHE_TTL_MS`,
  `DETAILS_CACHE_MAX_ENTRIES`) — métadonnées immuables, chaque /watch
  relançait un dump complet.
- `views`/`duration` ne sont plus émis quand ils valent 0 : un 0 écrasait la
  valeur du flux côté client (merge sur `typeof === 'number'`), d'où les
  « 0 vue » sur les fournisseurs muets — même doctrine que
  `parseRumbleViews` (« jamais de compteur à 0 »).
- Mapping pur `odyseeClaimToVideo` testé hors ligne : 21 assertions,
  `npm run test:odysee`.
2026-10-02 14:48:53 -04:00
bruno 6f6d7ddde1 docs(readme): roadmap — UI/UX des Shorts (rail d'actions, panneau d'infos, transition glissée)
CI / build-and-test (push) Canceled after 0s
2026-10-02 13:44:53 -04:00
bruno e48d76be57 refactor(rumble): un seul cœur de scraping, routeur fin, contrat 503 CF
CI / build-and-test (push) Successful in 14m57s
- server/providers/rumble.mjs devient l'unique implémentation : fetch
  (cookie jar __cf_bm + curl_cffi + cooldown global 60 s après un échec
  total), parsing liste/vidéo avec surcouche JSON-LD partagée, vues via
  parseRumbleViews partout (« 1,2K » n'est plus lu en 12 sur /api/rumble).
- server/rumble.mjs = couche HTTP mince : limiter 20/min, cache 60 s,
  5 routes qui délèguent au cœur ; /search passe par le registre de
  providers (cache SQLite, negative cache et channelRef partagés avec
  /api/search).
- Contrat d'erreur : un blocage Cloudflare est un 503
  rumble_cloudflare_challenge, plus jamais un 404 (qui faisait retirer
  les vidéos du flux Shorts lors d'un challenge) ; page reçue sans
  identité = 404 rumble_video_not_found.
- Code mort supprimé : scrapeRumbleVideo d'index.mjs (191 l., 3e
  implémentation jamais appelée), rumbleLimiter 10/min jamais monté,
  import cheerio devenu inutile. Net -238 lignes.
- Tests : parseur générique (vues, durées, ids /shorts/), contrat 503
  sans réseau via le cooldown, durées absentes -> undefined.
- Docs : API_MCP_GUIDE §5.5 (routes, rate-limit réel, erreurs) + note
  README « Rumble peut être temporairement absent selon le réseau ».
2026-10-02 08:36:48 -04:00
bruno f6fdf8ee08 feat(filtres): barre [Filtres] [Tri] unique sur recherche et themes
CI / build-and-test (push) Successful in 14m54s
Nouveau composant partage app-filter-toolbar, utilise tel quel par la page
de recherche ET les deux pages de themes (meme visuel, seul le cablage
differe : URL pour la recherche, signaux locaux pour les themes).

- Boutons : [Filtres] (pastilles de type, ligne Sources, selects periode /
  duree / langue, filtres actifs) et [Tri] (Pertinence, Date de mise en
  ligne, Vues, Note (likes), Duree).
- Page de recherche : le bouton oeil « filtres rapides » et le panneau de
  selects disparaissent, tout est sous [Filtres] ; le tri a son bouton.
- Sources : boutons Toutes / Aucune. « Aucune » masque tout (la garde
  « derniere source masquable » est retiree) et un bandeau vide dit de
  reactiver au moins un fournisseur.
- /t/:theme : les 3 pastilles maison sont remplacees par la barre ; les
  sources masquent des blocs entiers ; filtres et tri appliques sur les
  blocs deja charges.
- /p/:provider/t/:theme : les 5 selects et les pastilles sont remplaces par
  la barre ; signaux maison unifies sur le modele SearchFilters (defaut
  relevance) ; les onglets Twitch lisent/ecrivent filters().type.
- Tri etendu : FilterSort + rating + duration. Les providers ne connaissent
  que relevance/date/views (serveur inchange, repli relevance) : Note et
  Duree sont reordonnes cote client via sortItems(), partage avec les pages
  themes.
- Communs : sortItems / itemViews / itemRating / matchesLanguage /
  formatCount extraits de filters.ts (3 copies d'heuristiques supprimees) ;
  cles i18n fr+en (filters.sort.rating, filters.sort.duration,
  filters.sortHint, filters.localHint, « Date de mise en ligne »).

Tests : test:search, test:filters, test:suggest, test:kind, test:section
verts + test:api 41/41, build ng OK.
2026-10-01 16:26:29 -04:00
bruno 735255b7d8 feat(library): liste « À regarder plus tard » (watch later)
CI / build-and-test (push) Successful in 14m55s
Nouvelle liste dans la section Vous de la barre latérale, avec un bouton
horloge a cote du coeur sur toutes les cartes (accueil, themes, provider-
themes, recherche).

- db : les 4 fonctions de listes (like/unlike/isLiked/list) prennent un
  paramètre `tag` — la table `tags` sert déjà de mot-clés génériques, donc
  zéro migration.
- api : les 4 routes likes sont générées par registerVideoTagRoutes(prefix,
  tagName) ; /api/user/watch-later = même fabrique, même enrichissement
  yt-dlp, même middleware cookie-aware. Entrée OpenAPI ajoutée.
- front : LikesService prend un VideoTag, like-button un @Input() list
  (icône horloge, ambre quand enregistré, libellés a11y dédiés) — un seul
  composant pour les deux boutons, pas de copie.
- cartes de recherche : la grille partagée n'avait AUCUN bouton ; cœur +
  horloge ajoutés, masqués sur les lives et cartes chaîne (coin bas-gauche
  déjà occupé par le compteur de spectateurs / pas des vidéos).
- route /library/watch-later = LikedComponent via data.list (titre, icône,
  placeholder et état vide pilotés par la route), clés nav.watchLater FR/EN.

Tests : npm run test:api 41/41 (nouveau test watch-later : ajout, statut,
indépendance vis-à-vis des likes, liste, retrait, 400, 401) + 21 autres
suites vertes. Build OK, instance locale 4200 rebuildée. Sondage Playwright
desktop+mobile : 12/14 (2 faux positifs du sondage : accordéon replié sur
une route hors /library, 3×401 console pendant la phase déconnectée).
2026-10-01 14:13:43 -04:00
bruno f1f673040e feat(search): panneau de filtres unifié, clavier/focus du typeahead
CI / build-and-test (push) Successful in 14m10s
Barre de recherche
- Panneau de filtres unifié (remplace le ProviderPicker) : sources, type
  (vidéos/shorts/direct/chaînes), période (heure/jour/semaine/mois/année),
  durée (<4 / 4-20 / >20 min) et tri. Pastilles + roving tabindex
  (←/→ dans un groupe, ↑/↓ entre groupes), focus trap, Échap, Ctrl/⌘+Maj+F,
  recherches récentes, « mémoriser par défaut », « Réinitialiser ».
- Pastilles des filtres actifs sous la barre (retrait en un clic) + compteur.
- Typeahead : un seul keydown (fin du double Enter), ↑/↓ bouclants,
  Home/End, PageUp/PageDown, Entrée valide la ligne surlignée (sinon
  recherche brute), Tab complète sans chercher, Échap ferme puis vide,
  ligne « Rechercher <q> », défilement de l'option active, aria à jour.
- Focus : le panneau se referme dès que le focus quitte la barre
  (focusout + relatedTarget + activeElement), pointerdown neutralisé sur les
  lignes pour garder le focus dans l'input (plus de scintillement).
- Opérateurs en clair : `linux live:`, `tuto today: long:` deviennent des
  filtres et sont retirés de la requête (autocomplétion après le `:`).

Filtres côté serveur
- Modèle partagé : src/app/search/filters.ts + server/search-filters.mjs.
- /api/search?type=&duration=&period=&sort= : InnerTube (upload_date, type,
  duration, features), Data API v3 (type, videoDuration, publishedAfter),
  yt-dlp --dateafter ; post-filtrage pour les providers sans filtre natif.
  Un champ manquant ne fait jamais disparaître un résultat.
- Filtres dans l'URL (partageables) + adapters + SearchService (clé de cache).
- Correction : la recherche Shorts renvoyait 0 (ShortsLockupView/GridShelfView
  non mappés, id/titre dans l'endpoint) -> mappage + shelf + vignette/vues.

Tests / docs
- 23 tests unitaires (clavier, focus, opérateurs, panneau de filtres),
  server/tests/search-filters.test.mjs (npm run test:filters) + CI,
  scénarios e2e des filtres, README/api/README/todo/MCP à jour.
2026-09-28 17:42:19 -04:00
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
bruno 4bdcd393fe feat(youtube): InnerTube-first search, transcripts and watch-next related (Steps 15-18)
- InnerTube layer via pinned youtubei.js 18.1.0 (no quota, no key):
  search with merged continuations (unlimited pages), watch-next
  related with LockupView mapping, caption-track discovery
- 3-layer dispatcher (YT_SEARCH_MODE, default innertube-first):
  innertube -> yt-dlp scrape -> official API, graceful errors.yt
- Robust yt-dlp binary resolution (YT_DLP_PATH > PATH > bundled)
  with systematic API fallback (fixes spawn ENOENT in UI)
- Transcript: InnerTube caption discovery (YT_TRANSCRIPT_SOURCE),
  reusing pickTrack/orderedTracks/parseTrackText; yt-dlp fallback kept
- Watch: sidebar uses real watch-next related[] (/api/details),
  title-search fallback for other providers
- Cache: memory LRU + SQLite (youtube_search_cache, youtube_metrics),
  never persist empty pages; /healthz observability; /api/trending
- Includes pending Step 15/16 leftovers in same files (suggest,
  test scripts); unrelated provider adapters left uncommitted
2026-09-25 19:38:46 -04:00
bruno 97e5de74f5 feat(channel): page details chaine multi-providers avec onglets et infinite scroll
CI / build-and-test (push) Successful in 13m34s
- Strategy Pattern front: ChannelProviderInterface + 6 providers (yt/dm/tw/pt/od/ru) + factory (ids longs/courts)
- ChannelContentService: cache par onglet/tri/recherche (TTL 5min), pagination nextPage
- ChannelPage: header (banniere/avatar/subs/description ...plus), tabs dynamiques par capabilities, grilles videos/shorts 9:16/playlists/live, recherche debounced, tri recent/populaire, infinite scroll IntersectionObserver, skeletons/empty/error
- Backend: GET /api/channels/:provider/:id/content (videos/shorts/playlists/live) natif par provider
2026-09-25 10:15:12 -04:00
bruno 4eb339eb22 chore: update Angular cache and TypeScript build info files 2025-09-22 09:34:22 -04:00
bruno 08fd0682a4 docs: revamp README with comprehensive features, deployment guide and roadmap 2025-09-21 17:01:45 -04:00
bruno 3f04191623 chore: update Angular cache and TypeScript build info 2025-09-20 22:33:11 -04:00
bruno b835bfcdbd docs: update README with emoji styling and improved feature documentation 2025-09-15 22:43:25 -04:00
bruno 2866d74b32 first commit 2025-09-14 23:05:30 -04:00