Initial commit (v1.5.9)
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
import { ISource, SearchResult, MediaType, ContentLinks, SelectionData } from '../../src/types/source.js';
|
||||
import { CONFIG } from '../../src/utils/config.js';
|
||||
import { sourceRegistry } from '../../src/core/registry.js';
|
||||
import { fetchSearchResults, fetchTrendingMovies, fetchTrendingSeries, fetchContentPage, fetchResolvedLink, fetchRecent } from './api.js';
|
||||
import { parseSearchHTML, parseContentHTML, extractLinkFromZtProtect } from './parser.js';
|
||||
|
||||
/**
|
||||
* Normalise un titre pour la comparaison (minuscules, sans accents, sans ponctuation).
|
||||
*/
|
||||
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, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Déduplique les résultats par titre normalisé, en gardant la première occurrence.
|
||||
*/
|
||||
function deduplicateByTitle(results: SearchResult[]): SearchResult[] {
|
||||
const seen = new Set<string>();
|
||||
return results.filter(r => {
|
||||
const key = normalizeTitle(r.title);
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export class ZoneTelechargementAPI implements ISource {
|
||||
name = 'zt';
|
||||
displayName = 'Zone-Téléchargement';
|
||||
get baseUrl() {
|
||||
return CONFIG.ZT_URL?.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
async healthCheck(): Promise<boolean> {
|
||||
if (!this.baseUrl) {
|
||||
console.warn('[ZT] ⚠️ ZT_URL non définie.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async search(query: string, mediaType: MediaType = 'movie'): Promise<SearchResult[]> {
|
||||
if (!this.baseUrl) throw new Error('ZT_URL non configurée.');
|
||||
if (!query || query.length < 4) throw new Error('La recherche nécessite au moins 4 caractères.');
|
||||
|
||||
const html = await fetchSearchResults(this.baseUrl, query);
|
||||
if (html.includes('Aucun résultat')) return [];
|
||||
|
||||
let results = parseSearchHTML(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<SearchResult[]> {
|
||||
if (!this.baseUrl) return [];
|
||||
try {
|
||||
const html = mediaType === 'movie'
|
||||
? await fetchTrendingMovies(this.baseUrl)
|
||||
: await fetchTrendingSeries(this.baseUrl);
|
||||
const results = parseSearchHTML(html, this.baseUrl).slice(0, 40);
|
||||
return deduplicateByTitle(results).slice(0, 20);
|
||||
} catch (e: any) {
|
||||
console.error(`[ZT] ❌ Erreur trending ${mediaType}:`, e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getRecent(): Promise<SearchResult[]> {
|
||||
if (!this.baseUrl) return [];
|
||||
try {
|
||||
const html = await fetchRecent(this.baseUrl);
|
||||
const results = parseSearchHTML(html, this.baseUrl).slice(0, 40);
|
||||
return deduplicateByTitle(results).slice(0, 20);
|
||||
} catch (e: any) {
|
||||
console.error(`[ZT] ❌ Erreur getRecent:`, e.message);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async getContentLinks(pageUrl: string): Promise<ContentLinks> {
|
||||
if (!this.baseUrl) throw new Error('ZT_URL non configurée.');
|
||||
const fullUrl = pageUrl.startsWith('http') ? pageUrl : (this.baseUrl + (pageUrl.startsWith('/') ? '' : '/') + pageUrl);
|
||||
const html = await fetchContentPage(fullUrl);
|
||||
return parseContentHTML(html);
|
||||
}
|
||||
|
||||
|
||||
async getSelection(identifier: string, type?: string, seasonValue?: string | number): Promise<SelectionData> {
|
||||
const targetUrl = seasonValue ? String(seasonValue) : identifier;
|
||||
const content = await this.getContentLinks(targetUrl);
|
||||
|
||||
const isSeries = targetUrl.includes('/telecharger-serie/') || targetUrl.includes('/serie-') || (content.relatedSeasons?.length || 0) > 0;
|
||||
|
||||
let currentSeasonLabel = "Saison (Actuelle)";
|
||||
if (content.releaseNames && content.releaseNames.length > 0) {
|
||||
const sm = content.releaseNames[0].match(/Saison\s*\d+/i);
|
||||
if (sm) currentSeasonLabel = sm[0];
|
||||
}
|
||||
|
||||
const formattedSeasons = (content.relatedSeasons || []).map(s => ({
|
||||
label: s.label,
|
||||
value: s.href
|
||||
}));
|
||||
|
||||
if (isSeries) {
|
||||
formattedSeasons.push({ label: currentSeasonLabel, value: targetUrl });
|
||||
formattedSeasons.sort((a, b) => {
|
||||
const numA = parseInt(a.label.replace(/\D/g, '')) || 0;
|
||||
const numB = parseInt(b.label.replace(/\D/g, '')) || 0;
|
||||
return numA - numB;
|
||||
});
|
||||
}
|
||||
|
||||
const allLinks = [...content.links];
|
||||
if (content.relatedQualities && content.relatedQualities.length > 0) {
|
||||
console.log(`[ZT] Fetching ${content.relatedQualities.length} other qualities concurrently...`);
|
||||
const qualityPromises = content.relatedQualities.map(async (q) => {
|
||||
try {
|
||||
const qContent = await this.getContentLinks(q.href);
|
||||
return qContent.links;
|
||||
} catch (e) {
|
||||
console.error(`[ZT] Error fetching quality page ${q.href}:`, e);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
const otherQualitiesLinks = await Promise.all(qualityPromises);
|
||||
otherQualitiesLinks.forEach(links => allLinks.push(...links));
|
||||
}
|
||||
|
||||
return { links: allLinks, seasons: formattedSeasons, isSeries };
|
||||
}
|
||||
|
||||
async resolveLink(linkId: string): Promise<string | null> {
|
||||
try {
|
||||
console.log(`[ZT] 🔓 Résolution du lien : ${linkId}`);
|
||||
const html = await fetchResolvedLink(linkId);
|
||||
const resolved = extractLinkFromZtProtect(html);
|
||||
if (!resolved) {
|
||||
console.warn(`[ZT] ⚠️ Impossible d'extraire le lien résolu du HTML de ZTProtect pour ${linkId}`);
|
||||
}
|
||||
return resolved;
|
||||
} catch (e: any) {
|
||||
console.error(`[ZT] ❌ Erreur resolveLink pour ${linkId}:`, e.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Auto-registration ──
|
||||
sourceRegistry.register(new ZoneTelechargementAPI());
|
||||
Reference in New Issue
Block a user