Coller un lien de playlist (youtube.com/playlist?list=...) dans le champ URL affiche toutes ses videos dans la meme zone de resultats que la recherche de chaine ; chaque ligne a son bouton "Ajouter", et un bouton "Tout ajouter a la file" ajoute le reste d'un coup. Serveur : - GET /api/playlist (PlaylistDiscoveryService) : yt-dlp --flat-playlist -J. -J plutot que -j : le titre de la playlist n'existe qu'a la racine, et une playlist vide reste un resultat valide. - playlistUrl.ts : n'accepte que les liens youtube.com/playlist?list=<id> et reconstruit l'URL canonique depuis le seul identifiant valide (rien d'autre de l'entree n'atteint yt-dlp). Un lien watch?v=...&list=... reste une video (--no-playlist etait deja passe au telechargement). - videoSummary.ts : conversion d'une entree --flat-playlist en resume de video, extraite de ChannelDiscoveryService pour etre partagee (chaine et playlist parsent desormais identiquement). ChannelVideoSummary devient VideoSummary (et ChannelVideoDto -> VideoDto cote client). Client : - Le champ URL accepte video ou playlist (isPlaylistUrl aiguille) ; zone de resultats partagee (createResultsView). - "Tout ajouter" : POST /api/queue sequentiels (ordre conserve, pas de doublon avec les lignes deja ajoutees). Un echec n'interrompt pas le lot : nombre d'echecs + premier message affiches, seules les lignes en echec redeviennent ajoutables. Le mode est lu une seule fois pour tout le lot. - Miniatures de resultats en loading="lazy" (une playlist peut compter des centaines de videos). Verifie contre le vrai yt-dlp (2026.07.04) : forme reelle de -J relevee (fixtures de test calquees dessus), playlist de 1607 videos listee en ~11 s (delai fixe a 120 s). Teste dans le navigateur reel : playlist de 13 videos, ajout unitaire, "tout ajouter" (13 elements, aucun doublon), echec partiel simule + reprise, playlist inexistante, lien video avec list=, recherche de chaine (non-regression). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
1.9 KiB
TypeScript
54 lines
1.9 KiB
TypeScript
import type { FastifyInstance, FastifyPluginAsync, FastifyReply, FastifyRequest } from 'fastify';
|
|
import { PlaylistDiscoveryService } from '../services/PlaylistDiscoveryService.js';
|
|
import { InvalidPlaylistUrlError } from '../services/playlistUrl.js';
|
|
|
|
/** Paramètres de requête acceptés par `GET /api/playlist`. */
|
|
interface GetPlaylistQuerystring {
|
|
/** Lien de la playlist YouTube à parcourir. */
|
|
url: string;
|
|
}
|
|
|
|
const querystringSchema = {
|
|
type: 'object',
|
|
required: ['url'],
|
|
properties: {
|
|
url: { type: 'string', minLength: 1, maxLength: 300 },
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Plugin Fastify exposant les routes de découverte de playlist :
|
|
* `GET /api/playlist` liste les vidéos d'une playlist YouTube.
|
|
*
|
|
* @param app - Instance Fastify sur laquelle enregistrer les routes.
|
|
*/
|
|
export const playlistRoutes: FastifyPluginAsync = async (app: FastifyInstance): Promise<void> => {
|
|
const discoveryService = new PlaylistDiscoveryService(app.log);
|
|
|
|
app.get<{ Querystring: GetPlaylistQuerystring }>(
|
|
'/api/playlist',
|
|
{ schema: { querystring: querystringSchema } },
|
|
async (
|
|
request: FastifyRequest<{ Querystring: GetPlaylistQuerystring }>,
|
|
reply: FastifyReply,
|
|
) => {
|
|
try {
|
|
return await discoveryService.listPlaylist(request.query.url);
|
|
} catch (error) {
|
|
if (error instanceof InvalidPlaylistUrlError) {
|
|
return reply.status(400).send({ error: error.message });
|
|
}
|
|
// assertValidPlaylistUrl() lève InvalidPlaylistUrlError de façon
|
|
// synchrone, avant tout appel à yt-dlp : toute autre erreur ici
|
|
// vient forcément de runYtDlp (binaire introuvable, playlist
|
|
// introuvable ou privée, sortie illisible...), qu'elle soit une
|
|
// YtDlpError ou une erreur brute - toutes sont un échec côté
|
|
// yt-dlp/YouTube.
|
|
return reply.status(502).send({
|
|
error:
|
|
'Échec de la récupération de la playlist depuis YouTube (yt-dlp) : elle est peut-être introuvable ou privée.',
|
|
});
|
|
}
|
|
},
|
|
);
|
|
};
|