import { ISource, SearchResult, MediaType, SelectionData, ContentLinks } from '../../src/types/source.js'; import { CONFIG } from '../../src/utils/config.js'; import { sourceRegistry } from '../../src/core/registry.js'; import { fetchSearch, fetchTrending, fetchPage } from './api.js'; import { parseListingHTML, parseContentHTML, parseOtherVersions } from './parser.js'; function isSeriesIdentifier(identifier: string): boolean { return /[?&]p=serie\b|telecharger-serie/i.test(identifier); } function normalizeTitle(title: string): string { return title .toLowerCase() .normalize('NFD') .replace(/[\u0300-\u036f]/g, '') .replace(/-\s*saison\s*\d+/gi, '') .replace(/\(\s*\d{4}\s*\)/g, '') .replace(/[^a-z0-9]/g, ''); } function deduplicateByTitle(results: SearchResult[]): SearchResult[] { const seen = new Set(); return results.filter(r => { const key = normalizeTitle(r.title); if (seen.has(key)) return false; seen.add(key); return true; }); } export class ZtTeamAPI implements ISource { name = 'ztteam'; displayName = 'ZT (Team)'; private baseUrl: string | undefined; constructor(baseUrl?: string) { this.baseUrl = baseUrl?.replace(/\/$/, ''); } async healthCheck(): Promise { if (!this.baseUrl) { console.warn('[ztnews] ⚠️ ZTTEAM_URL non définie.'); return false; } return true; } async search(query: string, mediaType: MediaType = 'movie'): Promise { if (!this.baseUrl) throw new Error('ZTTEAM_URL non configurée.'); if (!query || query.length < 3) throw new Error('La recherche nécessite au moins 3 caractères.'); const html = await fetchSearch(this.baseUrl, query); let results = parseListingHTML(html, this.baseUrl); if (mediaType === 'movie') { results = results.filter(r => r.type === 'movie' || r.type === 'anime'); } else { results = results.filter(r => r.type === 'series' || r.type === 'anime'); } return deduplicateByTitle(results); } async getTrending(mediaType: MediaType): Promise { if (!this.baseUrl) return []; try { const html = await fetchTrending(this.baseUrl, mediaType === 'series' ? 'series' : 'films'); const results = parseListingHTML(html, this.baseUrl); return deduplicateByTitle(results).slice(0, 20); } catch (e: any) { console.error(`[ztnews] Erreur trending ${mediaType}:`, e.message); return []; } } async getRecent(): Promise { if (!this.baseUrl) return []; try { const html = await fetchPage(this.baseUrl); const results = parseListingHTML(html, this.baseUrl); return deduplicateByTitle(results).slice(0, 20); } catch (e: any) { console.error(`[ztnews] Erreur getRecent:`, e.message); return []; } } async getContentLinks(identifier: string): Promise { if (!this.baseUrl) throw new Error('ZTTEAM_URL non configurée.'); const url = identifier.startsWith('http') ? identifier : `${this.baseUrl}/${identifier.replace(/^\//, '')}`; const html = await fetchPage(url); return parseContentHTML(html, isSeriesIdentifier(identifier)); } async getSelection(identifier: string, type?: string, seasonValue?: string | number): Promise { if (!this.baseUrl) throw new Error('ZTTEAM_URL non configurée.'); const targetIdentifier = seasonValue ? String(seasonValue) : identifier; const isSeries = isSeriesIdentifier(targetIdentifier); const url = targetIdentifier.startsWith('http') ? targetIdentifier : `${this.baseUrl}/${targetIdentifier.replace(/^\//, '')}`; const html = await fetchPage(url); const content = parseContentHTML(html, isSeries); const allLinks = [...content.links]; let seasons: { label: string; value: string }[] = []; if (!isSeries) { const otherVersions = parseOtherVersions(html, this.baseUrl); if (otherVersions.length > 0) { console.log(`[ztnews] Fetching ${otherVersions.length} other qualities concurrently...`); const qualityPromises = otherVersions.map(async (q) => { try { const qHtml = await fetchPage(q.value); const qContent = parseContentHTML(qHtml, isSeries); return qContent.links; } catch (e) { console.error(`[ztnews] Error fetching quality page ${q.value}:`, e); return []; } }); const otherQualitiesLinks = await Promise.all(qualityPromises); otherQualitiesLinks.forEach(links => allLinks.push(...links)); } } return { links: allLinks, seasons, isSeries, }; } async resolveLink(linkId: string): Promise { console.log(`[ztTeam] 🔗 Renvoi du lien dl-protect brut (résolution via navigateur ou JDownloader requise) : ${linkId}`); return linkId || null; } } sourceRegistry.register(new ZtTeamAPI(CONFIG.ZTTEAM_URL));