import { SearchResult, ContentLinks, VideoLink } from '../../src/types/source.js'; function decodeFnMeta(url: string): { quality?: string; langs?: string[] } { try { const m = url.match(/[?&]fn=([^&]+)/); if (!m) return {}; const decoded = Buffer.from(decodeURIComponent(m[1]!), 'base64').toString('utf-8'); const qm = decoded.match(/\[([^\]]+)\]/); const quality = qm ? qm[1]!.trim() : undefined; // Tout après " - " jusqu'à la fin (typiquement la langue : FRENCH, MULTI, VOSTFR…) const lm = decoded.match(/-\s+([A-Za-z]+(?:\s+[A-Za-z]+)?)$/); const langs = lm ? [lm[1]!.trim()] : undefined; return { quality, langs }; } catch { return {}; } } function detectType(href: string): 'movie' | 'series' | 'anime' { if (/[?&]p=serie\b|telecharger-serie|serie-/i.test(href)) return 'series'; if (/animes?/i.test(href)) return 'anime'; return 'movie'; } function absUrl(url: string, baseUrl: string): string { if (url.startsWith('http')) return url; const cleanedBase = baseUrl.replace(/\/$/, ''); return cleanedBase + (url.startsWith('/') ? url : '/' + url); } /** * Strip suffixes de qualité/langue pour dedup par titre normalisé. */ function normalizeTitle(title: string): string { return title .toLowerCase() .normalize('NFD').replace(/[̀-ͯ]/g, '') .replace(/\b(web-?dl|web-?rip|blu-?ray|hdtv|hdrip|dvdrip|hdlight|truefrench|french|multi(?:langues?)?|vff|vf|vostfr|x264|x265|hevc)\b/g, '') .replace(/\b(720p|1080p|2160p|4k|uhd|3d|sd|hd)\b/g, '') .replace(/\(\s*\d{4}\s*\)/g, '') .replace(/-\s*saison\s*\d+/gi, '') .replace(/[^a-z0-9]/g, ''); } function deduplicateByTitle(items: T[]): T[] { const seen = new Set(); return items.filter(it => { const k = normalizeTitle(it.title); if (!k || seen.has(k)) return false; seen.add(k); return true; }); } /** * News utilise la structure DLE classique avec cover_global / cover_infos_title / mainimg * sur la home/listing/recherche. On peut donc partager le parser de listing. */ export function parseListingHTML(html: string, baseUrl: string): SearchResult[] { const results: SearchResult[] = []; const coverRegex = /
]*>([\s\S]*?)(?=
]*>\s*]*>\s*([^<]+)/); if (!titleMatch) continue; const href = absUrl(titleMatch[1]!.trim(), baseUrl); const title = titleMatch[2]!.trim(); const imgMatch = block.match(/]*src="([^"]+)"/); const image = imgMatch ? absUrl(imgMatch[1]!, baseUrl) : null; results.push({ title, year: null, image, hrefPath: href, type: detectType(titleMatch[1]!), source: 'ztnews', }); } return deduplicateByTitle(results); } /** * Parse la fiche film/série de news. * Structure dans