calculus / src /eztv.js
no1b4me's picture
Upload 12 files
8740315 verified
import { promisify } from 'util';
const SITE_CONFIG = {
baseUrl: 'https://eztvx.to',
fallbackUrls: [
'https://eztv.wf',
'https://eztv.tf',
'https://eztv.yt',
'https://eztv1.xyz'
],
headers: {
'Host': 'eztvx.to',
'Cookie': 'sort_no=100; q_filter=all; q_filter_web=on; q_filter_reality=on; q_filter_x265=on; layout=def_wlinks',
'Accept': '*/*',
'Accept-Language': '*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
'Accept-Encoding': 'gzip, deflate'
}
};
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 headers = {
...SITE_CONFIG.headers,
'Host': host,
...options.headers
};
const response = await fetch(url, {
...options,
headers,
signal: controller.signal
});
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
function isCorrectEpisode(title, searchQuery) {
if (!title || !searchQuery) return false;
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]);
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
/[. ]E(\d{2})[. ]/i, // .E01.
/(\d{1,2})[. ](\d{2})/ // 1.01 or 1 01
];
for (const pattern of patterns) {
const match = title.match(pattern);
if (match) {
let seasonNum, episodeNum;
if (pattern.toString().includes('[. ]E')) {
seasonNum = querySeasonNum;
episodeNum = parseInt(match[1]);
} else {
seasonNum = parseInt(match[1]);
episodeNum = parseInt(match[2]);
}
if (seasonNum === querySeasonNum && episodeNum === queryEpisodeNum) {
return true;
}
}
}
// Handle combined 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;
}
function parseSize(sizeStr) {
if (!sizeStr) return 0;
const match = sizeStr.match(/([\d.]+)\s*(KB|MB|GB|TB)/i);
if (!match) return 0;
const [, value, unit] = match;
const size = parseFloat(value);
switch (unit.toUpperCase()) {
case 'TB': return size * 1024 * 1024 * 1024;
case 'GB': return size * 1024 * 1024;
case 'MB': return size * 1024;
case 'KB': return size;
default: return 0;
}
}
function extractQuality(title) {
const qualityMatch = title.match(/\b(2160p|1080p|720p|4k|uhd)\b/i);
return qualityMatch ? qualityMatch[1].toLowerCase() : '';
}
function cleanTitle(title) {
if (!title) return '';
return title
.replace(/\[eztv\]/i, '')
.replace(/\[eztvx?\.to\]/i, '')
.replace(/\[eztv\.(re|io)\]/i, '')
.replace(/<img[^>]+>/g, '')
.replace(/\[.*?\]/g, '')
.replace(/\(.*?\)/g, '')
.replace(/\.+/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
async function searchTorrents(searchQuery, type = 'series') {
if (type !== 'series') {
console.log('EZTV only supports TV series searches');
return [];
}
console.log('\n🔄 Searching EZTV for:', searchQuery);
try {
const showMatch = searchQuery.match(/(.+?)S\d{2}E\d{2}/i);
const searchTerm = showMatch ? showMatch[1].trim() : searchQuery;
const formattedQuery = searchTerm
.replace(/-/g, '')
.replace(/\s+/g, '-')
.replace(/&/g, '')
.toLowerCase();
const url = `${SITE_CONFIG.baseUrl}/search/${encodeURIComponent(formattedQuery)}`;
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();
console.log('Response received, length:', html.length);
const rows = html.match(/<tr[^>]*name="hover"[^>]*>[\s\S]*?<\/tr>/g) || [];
console.log(`Found ${rows.length} raw results`);
const streams = rows.map(row => {
try {
const titleLinkMatch = row.match(/<a href="\/ep\/\d+\/[^"]+\/"[^>]*class="epinfo">(.*?)<\/a>/);
if (!titleLinkMatch) return null;
let title = titleLinkMatch[1].replace(/<[^>]+>/g, '').trim();
const magnetMatch = row.match(/href="(magnet:\?xt=urn:btih:[^"]+)"/);
if (!magnetMatch) return null;
const magnetLink = decodeURIComponent(magnetMatch[1]);
const sizeMatch = row.match(/<td[^>]*class="forum_thread_post"[^>]*>([^<]+(?:KB|MB|GB|TB))<\/td>/);
const size = sizeMatch ? sizeMatch[1].trim() : '512 MB';
const seedersMatch = row.match(/<font color="green">(\d+)<\/font>/);
const seeders = seedersMatch ? parseInt(seedersMatch[1]) : 0;
title = cleanTitle(title);
if (title.includes('COMPLETE') && searchQuery.match(/S\d{2}E\d{2}/i)) {
console.log('Skipping complete season pack:', title);
return null;
}
const mainFileSize = parseSize(size);
if (mainFileSize < (100 * 1024)) {
console.log('Skipping small file:', title, size);
return null;
}
return {
magnetLink,
filename: title,
websiteTitle: title,
quality: extractQuality(title),
size,
source: 'EZTV',
seeders,
leechers: 0,
mainFileSize
};
} catch (error) {
console.error('Error parsing row:', error);
return null;
}
}).filter(Boolean);
const filteredStreams = streams.filter(stream =>
isCorrectEpisode(stream.websiteTitle, searchQuery)
);
console.log(`Found ${filteredStreams.length} matching streams after filtering`);
filteredStreams.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;
});
if (filteredStreams.length > 0) {
console.log('Sample stream:', {
title: filteredStreams[0].websiteTitle,
quality: filteredStreams[0].quality,
size: filteredStreams[0].size,
seeders: filteredStreams[0].seeders
});
}
return filteredStreams;
} catch (error) {
console.error('❌ Error searching EZTV:', error);
for (const fallbackUrl of SITE_CONFIG.fallbackUrls) {
try {
console.log(`Trying fallback URL: ${fallbackUrl}`);
const originalBaseUrl = SITE_CONFIG.baseUrl;
SITE_CONFIG.baseUrl = fallbackUrl;
const results = await searchTorrents(searchQuery, type);
if (results.length > 0) {
return results;
}
SITE_CONFIG.baseUrl = originalBaseUrl;
} catch (fallbackError) {
console.error(`Fallback ${fallbackUrl} also failed:`, fallbackError);
}
}
return [];
}
}
export { searchTorrents };