|
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); |
|
|
|
|
|
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; |
|
} |
|
|
|
|
|
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`); |
|
|
|
|
|
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(/<tr class="table-default">[\s\S]*?<\/tr>/g) || []; |
|
|
|
for (const row of rows) { |
|
try { |
|
const titleMatch = row.match(/href="\/torrent\/.*?">(.*?)<\/a>/); |
|
const sizeMatch = row.match(/<td.*?>\s*([\d.]+\s*[KMGT]B)\s*<\/td>/); |
|
const seedersMatch = row.match(/<td.*?>\s*(\d+)\s*<\/td>/); |
|
const leechersMatch = row.match(/<td.*?>\s*(\d+)\s*<\/td>/); |
|
|
|
if (titleMatch) { |
|
const downloadId = row.match(/\/torrent\/(\d+)\//)?.[1]; |
|
results.push({ |
|
title: titleMatch[1], |
|
size: sizeMatch?.[1] || 'Unknown', |
|
seeders: seedersMatch?.[1] || '0', |
|
leechers: leechersMatch?.[1] || '0', |
|
magnetLink: downloadId ? |
|
`${SITE_CONFIG.baseUrl}/down/${downloadId}.torrent` : |
|
null |
|
}); |
|
} |
|
} catch (error) { |
|
console.error('Error parsing result row:', error); |
|
} |
|
} |
|
|
|
return results; |
|
} |
|
|
|
function extractQuality(title) { |
|
const qualityMatch = title.match(/\b(2160p|1080p|720p|4k|uhd)\b/i); |
|
return qualityMatch ? qualityMatch[1].toLowerCase() : ''; |
|
} |
|
|
|
export { searchTorrents }; |
|
|