import { ERROR } from './const.js'; import { createHash } from 'crypto'; class BaseDebrid { #apiKey; constructor(apiKey, prefix) { this.#apiKey = apiKey.replace(`${prefix}=`, ''); } getKey() { return this.#apiKey; } } class DebridLink extends BaseDebrid { constructor(apiKey) { super(apiKey, 'dl'); } static canHandle(apiKey) { return apiKey.startsWith('dl='); } async #request(method, path, opts = {}) { try { const query = opts.query || {}; const queryString = new URLSearchParams(query).toString(); const url = `https://debrid-link.com/api/v2${path}${queryString ? '?' + queryString : ''}`; opts = { method, headers: { 'User-Agent': 'Stremio', 'Accept': 'application/json', 'Authorization': `Bearer ${this.getKey()}`, ...(method === 'POST' && { 'Content-Type': 'application/json' }), ...(opts.headers || {}) }, ...opts }; console.log('\nšŸ”· DebridLink Request:', method, path); if (opts.body) console.log('Request Body:', opts.body); console.log('Request URL:', url); console.log('Request Headers:', opts.headers); const startTime = Date.now(); const res = await fetch(url, opts); console.log(`Response Time: ${Date.now() - startTime}ms`); console.log('Response Status:', res.status); const data = await res.json(); console.log('Response Data:', data); if (!data.success) { switch (data.error) { case 'badToken': throw new Error(ERROR.INVALID_API_KEY); case 'maxLink': case 'maxLinkHost': case 'maxData': case 'maxDataHost': case 'maxTorrent': case 'torrentTooBig': case 'freeServerOverload': throw new Error(ERROR.NOT_PREMIUM); default: throw new Error(`API Error: ${JSON.stringify(data)}`); } } return data.value; } catch (error) { console.error('āŒ Request failed:', error); throw error; } } async checkCacheStatuses(hashes) { try { console.log(`\nšŸ“” DebridLink: Batch checking ${hashes.length} hashes`); console.log('Sample hashes being checked:', hashes.slice(0, 3)); const response = await this.#request('GET', '/seedbox/cached', { query: { url: hashes.join(',') } }); console.log('Raw cache check response:', response); const results = {}; for (const hash of hashes) { const cacheInfo = response[hash]; results[hash] = { cached: !!cacheInfo, files: cacheInfo?.files || [], fileCount: cacheInfo?.files?.length || 0, service: 'DebridLink' }; } const cachedCount = Object.values(results).filter(r => r.cached).length; console.log(`DebridLink found ${cachedCount} cached torrents out of ${hashes.length}`); return results; } catch (error) { if (error.message === ERROR.INVALID_API_KEY) { console.error('āŒ Invalid DebridLink API key'); return {}; } console.error('Cache check failed:', error); return {}; } } async getStreamUrl(magnetLink) { try { console.log('\nšŸ“„ Using DebridLink to process magnet:', magnetLink.substring(0, 100) + '...'); const data = await this.#request('POST', '/seedbox/add', { body: JSON.stringify({ url: magnetLink, async: true }) }); console.log('Seedbox add response:', data); const videoFiles = data.files .filter(file => /\.(mp4|mkv|avi|mov|webm)$/i.test(file.name)) .sort((a, b) => b.size - a.size); if (!videoFiles.length) { console.error('No video files found in torrent'); throw new Error('No video files found'); } console.log('Selected video file:', videoFiles[0].name); return videoFiles[0].downloadUrl; } catch (error) { console.error('āŒ Failed to get stream URL:', error); throw error; } } } class Premiumize extends BaseDebrid { #apiUrl = 'https://www.premiumize.me/api'; constructor(apiKey) { super(apiKey, 'pr'); } static canHandle(apiKey) { return apiKey.startsWith('pr='); } async #request(method, url, opts = {}) { const retries = 3; let lastError; for (let i = 0; i < retries; i++) { try { console.log(`\nšŸ”· Premiumize Request (Attempt ${i + 1}/${retries}):`, method, url); if (opts.body) console.log('Request Body:', opts.body); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 30000); const startTime = Date.now(); const response = await fetch(url, { ...opts, method, signal: controller.signal }); clearTimeout(timeout); console.log(`Response Time: ${Date.now() - startTime}ms`); console.log('Response Status:', response.status); const data = await response.json(); console.log('Response Data:', data); return data; } catch (error) { console.log(`Attempt ${i + 1} failed:`, error.message); lastError = error; if (i < retries - 1) { console.log('Retrying after 2 seconds...'); await new Promise(r => setTimeout(r, 2000)); } } } throw lastError; } async checkCacheStatuses(hashes) { try { console.log(`\nšŸ“” Premiumize: Batch checking ${hashes.length} hashes`); console.log('Sample hashes being checked:', hashes.slice(0, 3)); const params = new URLSearchParams({ apikey: this.getKey() }); hashes.forEach(hash => params.append('items[]', hash)); const data = await this.#request('GET', `${this.#apiUrl}/cache/check?${params}`); if (data.status !== 'success') { if (data.message === 'Invalid API key.') { console.error('āŒ Invalid Premiumize API key'); return {}; } throw new Error('API Error: ' + JSON.stringify(data)); } const results = {}; hashes.forEach((hash, index) => { results[hash] = { cached: data.response[index], files: [], fileCount: 0, service: 'Premiumize' }; }); const cachedCount = Object.values(results).filter(r => r.cached).length; console.log(`Premiumize found ${cachedCount} cached torrents out of ${hashes.length}`); return results; } catch (error) { console.error('Cache check failed:', error); return {}; } } async getStreamUrl(magnetLink) { try { console.log('\nšŸ“„ Using Premiumize to process magnet:', magnetLink.substring(0, 100) + '...'); const body = new FormData(); body.append('apikey', this.getKey()); body.append('src', magnetLink); const data = await this.#request('POST', `${this.#apiUrl}/transfer/directdl`, { body }); if (data.status !== 'success') { console.error('API Error:', data); throw new Error('Failed to add magnet'); } const videoFiles = data.content .filter(file => /\.(mp4|mkv|avi|mov|webm)$/i.test(file.path)) .sort((a, b) => b.size - a.size); if (!videoFiles.length) { console.error('No video files found in torrent'); throw new Error('No video files found'); } console.log('Selected video file:', videoFiles[0].path); return videoFiles[0].link; } catch (error) { console.error('āŒ Failed to get stream URL:', error); throw error; } } } export function getDebridServices(apiKeys) { console.log('\nšŸ” Initializing debrid services with keys:', apiKeys); const services = []; for (const key of apiKeys.split(',')) { if (DebridLink.canHandle(key)) { console.log('Adding DebridLink service'); services.push(new DebridLink(key)); } else if (Premiumize.canHandle(key)) { console.log('Adding Premiumize service'); services.push(new Premiumize(key)); } else { console.log('Unknown service key format:', key); } } console.log(`Initialized ${services.length} debrid services`); return services; } export { DebridLink, Premiumize };