calculus / src /1337x.js
no1b4me's picture
Upload 12 files
8740315 verified
raw
history blame
8.78 kB
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(/<tr>\s*<td class="coll-1 name">[\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(/<td class="coll-4[^"]*">([^<]+)<span/);
const size = sizeMatch ? sizeMatch[1].trim() : 'Unknown';
// Extract seeders and leechers
const seedersMatch = row.match(/<td class="coll-2 seeds">(\d+)<\/td>/);
const leechersMatch = row.match(/<td class="coll-3 leeches">(\d+)<\/td>/);
const seeders = seedersMatch ? parseInt(seedersMatch[1]) : 0;
const leechers = leechersMatch ? parseInt(leechersMatch[1]) : 0;
// Extract date
const dateMatch = row.match(/<td class="coll-date">([^<]+)<\/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 };