import https from 'https'; // --- REPUTATION MODULE --- const TRUSTED_GROUPS = [ 'framestor', 'ntb', 'flux', 'qxr', 'tigole', 'epsilon', 'yify', 'rarbg', 'galaxyrg', 'sparks', 'amiable', 'rovers', 'psa', 'd3g', 'megusta', 'vyndros', 'playweb', 'judas', 'subsplease', 'erai-raws', 'asw', 'chunchunmaru', 'kametsu' ]; function evaluateReputation(stream, searchString) { let detectedGroup = undefined; for (const group of TRUSTED_GROUPS) { if (searchString.includes(group) || searchString.includes(`-${group}`)) { detectedGroup = group; break; } } if (!detectedGroup) { const match = searchString.match(/-([A-Za-z0-9]+)(\s|$|\[)/); if (match && match[1]) { detectedGroup = match[1]; } } let score = 0; if (detectedGroup && TRUSTED_GROUPS.includes(detectedGroup.toLowerCase())) { score = 100; } else if (detectedGroup) { score = 30; } return { score, group: detectedGroup }; } // --- LANGUAGE MODULE --- const KNOWN_LANGUAGES = [ 'multi', 'dual-audio', 'hin', 'hindi', 'eng', 'english', 'fre', 'french', 'spa', 'spanish', 'rus', 'russian', 'ger', 'german', 'ita', 'italian', 'jap', 'japanese', 'kor', 'korean' ]; const LANGUAGE_REGEXES = KNOWN_LANGUAGES.map(lang => ({ lang, regex: new RegExp(`\\b${lang}\\b`, 'i') })); function evaluateLanguage(stream, searchString) { const foundLanguages = []; for (const item of LANGUAGE_REGEXES) { if (item.regex.test(searchString)) { foundLanguages.push(item.lang); } } let score = 50; if (foundLanguages.includes('multi') || foundLanguages.includes('dual-audio') || searchString.includes('แด…แดœแด€สŸ')) { score = 100; } else if (foundLanguages.includes('hin') || foundLanguages.includes('hindi') || searchString.includes('๐Ÿ‡ฎ๐Ÿ‡ณ')) { score = 95; // Top priority for Hindi / Indian releases } else if (foundLanguages.includes('eng') || foundLanguages.includes('english') || searchString.includes('๐Ÿ‡ฌ๐Ÿ‡ง') || searchString.includes('๐Ÿ‡บ๐Ÿ‡ธ')) { score = 80; } else if (foundLanguages.includes('jap') || searchString.includes('๐Ÿ‡ฏ๐Ÿ‡ต')) { score = 75; } else if (foundLanguages.length > 0) { score = 35; } return { score, languages: foundLanguages }; } // --- SEEDERS & STREAM METRICS --- function extractSeeders(stream, searchString) { if (stream.seeders !== undefined && stream.seeders !== null) { const parsed = parseInt(stream.seeders, 10); if (!isNaN(parsed)) return parsed; } const seederMatch = searchString.match(/(?:๐Ÿงฒ\s*|๐Ÿ‘ค\s*|seeders?:?\s*|โ‡‹\s*)(\d+)(?:\s*๐ŸŒฑ)?/i); return seederMatch ? parseInt(seederMatch[1] || '0', 10) : 0; } function evaluateHealth(stream, searchString) { const seeders = extractSeeders(stream, searchString); // Resolution (inspect title, name, description, and filename/bingeGroup without url tokens) const metaString = `${stream.name || ''} ${stream.title || ''} ${stream.description || ''} ${stream.behaviorHints?.filename || ''} ${stream.behaviorHints?.bingeGroup || ''}`.toLowerCase(); let resolutionScore = 50; if (/\b(?:4k|2160p|uhd)\b/i.test(metaString) || metaString.includes('2160p') || metaString.includes('uhd')) resolutionScore = 100; else if (/\b(?:1080p|1080i|fhd)\b/i.test(metaString) || metaString.includes('1080p') || metaString.includes('fhd')) resolutionScore = 80; else if (/\b(?:720p|hd)\b/i.test(metaString) || metaString.includes('720p')) resolutionScore = 60; else if (/\b(?:480p|sd)\b/i.test(metaString) || metaString.includes('480p')) resolutionScore = 40; let seederScore = 0; if (seeders > 100) seederScore = 100; else if (seeders > 50) seederScore = 80; else if (seeders > 10) seederScore = 60; else if (seeders > 0) seederScore = 30; else seederScore = 10; const score = Math.round((seederScore * 0.3) + (resolutionScore * 0.7)); return { score, seeders, resolutionScore }; } // --- STATS MODULE --- // Use null prototype to prevent __proto__ and constructor prototype pollution export const providerStats = Object.create(null); const MAX_PROVIDER_KEYS = 256; function extractProvider(stream) { const searchStr = `${stream.name || ''} ${stream.description || ''} ${stream.title || ''}`; // 1. Check for wrench or key icon in description (e.g. ๐Ÿ”ง Torrentio or ๐Ÿ” HdHub) const wrenchMatch = searchStr.match(/(?:๐Ÿ”ง|๐Ÿ”)\s*([^\sยท\n\r]+)/); if (wrenchMatch && wrenchMatch[1]) { return wrenchMatch[1].trim(); } // 2. Check for bracket prefix (e.g. [Torrentio]) const bracketMatch = (stream.name || '').match(/^\[([^\]]+)\]/); if (bracketMatch && bracketMatch[1]) { return bracketMatch[1].trim(); } // 3. Fallback to common known providers const knownMatch = searchStr.match(/\b(torrentio|comet|meteor|torrentsdb|mediafusion|hdhub|peerflix|sootio|flix-streams|flixstreams|penguplay)\b/i); if (knownMatch && knownMatch[1]) { return knownMatch[1]; } return 'Unknown'; } function recordProviderStats(stream, isHighestScoring) { const provider = extractProvider(stream); if (!providerStats[provider]) { if (Object.keys(providerStats).length >= MAX_PROVIDER_KEYS) return; providerStats[provider] = { totalStreams: 0, highestScores: 0 }; } providerStats[provider].totalStreams += 1; if (isHighestScoring) { providerStats[provider].highestScores += 1; } } // --- METADATA & TITLE MISMATCH ENGINE --- function escapeRegExp(string) { return String(string).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // Bounded LRU Cache: Max 500 items, 4-hour TTL, negative cache support (avoids cgroup OOM) class BoundedMetaCache { constructor(maxEntries = 500, defaultTtlMs = 4 * 60 * 60 * 1000) { this.cache = new Map(); this.max = maxEntries; this.defaultTtl = defaultTtlMs; } get(key) { const entry = this.cache.get(key); if (!entry) return undefined; if (Date.now() > entry.expiresAt) { this.cache.delete(key); return undefined; } // Refresh recency for LRU this.cache.delete(key); this.cache.set(key, entry); return entry.value; } set(key, value, customTtl) { if (this.cache.size >= this.max) { const oldestKey = this.cache.keys().next().value; if (oldestKey !== undefined) this.cache.delete(oldestKey); } const ttl = customTtl !== undefined ? customTtl : this.defaultTtl; this.cache.set(key, { value, expiresAt: Date.now() + ttl }); } } const metaCache = new BoundedMetaCache(500, 4 * 60 * 60 * 1000); export function fetchCinemetaMeta(type, id) { const cleanId = String(id || '').split(':')[0]; if (!cleanId || cleanId.startsWith('kitsu:')) { return Promise.resolve(null); } const cacheKey = `${type}:${cleanId}`; const cached = metaCache.get(cacheKey); if (cached !== undefined) return Promise.resolve(cached); return new Promise((resolve) => { let settled = false; const safeResolve = (val, ttl) => { if (settled) return; settled = true; metaCache.set(cacheKey, val, ttl); resolve(val); }; const fetchUrl = (url, depth = 0) => { if (depth > 2) return safeResolve(null, 60 * 1000); try { const req = https.get(url, { timeout: 3500 }, (res) => { // Handle HTTP redirects safely (prevent ERR_INVALID_URL and SSRF) if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) { res.resume(); res.destroy(); try { const redirectUrl = new URL(res.headers.location, url); if (redirectUrl.protocol === 'https:' && redirectUrl.hostname.endsWith('strem.io')) { return fetchUrl(redirectUrl.href, depth + 1); } } catch (err) { // Invalid redirect URL } return safeResolve(null, 60 * 1000); } if (res.statusCode < 200 || res.statusCode >= 300) { res.resume(); return safeResolve(null, 60 * 1000); // Negative cache on non-200 } let data = ''; res.on('data', chunk => { data += chunk; if (data.length > 512 * 1024) { // 512 KB response ceiling res.destroy(); safeResolve(null, 60 * 1000); } }); res.on('end', () => { try { const parsed = JSON.parse(data); const rawMeta = parsed.meta; if (rawMeta && typeof rawMeta === 'object') { // Store only lightweight projection (name, language, year) - never full videos[] const projected = { name: typeof rawMeta.name === 'string' ? rawMeta.name : '', language: typeof rawMeta.language === 'string' ? rawMeta.language : '', year: rawMeta.year || null }; safeResolve(projected); } else { safeResolve(null, 60 * 1000); } } catch (e) { safeResolve(null, 60 * 1000); } }); res.on('error', () => safeResolve(null, 60 * 1000)); res.on('close', () => safeResolve(null, 60 * 1000)); }); req.on('error', () => safeResolve(null, 60 * 1000)); req.on('timeout', () => { req.destroy(); safeResolve(null, 60 * 1000); }); } catch (err) { safeResolve(null, 60 * 1000); } }; fetchUrl(`https://v3-cinemeta.strem.io/meta/${type}/${cleanId}.json`); }); } function filterMismatches(streams, meta) { if (!meta || !meta.name) return streams; const targetName = (meta.name || '').toLowerCase(); const targetLang = (meta.language || '').toLowerCase(); const colonIdx = meta.name.indexOf(':'); const dashIdx = meta.name.indexOf(' - '); let subtitle = null; let mainTitle = targetName; if (colonIdx !== -1) { mainTitle = targetName.slice(0, colonIdx).trim(); subtitle = targetName.slice(colonIdx + 1).trim(); } else if (dashIdx !== -1) { mainTitle = targetName.slice(0, dashIdx).trim(); subtitle = targetName.slice(dashIdx + 3).trim(); } const subtitleKeyword = subtitle ? subtitle.replace(/^(the|a|an)\s+/i, '').trim().toLowerCase() : null; const hasSubtitleMatch = subtitleKeyword && streams.some(s => { if (!s || typeof s !== 'object') return false; const filename = (s.behaviorHints?.filename || s.title || '').toLowerCase(); return filename.includes(subtitleKeyword); }); // Compile regex ONCE per request outside .filter() with escaping and digit boundary (?![0-9]) const sequelConflictRegex = (hasSubtitleMatch && subtitleKeyword && mainTitle) ? new RegExp(escapeRegExp(mainTitle) + '[.\\s]+(\\d{1,2})(?![0-9])', 'i') : null; return streams.filter(s => { if (!s || typeof s !== 'object') return false; const filename = (s.behaviorHints?.filename || s.title || '').toLowerCase(); const searchStr = `${s.name || ''} ${s.description || ''} ${filename}`.toLowerCase(); // Drop non-media info banners if (!s.url && !s.infoHash) return false; // 1. Cross-Language Remake Mismatch: // When requesting a Hindi film, reject streams that are explicitly other regional movies dubbed into Hindi // (e.g. Malayalam.Movie, Tamil.Movie, Telugu.Movie, HIN.MAL) if (targetLang === 'hindi') { const isOtherRegional = searchStr.includes('malayalam.movie') || searchStr.includes('malayalam movie') || searchStr.includes('hin.mal') || searchStr.includes('tamil.movie') || searchStr.includes('telugu.movie') || searchStr.includes('kannada.movie'); if (isOtherRegional) { console.log(`[Filter Mismatch] Removed regional remake stream: ${filename}`); return false; } } // 2. Conflicting Sequel Number Mismatch: // If target has a distinctive subtitle (e.g. "The Conclusion") and we have streams matching it, // drop streams that belong to a conflicting sequel entry (e.g. "Drishyam.3" or "Drishyam.2") if (sequelConflictRegex) { const matchesSubtitle = filename.includes(subtitleKeyword); if (!matchesSubtitle && sequelConflictRegex.test(filename)) { console.log(`[Filter Mismatch] Removed conflicting sequel title: ${filename}`); return false; } } return true; }); } // --- CONSENSUS & PIPELINE --- export async function runVerificationPipeline(streams, reqUrl = '') { if (!Array.isArray(streams)) return streams; // Title & Metadata Verification Filter: Purge fake releases and franchise mismatches const streamMatch = (reqUrl || '').match(/\/stream\/(movie|series)\/([^/.]+)/); if (streamMatch) { const type = streamMatch[1]; const id = streamMatch[2]; try { const meta = await fetchCinemetaMeta(type, id); if (meta) { streams = filterMismatches(streams, meta); } } catch (e) { console.error('[Cinemeta Pipeline Error]', e.message); } } // Pre-Filter: Purge promotional clips, trailers, samples, tiny files (<50MB), and dead 0-seeder swarms // Scoped strictly to metadata fields (never s.url) to prevent false-dropping signed URLs const TRASH_KEYWORDS = /\b(trailer|promo|teaser|sample|preview|clip|recap|ncop|nced|pv)\b/i; streams = streams.filter(s => { if (!s || typeof s !== 'object') return false; const filename = s.behaviorHints?.filename || ''; const searchStr = `${s.title || ''} ${s.description || ''} ${s.name || ''} ${filename}`.toLowerCase(); if (TRASH_KEYWORDS.test(searchStr)) return false; // Check file size (if present, must be >= 50MB to be a real episode/movie) let sizeNum = null; if (typeof s.size === 'number' && !isNaN(s.size)) sizeNum = s.size; else if (typeof s.size === 'string') sizeNum = parseInt(s.size, 10); if (sizeNum !== null && sizeNum > 0 && sizeNum < 50000000) return false; // Check for dead 0-seeder P2P torrents (keep HTTP/Debrid links) const isHttp = searchStr.includes('โšก') || searchStr.includes('๐ŸŒ') || Boolean(s.url); if (!isHttp && s.infoHash) { const seeders = extractSeeders(s, searchStr); if (seeders === 0) return false; } return true; }); // Consensus Map const consensusMap = {}; for (const stream of streams) { if (stream && stream.infoHash) { const hash = stream.infoHash.toLowerCase(); consensusMap[hash] = (consensusMap[hash] || 0) + 1; } } const decodedUrl = decodeURIComponent(reqUrl || ''); const epMatch = decodedUrl.match(/:(?:(\d+):)?(\d+)\.json$/); const requestedEpisode = epMatch ? parseInt(epMatch[2], 10) : null; // Sorting Helpers const isBatch = (s, searchStr) => { return searchStr.includes('batch') || searchStr.includes('season pack') || searchStr.includes('complete series') || searchStr.includes('๐Ÿ“š') || s.seasonPack === true || /\b\d{2}\s*-\s*\d{2}\b/.test(searchStr) || /s\d+\s*-\s*s\d+/i.test(searchStr) || /e\d+\s*-\s*e\d+/i.test(searchStr) || /\b(?:1[1-9]|[2-9]\d|1\d{2})\s*eps?\b/.test(searchStr); }; const isBrokenMismatch = (s, searchStr) => { if (requestedEpisode && requestedEpisode >= 3 && isBatch(s, searchStr)) { if (s.fileIdx === 0 || s.fileIdx === undefined) return true; } return false; }; const getEpisodeTier = (s, searchStr) => { if (isBrokenMismatch(s, searchStr)) return 2; if (isBatch(s, searchStr)) return 1; return 0; }; const getSeaDexTier = (s, searchStr) => { if (s.seadexBest === true || searchStr.includes('[ส™แด‡sแด›]') || searchStr.includes('[best]')) return 2; if (s.seadex === true || searchStr.includes('[แด€สŸแด›]') || searchStr.includes('[alt]')) return 1; return 0; }; const getLanguageTier = (s, searchStr) => { const isMultiOrDual = searchStr.includes('multi') || searchStr.includes('dual') || searchStr.includes('แด…แดœแด€สŸ') || searchStr.includes('dual-audio') || searchStr.includes('dual audio'); const isHindi = searchStr.includes('hin') || searchStr.includes('hindi') || searchStr.includes('๐Ÿ‡ฎ๐Ÿ‡ณ'); const isEnglish = searchStr.includes('eng') || searchStr.includes('english') || searchStr.includes('๐Ÿ‡ฌ๐Ÿ‡ง') || searchStr.includes('๐Ÿ‡บ๐Ÿ‡ธ'); if (isMultiOrDual || isHindi || isEnglish) return 3; const hasJapanese = searchStr.includes('jap') || searchStr.includes('japanese') || searchStr.includes('๐Ÿ‡ฏ๐Ÿ‡ต'); const hasForeignOnly = searchStr.includes('๐Ÿ‡ฉ๐Ÿ‡ช') || searchStr.includes('german') || searchStr.includes('๐Ÿ‡ซ๐Ÿ‡ท') || searchStr.includes('french') || searchStr.includes('๐Ÿ‡ท๐Ÿ‡บ') || searchStr.includes('russian') || searchStr.includes('๐Ÿ‡ฎ๐Ÿ‡น') || searchStr.includes('italian') || searchStr.includes('๐Ÿ‡ช๐Ÿ‡ธ') || searchStr.includes('spanish'); if (hasForeignOnly && !hasJapanese && !isMultiOrDual && !isEnglish) { return 1; } return 2; }; const getStreamTypePriority = (s, seeders, searchStr) => { if (searchStr.includes('โšก')) return 4; const isTorrent = Boolean(s.infoHash); const isHttp = !isTorrent && (searchStr.includes('๐ŸŒ') || Boolean(s.url) || s.type === 'http' || s.type === 'url'); if (isHttp) return 3; if (seeders >= 5) return 2; if (seeders >= 2) return 1; return 0; }; // Pre-calculate all tiers and scores ONCE per stream (eliminates O(n log n) JSON.stringify in sort) const verifiedStreams = streams.map(stream => { const filename = stream.behaviorHints?.filename || ''; const searchString = `${stream.title || ''} ${stream.description || ''} ${stream.name || ''} ${filename}`.toLowerCase(); const health = evaluateHealth(stream, searchString); const reputation = evaluateReputation(stream, searchString); const language = evaluateLanguage(stream, searchString); // Consensus let consensusScore = 50; if (stream.infoHash) { const hash = stream.infoHash.toLowerCase(); const count = consensusMap[hash] || 1; if (count > 3) consensusScore = 100; else if (count === 3) consensusScore = 90; else if (count === 2) consensusScore = 75; } // Weighted Score let totalScore = (health.score * 0.35) + (reputation.score * 0.25) + (consensusScore * 0.15) + (language.score * 0.25); if (language.score < 20) { totalScore *= 0.1; } const finalScore = Math.round(Math.max(0, Math.min(100, totalScore))); const seeders = health.seeders; const streamPriority = getStreamTypePriority(stream, seeders, searchString); const epTier = getEpisodeTier(stream, searchString); const seaTier = getSeaDexTier(stream, searchString); const langTier = getLanguageTier(stream, searchString); stream._verification = { score: finalScore, resolutionScore: health.resolutionScore, provider: extractProvider(stream), seeders: seeders, priority: streamPriority, episodeTier: epTier, seadexTier: seaTier, langTier: langTier }; return stream; }); // Fast Sort: Comparisons on precomputed primitive integers (zero string serialization in comparator!) verifiedStreams.sort((a, b) => { const vA = a._verification; const vB = b._verification; // 1. Smart Episode Mismatch & Season Pack Tiering if (vA.episodeTier !== vB.episodeTier) { return vA.episodeTier - vB.episodeTier; } // 2. SeaDex Protection: Best [2] > Alt [1] > Standard [0] if (vB.seadexTier !== vA.seadexTier) { return vB.seadexTier - vA.seadexTier; } // 3. Resolution Priority (1080p > 720p) if (vB.resolutionScore !== vA.resolutionScore) { return vB.resolutionScore - vA.resolutionScore; } // 4. Stream Type Priority (HTTP first, then P2P by seeders) if (vB.priority !== vA.priority) { return vB.priority - vA.priority; } // 5. Seeders Descending for P2P & Language Demotion if (vA.priority === 2 || vA.priority === 1) { if (vA.langTier !== vB.langTier && (vA.langTier === 1 || vB.langTier === 1)) { return vB.langTier - vA.langTier; } if (vB.seeders !== vA.seeders) { return vB.seeders - vA.seeders; } if (vB.langTier !== vA.langTier) { return vB.langTier - vA.langTier; } } else { if (vB.langTier !== vA.langTier) { return vB.langTier - vA.langTier; } } // 6. Tie-breaker: Fall back to overall score return vB.score - vA.score; }); // Record provider stats for the top overall stream if (verifiedStreams.length > 0) { recordProviderStats(verifiedStreams[0], true); } const seenHashes = new Set(); const seenHttpKeys = new Set(); const categorized = { '1080p_http': [], '1080p_p2p': [], '720p_http': [], '720p_p2p': [], 'other': [] }; for (let i = 0; i < verifiedStreams.length; i++) { const stream = verifiedStreams[i]; if (i > 0) { recordProviderStats(stream, false); } const isTorrent = Boolean(stream.infoHash); const filename = stream.behaviorHints?.filename || ''; const searchStr = `${stream.title || ''} ${stream.description || ''} ${stream.name || ''} ${filename}`.toLowerCase(); const isHttp = !isTorrent && (searchStr.includes('๐ŸŒ') || Boolean(stream.url) || stream.type === 'http' || stream.type === 'url'); // 1. Deduplication if (isTorrent) { const hash = stream.infoHash.toLowerCase(); if (seenHashes.has(hash)) continue; seenHashes.add(hash); } else if (isHttp) { const provider = extractProvider(stream); const size = stream.behaviorHints?.videoSize || ''; const fname = filename || stream.title || ''; const httpKey = `${provider}|${size}|${fname}`.toLowerCase(); if (seenHttpKeys.has(httpKey)) continue; seenHttpKeys.add(httpKey); } // 2. Classify into tiers based on verified resolution and stream type const resScore = stream._verification?.resolutionScore || 50; if (resScore >= 80) { if (isHttp) categorized['1080p_http'].push(stream); else categorized['1080p_p2p'].push(stream); } else if (resScore >= 60) { if (isHttp) categorized['720p_http'].push(stream); else categorized['720p_p2p'].push(stream); } else { categorized['other'].push(stream); } } const finalStreams = [ ...categorized['1080p_http'], ...categorized['1080p_p2p'], ...categorized['720p_http'], ...categorized['720p_p2p'], ...categorized['other'] ]; if (finalStreams.length > 0) { const summary = finalStreams.slice(0, 25).map(s => { const prov = extractProvider(s); const isTorr = Boolean(s.infoHash); const seeders = isTorr ? s._verification?.seeders : 'HTTP'; const res = (s._verification?.resolutionScore >= 80) ? '1080p' : '720p'; return `[${res} ${isTorr ? `P2P(${seeders}s)` : 'HTTP'} ${prov}]`; }); console.log(`[Proxy Sort Debug] Top streams after tiering:\n ${summary.join('\n ')}`); } return finalStreams.slice(0, 80); }