import { SearchResult, VideoLink, SeasonOption, SelectionData, ContentLinks } from '../../src/types/source.js'; import { FlixArtAuth } from './auth.js'; import { FlixArtParser } from './parser.js'; import { CONFIG } from '../../src/utils/config.js'; export class FlixArtAPI { private static get baseUrl() { return CONFIG.FLIXART_URL || ''; } private static get ajaxUrl() { return `${this.baseUrl}/wp-admin/admin-ajax.php`; } private static userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'; // Cache the film page data temporarily for resolveLink private static contextCache: { [url: string]: { postId: string, nonce: string, type: string } } = {}; private static async fetchWithAuth(url: string, options: RequestInit = {}, retries = 1): Promise { try { const cookie = await FlixArtAuth.getCookie(); const headers = new Headers(options.headers || {}); headers.set('User-Agent', this.userAgent); headers.set('Cookie', cookie); headers.set('Origin', this.baseUrl); headers.set('Referer', this.baseUrl); const response = await fetch(url, { ...options, headers }); // If FlixArt returns 403 or redirects to login, refresh cookie and retry if (response.status === 403 && retries > 0) { console.log('[FlixArt] Session expirée, renouvellement du cookie...'); await FlixArtAuth.getCookie(true); return this.fetchWithAuth(url, options, retries - 1); } return response; } catch (error) { if (retries > 0) { await FlixArtAuth.getCookie(true); return this.fetchWithAuth(url, options, retries - 1); } throw error; } } public static async search(query: string, mediaType?: string): Promise { const body = new URLSearchParams({ action: 'flixart_header_search', s: query, search: query, type_query: 'all', post_type: mediaType === 'series' ? 'tv_shows' : 'movies' }); // Search works without auth, but we use fetchWithAuth just in case const res = await this.fetchWithAuth(this.ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'X-Requested-With': 'XMLHttpRequest' }, body: body.toString() }); const data = await res.json(); if (data.success && data.data && data.data.results) { return FlixArtParser.parseSearchAjax(data.data.results); } return []; } public static async getTrending(mediaType: 'movie' | 'series'): Promise { const res = await this.fetchWithAuth(this.baseUrl, { method: 'GET' }); const html = await res.text(); return FlixArtParser.parseTrending(html, mediaType === 'series'); } public static async getSelection(url: string): Promise { const res = await this.fetchWithAuth(url, { method: 'GET' }); const html = await res.text(); const parsed = FlixArtParser.parseSelection(html); // Cache post data for resolveLink if (parsed.postId && parsed.nonce) { this.contextCache[url] = { postId: parsed.postId, nonce: parsed.nonce, type: parsed.isSeries ? 'tv_shows' : 'movies' // Note: actually parser returns isSeries. Captcha needs 'movies' or 'tv_shows' }; } // Prefix ID with url to pass state to resolveLink parsed.links.forEach((link: any) => { link.id = `${url}|${link.id}`; }); return { links: parsed.links, seasons: parsed.seasons, isSeries: parsed.isSeries }; } public static async getContentLinks(url: string, season?: number): Promise { // Not used heavily if getSelection is prioritized, but we need to fetch the season HTML via ajax // For simplicity, if season is passed, we fetch season content via AJAX. // Actually, FlixArt loads all episodes HTML when you click a season tab. // For now, getSelection is sufficient. const selection = await this.getSelection(url); return { links: selection.links }; } }