import parseTorrent from 'parse-torrent'; const SITE_CONFIG = { baseUrl: 'https://1337x.to', fallbackUrls: [ 'https://1337x.st', 'https://x1337x.ws', 'https://x1337x.eu', 'https://x1337x.se', 'https://x1337x.cc' ] }; async function fetchWithTimeout(url, options = {}, timeout = 30000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { const host = new URL(url).host; const response = await fetch(url, { ...options, 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', 'Host': host, ...options.headers }, signal: controller.signal }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } } function extractQuality(title) { const qualityMatch = title.match(/\b(2160p|1080p|720p|4k|uhd)\b/i); return qualityMatch ? qualityMatch[1].toLowerCase() : ''; } function formatSize(sizeStr) { if (!sizeStr) return 'Unknown'; return sizeStr.replace(/\s+/g, '').toUpperCase(); } function parseSearchResults(html) { const results = []; const rows = html.match(/\s*[\s\S]*?<\/tr>/g) || []; for (const row of rows) { try { // Skip rows without torrent links if (!row.includes('href="/torrent/')) continue; // Extract title const titleMatch = row.match(/href="\/torrent\/\d+\/([^"]+)"/); if (!titleMatch) continue; const title = decodeURIComponent(titleMatch[1].replace(/\-/g, ' ')); // Extract torrent path const pathMatch = row.match(/href="(\/torrent\/\d+\/[^"]+)"/); const detailsPath = pathMatch ? pathMatch[1] : null; // Extract size const sizeMatch = row.match(/([^<]+)(\d+)<\/td>/); const leechersMatch = row.match(/(\d+)<\/td>/); const seeders = seedersMatch ? parseInt(seedersMatch[1]) : 0; const leechers = leechersMatch ? parseInt(leechersMatch[1]) : 0; // Extract date const dateMatch = row.match(/([^<]+)<\/td>/); const uploadDate = dateMatch ? dateMatch[1].trim() : ''; if (title && detailsPath) { results.push({ title, detailsPath, size, seeders, leechers, uploadDate }); } } catch (error) { console.error('Error parsing row:', error); } } return results; } async function getMagnetLink(detailsPath) { try { const url = `${SITE_CONFIG.baseUrl}${detailsPath}`; console.log('Fetching details:', url); const response = await fetchWithTimeout(url); if (!response.ok) throw new Error(`Failed to fetch details: ${response.status}`); const html = await response.text(); // Get magnet link const magnetMatch = html.match(/href="(magnet:\?xt=urn:btih:[^"]+)"/); if (!magnetMatch) { console.log('No magnet link found in details page'); return null; } const magnetLink = magnetMatch[1]; // Parse the magnet link const parsed = await parseTorrent(magnetLink); if (!parsed.files) { console.log('No file list in magnet link'); return magnetLink; // Return magnet anyway as some valid magnets might not have file list } // Check files const hasRarFiles = parsed.files.some(file => /\.rar$|\.r\d+$|part\d+\.rar$/i.test(file.name) ); if (hasRarFiles) { console.log('Skipping magnet containing RAR files'); return null; } const videoFiles = parsed.files.filter(file => /\.(mkv|mp4|avi|mov|wmv|m4v|ts)$/i.test(file.name) ); if (videoFiles.length === 0) { console.log('No video files found in magnet'); return null; } console.log('Found video files:', videoFiles.map(f => f.name)); return magnetLink; } catch (error) { console.error('Error getting magnet link:', error); return null; } } function isCorrectEpisode(title, searchQuery) { if (!title || !searchQuery) return false; const queryMatch = searchQuery.match(/S(\d{2})E(\d{2})/i); if (!queryMatch) return true; // If no episode info in search, accept all results const season = queryMatch[1]; const episode = queryMatch[2]; // Common episode patterns const patterns = [ new RegExp(`S${season}E${episode}`, 'i'), new RegExp(`${season}x${episode}`, 'i'), new RegExp(`Season.?${season}.?Episode.?${episode}`, 'i') ]; return patterns.some(pattern => pattern.test(title)); } async function searchTorrents(searchQuery, type = 'movie') { console.log('\nšŸ”„ Searching 1337x for:', searchQuery); try { let searchPath; if (type === 'series') { searchPath = `/category-search/${encodeURIComponent(searchQuery)}/TV/1/`; } else { searchPath = `/category-search/${encodeURIComponent(searchQuery)}/Movies/1/`; } const url = `${SITE_CONFIG.baseUrl}${searchPath}`; console.log('Request URL:', url); const response = await fetchWithTimeout(url); 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} initial results`); // Filter episode matches for TV series const filteredResults = type === 'series' ? results.filter(r => isCorrectEpisode(r.title, searchQuery)) : results; console.log(`Filtered to ${filteredResults.length} matching results`); // Process each result to get magnet links const streams = await Promise.all(filteredResults.map(async result => { try { const magnetLink = await getMagnetLink(result.detailsPath); if (!magnetLink) return null; return { magnetLink, filename: result.title, websiteTitle: result.title, quality: extractQuality(result.title), size: formatSize(result.size), source: '1337x', seeders: result.seeders, leechers: result.leechers }; } catch (error) { console.error('Error processing result:', error); return null; } })); const validStreams = streams.filter(Boolean); if (validStreams.length > 0) { console.log('\nSample stream:', { title: validStreams[0].websiteTitle, quality: validStreams[0].quality, size: validStreams[0].size, seeders: validStreams[0].seeders }); } // Sort by seeders and quality validStreams.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 qualityDiff; return b.seeders - a.seeders; }); return validStreams; } catch (error) { console.error('āŒ Error searching 1337x:', error); for (const fallbackUrl of SITE_CONFIG.fallbackUrls) { try { console.log(`Trying fallback URL: ${fallbackUrl}`); SITE_CONFIG.baseUrl = fallbackUrl; return await searchTorrents(searchQuery, type); } catch (fallbackError) { console.error(`Fallback ${fallbackUrl} also failed:`, fallbackError); } } return []; } } export { searchTorrents };