CI / build-and-test (push) Successful in 14m9s
Source unique de classification (video-kind.ts) + politique de section centralisee (section-policy.ts). Les composants ne portent plus de seuils ni de regles de type maison. - section-policy.ts: sectionAccepts / routeForVideo / shortsFeedAccepts / isPlayable + classifyOnce (enrichissement _kind a l'ingestion) - watch-short: separe classification (shortsFeedAccepts) et jouabilite (isPlayable: allowEmbed DM, live/channel Twitch, Rumble 404); supprime le seuil Rumble 90 s et l'import YOUTUBE_SHORT_MAX_SECONDS du composant - home: filtre sectionAccepts(...,'home') (plus de shorts dans le trending), routeForVideo, pagination bouclee (min 20, max 5 iterations) - themes: _kind pre-calcule une fois, sectionAccepts, capacites live lues depuis le registre providers (fin de LIVE_PROVIDERS codes en dur) - video-kind: helpers isNativeVerticalProvider / hasKnownDuration - routes: alias /shorts/:id et /live/:id pour routeForVideo - tests: section-policy.spec.ts + video-kind.spec.ts etendus; npm run test:section
57 lines
3.5 KiB
TypeScript
57 lines
3.5 KiB
TypeScript
export type ProviderId = 'yt' | 'dm' | 'tw' | 'pt' | 'od' | 'ru';
|
|
|
|
export interface ProviderSpec {
|
|
id: ProviderId;
|
|
displayName: string;
|
|
shortLabel: string; // YT, DM, ...
|
|
icon: string; // lucide or custom svg name
|
|
colorClass: string; // tailwind text-red-500, etc.
|
|
supports: { search: boolean; shorts: boolean; live: boolean; playlists: boolean };
|
|
buildSearchUrl: (q: string, opts?: { page?: number; limit?: number; providers?: ProviderId[] | 'all' }) => string; // front-only fallback
|
|
}
|
|
|
|
export const PROVIDERS: ProviderSpec[] = [
|
|
{ id: 'yt', displayName: 'YouTube', shortLabel: 'YT', icon: 'youtube', colorClass: 'text-red-500',
|
|
supports: { search: true, shorts: true, live: true, playlists: true },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
{ id: 'dm', displayName: 'Dailymotion', shortLabel: 'DM', icon: 'dailymotion', colorClass: 'text-blue-500',
|
|
supports: { search: true, shorts: false, live: false, playlists: false },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
{ id: 'tw', displayName: 'Twitch', shortLabel: 'TW', icon: 'twitch', colorClass: 'text-purple-500',
|
|
supports: { search: true, shorts: false, live: true, playlists: false },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
{ id: 'pt', displayName: 'PeerTube', shortLabel: 'PT', icon: 'peertube', colorClass: 'text-green-500',
|
|
supports: { search: true, shorts: false, live: false, playlists: true },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
{ id: 'od', displayName: 'Odysee', shortLabel: 'OD', icon: 'odysee', colorClass: 'text-yellow-500',
|
|
supports: { search: true, shorts: false, live: false, playlists: false },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
{ id: 'ru', displayName: 'Rumble', shortLabel: 'RU', icon: 'rumble', colorClass: 'text-orange-500',
|
|
supports: { search: true, shorts: false, live: false, playlists: false },
|
|
buildSearchUrl: (q, opts) => `/api/search?q=${encodeURIComponent(q)}&providers=${encodeURIComponent((opts?.providers && opts.providers !== 'all') ? opts.providers.join(',') : 'yt,dm,tw,pt,od,ru')}`
|
|
},
|
|
];
|
|
|
|
export const PROVIDER_MAP = new Map(PROVIDERS.map(p => [p.id, p]));
|
|
|
|
/** Provider long (instance.service) -> id court du registre. */
|
|
const LONG_TO_SHORT: Record<string, ProviderId> = {
|
|
youtube: 'yt', dailymotion: 'dm', twitch: 'tw', peertube: 'pt', rumble: 'ru', odysee: 'od',
|
|
};
|
|
|
|
/**
|
|
* Le provider supporte-t-il le direct ? Source unique = le registre.
|
|
* Provider inconnu ⇒ false (jamais de crash), l'ajout d'un provider live
|
|
* ne touche que ce registre.
|
|
*/
|
|
export function providerSupportsLive(provider?: string | null): boolean {
|
|
const key = String(provider || '').toLowerCase();
|
|
const id = LONG_TO_SHORT[key] || (key as ProviderId);
|
|
return PROVIDER_MAP.get(id)?.supports.live === true;
|
|
}
|