import xml2js from 'xml2js'; import readTorrent from 'read-torrent'; import { promisify } from 'util'; const readTorrentPromise = promisify(readTorrent); const RSS_FEEDS = [ { name: 'TorrentDay', url: 'https://www.torrentday.com/t.rss?7;26;14;46;33;2;31;96;34;5;44;25;11;82;24;21;22;48;13;1;3;32;download;u=addyourown;tp=addyourown;addyourown;private;do-not-share' } ]; const parser = new xml2js.Parser({ explicitArray: false, ignoreAttrs: true }); function isCorrectEpisode(title, searchQuery) { if (!title || !searchQuery) return false; // Extract season and episode numbers from search query const queryMatch = searchQuery.match(/S(\d{2})E(\d{2})/i); if (!queryMatch) return false; const querySeasonNum = parseInt(queryMatch[1]); const queryEpisodeNum = parseInt(queryMatch[2]); // Common episode patterns const patterns = [ /S(\d{2})E(\d{2})/i, // S01E01 /(\d{1,2})x(\d{2})/i, // 1x01 /[. ](\d{1,2})(\d{2})[. ]/, // .101. or ' 101 ' /Season (\d{1,2}) Episode (\d{1,2})/i, // Season 1 Episode 1 /Season.?(\d{1,2}).?Episode.?(\d{1,2})/i, // Season1Episode1, Season.1.Episode.1 /E(\d{2}) of S(\d{2})/i, // E01 of S01 /(\d{1,2})[. ](\d{2})/, // 1.01 or 1 01 /\bS(\d{2}) ?- ?E(\d{2})\b/i // S01-E01 or S01 - E01 ]; for (const pattern of patterns) { const match = title.match(pattern); if (match) { let seasonNum, episodeNum; if (pattern.toString().includes('of S')) { seasonNum = parseInt(match[2]); episodeNum = parseInt(match[1]); } else { seasonNum = parseInt(match[1]); episodeNum = parseInt(match[2]); } if (seasonNum === querySeasonNum && episodeNum === queryEpisodeNum) { return true; } } } // Handle combined season+episode format (101, 102, etc) const combinedMatch = title.match(/[^0-9](\d)(\d{2})[^0-9]/); if (combinedMatch) { const seasonNum = parseInt(combinedMatch[1]); const episodeNum = parseInt(combinedMatch[2]); if (seasonNum === querySeasonNum && episodeNum === queryEpisodeNum) { return true; } } return false; } 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': '*/*', 'Referer': 'https://www.torrentday.com/', 'Origin': 'https://www.torrentday.com' } }; const torrentInfo = await readTorrentPromise(url, options); console.log('Parsed torrent info:', { name: torrentInfo.name, length: torrentInfo.length, files: torrentInfo.files?.length || 0 }); if (!torrentInfo?.infoHash) { console.error('No info hash found'); return null; } // Filter and sort video files 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); }) || []; if (videoFiles.length === 0) { console.log('No video files found'); return null; } // Sort by size and filter small files videoFiles.sort((a, b) => b.length - a.length); const mainFile = videoFiles[0]; // Skip if main file is too small (likely sample) const mainFileSizeMB = mainFile.length / (1024 * 1024); if (mainFileSizeMB < 100) { console.log('Main file too small, likely a sample:', mainFileSizeMB.toFixed(2) + 'MB'); return null; } 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, files: videoFiles, infoHash: torrentInfo.infoHash, mainFile: { path: mainFilePath, length: mainFile.length } }; } catch (error) { console.error('Error:', error); return null; } } function parseTorrentInfo(item) { const desc = item.description || ''; const match = desc.match(/Category:\s*([^]+?)Size:\s*([^]+?)$/); if (!match) { return { size: 'Unknown', category: 'Unknown', sizeInMB: 0 }; } const category = match[1].trim(); const size = match[2].trim(); const sizeMatch = size.match(/([\d.]+)\s*(GB|MB|TB)/i); let sizeInMB = 0; if (sizeMatch) { const [, value, unit] = sizeMatch; sizeInMB = parseFloat(value); switch (unit.toUpperCase()) { case 'TB': sizeInMB *= 1024 * 1024; break; case 'GB': sizeInMB *= 1024; break; } } return { size, category, sizeInMB }; } function extractQuality(title) { const qualityMatch = title.match(/\b(2160p|1080p|720p|4k|uhd)\b/i); return qualityMatch ? qualityMatch[1].toLowerCase() : ''; } async function fetchRSSFeeds(searchQuery, type = 'movie') { console.log('\nšŸ”„ Fetching RSS feeds for:', searchQuery); let allStreams = []; for (const feed of RSS_FEEDS) { try { console.log(`\nFetching from ${feed.name}...`); // Modify search based on type let searchTerm; if (type === 'series') { // Extract show name without episode info for broader search const showMatch = searchQuery.match(/(.+?)S\d{2}E\d{2}/i); searchTerm = showMatch ? showMatch[1].trim() : searchQuery; } else { searchTerm = searchQuery; } const rssUrl = `${feed.url};q=${encodeURIComponent(searchTerm)}`; console.log('Request URL:', rssUrl); const response = await fetch(rssUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Accept': 'application/rss+xml,application/xml,text/xml', 'Referer': 'https://www.torrentday.com/', 'Origin': 'https://www.torrentday.com' } }); if (!response.ok) { console.error(`āŒ Failed to fetch from ${feed.name}:`, response.status); continue; } const rssData = await response.text(); const result = await parser.parseStringPromise(rssData); if (!result?.rss?.channel?.item) { console.log(`No items found in ${feed.name}`); continue; } const items = Array.isArray(result.rss.channel.item) ? result.rss.channel.item : [result.rss.channel.item]; console.log(`Found ${items.length} items in ${feed.name}`); const streams = await Promise.all(items.map(async (item, index) => { try { console.log(`\nProcessing item ${index + 1}/${items.length}:`, item.title); // For series, check if item matches requested episode if (type === 'series' && !isCorrectEpisode(item.title, searchQuery)) { console.log('Episode mismatch:', item.title); return null; } const torrentInfo = await downloadAndParseTorrent(item.link); if (!torrentInfo) return null; const { size, category } = parseTorrentInfo(item); const quality = extractQuality(item.title); return { magnetLink: torrentInfo.magnetLink, filename: torrentInfo.mainFile.path, websiteTitle: item.title, quality, size, category, source: feed.name, infoHash: torrentInfo.infoHash, mainFileSize: torrentInfo.mainFile.length, pubDate: item.pubDate }; } catch (error) { console.error(`Error processing item ${index + 1}:`, error); return null; } })); const validStreams = streams.filter(Boolean); console.log(`āœ… Processed ${validStreams.length} valid streams from ${feed.name}`); allStreams = [...allStreams, ...validStreams]; } catch (error) { console.error(`āŒ Error fetching ${feed.name}:`, error); } } // Sort by quality and size allStreams.sort((a, b) => { const qualityOrder = { '2160p': 4, '4k': 4, 'uhd': 4, '1080p': 3, '720p': 2 }; const qualityDiff = (qualityOrder[b.quality] || 0) - (qualityOrder[a.quality] || 0); if (qualityDiff === 0) { return b.mainFileSize - a.mainFileSize; } return qualityDiff; }); return allStreams; } export { fetchRSSFeeds };