import readTorrent from 'read-torrent'; import { promisify } from 'util'; const readTorrentPromise = promisify(readTorrent); const SITE_CONFIG = { baseUrl: 'https://yourbittorrent.com', fallbackUrls: [ 'https://yourbittorrent2.com' ] }; async function getCinemetaMetadata(imdbId) { try { console.log(`\nš¬ Fetching Cinemeta data for ${imdbId}`); const response = await fetch(`https://v3-cinemeta.strem.io/meta/movie/${imdbId}.json`); if (!response.ok) throw new Error('Failed to fetch from Cinemeta'); const data = await response.json(); console.log('ā Found:', data.meta.name); return data; } catch (error) { console.error('ā Cinemeta error:', error); return null; } } async function fetchWithTimeout(url, options = {}, timeout = 30000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const response = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } } async function downloadAndParseTorrent(url) { try { console.log('Downloading torrent from:', url); const options = { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': '*/*', } }; const torrentInfo = await readTorrentPromise(url, options); // Check if any of the files are video files const hasVideoFiles = torrentInfo.files?.some(file => { const filePath = Array.isArray(file.path) ? file.path.join('/') : file.path; return /\.(mp4|mkv|avi|mov|wmv)$/i.test(filePath); }); if (!hasVideoFiles) { console.log('No video files found'); return null; } // Find main video file const videoFiles = torrentInfo.files.filter(file => { const filePath = Array.isArray(file.path) ? file.path.join('/') : file.path; return /\.(mp4|mkv|avi|mov|wmv)$/i.test(filePath); }).sort((a, b) => b.length - a.length); const mainFile = videoFiles[0]; const mainFilePath = Array.isArray(mainFile.path) ? mainFile.path.join('/') : mainFile.path; const magnetUri = `magnet:?xt=urn:btih:${torrentInfo.infoHash}` + `&dn=${encodeURIComponent(torrentInfo.name)}` + (torrentInfo.announce ? torrentInfo.announce.map(tr => `&tr=${encodeURIComponent(tr)}`).join('') : ''); return { magnetLink: magnetUri, filename: mainFilePath, torrentName: torrentInfo.name, infoHash: torrentInfo.infoHash, mainFileSize: mainFile.length }; } catch (error) { console.error('Error downloading/parsing torrent:', error); return null; } } async function searchTorrents(imdbId) { console.log('\nš Searching YourBittorrent for IMDB:', imdbId); try { const metadata = await getCinemetaMetadata(imdbId); if (!metadata?.meta) { throw new Error('Failed to get movie metadata'); } const movieName = metadata.meta.name.replace(/[&]/g, '').trim(); const searchQuery = `${movieName} ${new Date(metadata.meta.released).getFullYear()}`; console.log('Searching for:', searchQuery); const formattedQuery = searchQuery .replace(/[\\s]+/g, '-') .toLowerCase(); const url = `${SITE_CONFIG.baseUrl}/?v=&c=movies&q=${encodeURIComponent(formattedQuery)}`; console.log('Request URL:', url); const response = await fetchWithTimeout(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9', 'Accept-Language': 'en-US,en;q=0.9' } }); if (!response.ok) { throw new Error(`Search request failed: ${response.status}`); } const html = await response.text(); const results = parseSearchResults(html); console.log(`Found ${results.length} raw results`); // Process each result through read-torrent const streams = await Promise.all(results.map(async result => { try { if (!result.magnetLink) return null; const torrentInfo = await downloadAndParseTorrent(result.magnetLink); if (!torrentInfo) return null; return { magnetLink: torrentInfo.magnetLink, filename: torrentInfo.filename, websiteTitle: result.title, quality: extractQuality(result.title), size: result.size, source: 'YourBittorrent', seeders: parseInt(result.seeders) || 0, leechers: parseInt(result.leechers) || 0, mainFileSize: torrentInfo.mainFileSize }; } catch (error) { console.error('Error processing result:', error); return null; } })); const validStreams = streams.filter(Boolean); const movieYear = new Date(metadata.meta.released).getFullYear().toString(); const searchTerms = movieName.toLowerCase().split(' '); const filteredStreams = validStreams.filter(stream => { const streamTitle = stream.websiteTitle.toLowerCase(); const hasYear = streamTitle.includes(movieYear); const hasAllTerms = searchTerms.every(term => streamTitle.includes(term.toLowerCase()) ); return hasYear && hasAllTerms; }); console.log(`Found ${filteredStreams.length} relevant streams after filtering`); filteredStreams.sort((a, b) => { const qualityOrder = { '2160p': 4, '4k': 4, '1080p': 3, '720p': 2 }; const qualityDiff = (qualityOrder[b.quality] || 0) - (qualityOrder[a.quality] || 0); if (qualityDiff === 0) { return b.seeders - a.seeders; } return qualityDiff; }); return filteredStreams; } catch (error) { console.error('ā Error searching YourBittorrent:', error); for (const fallbackUrl of SITE_CONFIG.fallbackUrls) { try { SITE_CONFIG.baseUrl = fallbackUrl; return await searchTorrents(imdbId); } catch (fallbackError) { console.error(`Fallback ${fallbackUrl} also failed:`, fallbackError); } } return []; } } function parseSearchResults(html) { const results = []; const rows = html.match(/