144 lines
5.0 KiB
TypeScript
144 lines
5.0 KiB
TypeScript
import { CONFIG } from '../../src/utils/config.js';
|
|
|
|
export const CONFIG_HYDRACKER = {
|
|
BASE_URL: (CONFIG.HYDRACKER_URL || '').replace(/\/$/, ''), // Supprime le slash final
|
|
API_KEY: CONFIG.HYDRACKER_API_KEY,
|
|
TIMEOUT: CONFIG.HYDRACKER_TIMEOUT || 15000,
|
|
};
|
|
|
|
const HYDRACKER_HEADERS = {
|
|
'Accept': 'application/json',
|
|
'Authorization': `Bearer ${CONFIG_HYDRACKER.API_KEY}`,
|
|
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
|
};
|
|
|
|
const TIMEOUT = CONFIG_HYDRACKER.TIMEOUT; // 30 secondes par défaut (configurable)
|
|
|
|
async function fetchWithRetry(
|
|
url: string,
|
|
options: RequestInit = {},
|
|
maxRetries: number = 2,
|
|
initialDelay: number = 2000
|
|
): Promise<Response> {
|
|
let attempt = 0;
|
|
let delay = initialDelay;
|
|
|
|
while (true) {
|
|
attempt++;
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
|
|
|
|
try {
|
|
const res = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal
|
|
});
|
|
clearTimeout(timeoutId);
|
|
|
|
if (res.status === 502 || res.status === 503 || res.status === 504 || res.status === 429) {
|
|
if (attempt < maxRetries) {
|
|
console.warn(`[Hydracker-API] Attempt ${attempt}/${maxRetries} returned HTTP ${res.status} on fetch. Retrying in ${delay}ms...`);
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
delay *= 2;
|
|
continue;
|
|
}
|
|
}
|
|
return res;
|
|
} catch (err: any) {
|
|
clearTimeout(timeoutId);
|
|
const isTimeout = err.name === 'AbortError' || err.message?.includes('aborted');
|
|
if (attempt < maxRetries) {
|
|
const waitTime = isTimeout ? 1000 : delay;
|
|
console.warn(`[Hydracker-API] Attempt ${attempt}/${maxRetries} failed/timed out (${err.message}). Retrying in ${waitTime}ms...`);
|
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
if (!isTimeout) delay *= 2;
|
|
continue;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function apiGet(urlPath: string, params: Record<string, any> = {}) {
|
|
const qs = Object.entries(params).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&');
|
|
const url = `${CONFIG_HYDRACKER.BASE_URL}/api/v1/${urlPath}` + (qs ? `?${qs}` : '');
|
|
try {
|
|
const res = await fetchWithRetry(url, {
|
|
headers: HYDRACKER_HEADERS
|
|
});
|
|
if (!res.ok) {
|
|
console.error(`[Hydracker-API] apiGet HTTP ${res.status} on ${urlPath}`);
|
|
return null;
|
|
}
|
|
return await res.json();
|
|
} catch (e: any) {
|
|
console.error(`[Hydracker-API] apiGet Error on ${urlPath}:`, e.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function apiPost(urlPath: string, body: any = {}) {
|
|
const url = `${CONFIG_HYDRACKER.BASE_URL}/api/v1/${urlPath}`;
|
|
try {
|
|
const res = await fetchWithRetry(url, {
|
|
method: 'POST',
|
|
headers: { ...HYDRACKER_HEADERS, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body)
|
|
});
|
|
return { status: res.status, body: await res.text() };
|
|
} catch (e: any) {
|
|
console.error(`[Hydracker-API] apiPost Error on ${urlPath}:`, e.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchSearch(query: string) {
|
|
const url = `${CONFIG_HYDRACKER.BASE_URL}/api/v1/search/${encodeURIComponent(query)}?loader=searchAutocomplete`;
|
|
try {
|
|
const res = await fetchWithRetry(url, {
|
|
headers: HYDRACKER_HEADERS
|
|
});
|
|
if (!res.ok) {
|
|
console.error(`[Hydracker-API] Search HTTP ${res.status} for "${query}"`);
|
|
return null;
|
|
}
|
|
return await res.json();
|
|
} catch (e: any) {
|
|
console.error('[Hydracker-API] Search failed:', e.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchMovieLinks(titleId: string) {
|
|
const url = `${CONFIG_HYDRACKER.BASE_URL}/api/v1/titles/${titleId}/download`;
|
|
try {
|
|
const res = await fetchWithRetry(url, {
|
|
headers: HYDRACKER_HEADERS
|
|
});
|
|
if (!res.ok) return null;
|
|
return await res.json();
|
|
} catch (e: any) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function fetchSeriesLiens(titleId: string, season: number = 1) {
|
|
const allLiens: any[] = [];
|
|
let page = 1;
|
|
while (true) {
|
|
const result = await apiGet('liens', {
|
|
title_id: titleId, loader: 'linksdl', season,
|
|
perPage: 500, page, filters: '', paginate: 'lengthAware'
|
|
});
|
|
if (!result || result.error) break;
|
|
const pagination = result.pagination || {};
|
|
const data = pagination.data || [];
|
|
if (!data.length) break;
|
|
allLiens.push(...data);
|
|
const lastPage = pagination.last_page || pagination.lastPage || 1;
|
|
if (page >= lastPage) break;
|
|
page++;
|
|
}
|
|
return allLiens;
|
|
}
|