import { SearchResult, VideoLink, MediaType } from '../../src/types/source.js'; /** * Parse le HTML AJAX de résultats de recherche ou pages catégories FS24. * Supporte les blocs `.search-item` et `.short` */ export function parseListingHTML(html: string, mediaType: MediaType): SearchResult[] { const results: SearchResult[] = []; // 1. Matches pour les blocs de recherche AJAX (.search-item) const searchRegex = /
]*onclick="location\.href='([^']+)'"[^>]*>([\s\S]*?)(?=
]*src=['"]([^'"]+)['"]/); const image = imgMatch ? imgMatch[1]! : null; const titleMatch = block.match(/
([^<]+)<\/div>/); if (!titleMatch) continue; let titleRaw = titleMatch[1]!.trim(); let year: string | null = null; const yearMatch = titleRaw.match(/\((\d{4})\)/); if (yearMatch) { year = yearMatch[1]!; titleRaw = titleRaw.replace(/\s*\(\d{4}\)\s*/, '').trim(); } if (titleRaw && hrefPath) { results.push({ title: titleRaw, year, image, hrefPath, type: mediaType, source: 'fs24' }); } } // 2. Matches pour les pages régulières DLE (.short) const shortRegex = /
([\s\S]*?)<\/div>\s*|
([\s\S]*?)(?=
]*src=['"]([^'"]+)['"]/); const image = imgMatch ? imgMatch[1]! : null; // Extract title const titleMatch = block.match(/
([^<]+)<\/div>/); if (!titleMatch) continue; let titleRaw = titleMatch[1]!.trim(); // Extract link const linkMatch = block.match(/]*href=['"]([^'"]+)['"]/); let hrefPath = linkMatch ? linkMatch[1]! : null; if (!hrefPath) continue; // Remove domain if the link is absolute to keep paths source-agnostic if (hrefPath.startsWith('http')) { try { const u = new URL(hrefPath); hrefPath = u.pathname + u.search; } catch { /* ignore */ } } let year: string | null = null; const yearMatch = titleRaw.match(/\((\d{4})\)/); if (yearMatch) { year = yearMatch[1]!; titleRaw = titleRaw.replace(/\s*\(\d{4}\)\s*/, '').trim(); } if (titleRaw && hrefPath) { results.push({ title: titleRaw, year, image, hrefPath, type: mediaType, source: 'fs24' }); } } return results; } /** * Extrait le news_id depuis la page HTML (attribut data-news-id du bloc commu-releases-block). */ export function extractNewsId(html: string): string | null { const match = html.match(/data-news-id="(\d+)"/); return match ? match[1]! : null; } /** * Décode un lien fsprotect double-Base64 en URL finale. * Format: base64 → "url:|metadata|timestamp|hash" * second_b64 → URL finale (ex: https://1fichier.com/...) */ export function decodeFsProtectLink(rawHref: string): string | null { try { // Extract the ?t= parameter const tParamMatch = rawHref.match(/[?&]t=([^&]+)/); if (!tParamMatch) return null; const base64t = tParamMatch[1]!; // First Base64 decode const decodedT = Buffer.from(base64t, 'base64').toString('utf-8'); // Format: url:||| if (!decodedT.startsWith('url:')) return null; const firstPart = decodedT.substring(4).split('|')[0]!; if (!firstPart) return null; // Second Base64 decode → final URL return Buffer.from(firstPart, 'base64').toString('utf-8'); } catch (e: any) { console.error('[FS24] Erreur décodage lien Base64:', e.message); return null; } } function formatBytes(bytes: number): string { if (!bytes || bytes <= 0) return ''; if (bytes > 1073741824) return (bytes / 1073741824).toFixed(2) + ' GB'; if (bytes > 1048576) return (bytes / 1048576).toFixed(0) + ' MB'; return (bytes / 1024).toFixed(0) + ' KB'; } /** * Parse la réponse JSON de l'API release-api.php en VideoLink[]. */ export function parseReleasesJSON(data: any): VideoLink[] { const links: VideoLink[] = []; if (!data || !data.ok || !Array.isArray(data.items)) return links; for (const item of data.items) { const rawLink = item.original_link || ''; const finalUrl = decodeFsProtectLink(rawLink); if (!finalUrl) continue; const releaseName = item.release_name || 'Inconnu'; const lowerName = releaseName.toLowerCase(); // Detect language from release name const langs: string[] = []; if (lowerName.includes('multi')) langs.push('vf', 'vostfr'); else if (lowerName.includes('vostfr')) langs.push('vostfr'); else if (lowerName.includes('truefrench') || lowerName.includes('french')) langs.push('vf'); else langs.push('vf'); // Detect host from URL let host = 'Inconnu'; try { const urlObj = new URL(finalUrl); host = urlObj.hostname.replace('www.', ''); } catch { /* ignore */ } links.push({ id: String(item.id), host, url: finalUrl, quality: item.quality || '', size: formatBytes(item.size_bytes), releaseName: item.is_team ? `[TEAM] ${releaseName}` : releaseName, langs }); } return links; }