Spaces:
Running
Running
Download proxy/verification.js from site0230/AIOStreams-Mega: direct link, hf CLI and curl.
- Browser
- Download file 23.6 kB
-
https://huggingface.co/spaces/site0230/AIOStreams-Mega/resolve/main/proxy/verification.js
- Command line
-
hf download hf://spaces/site0230/AIOStreams-Mega/proxy/verification.js
-
curl -L -o verification.js https://huggingface.co/spaces/site0230/AIOStreams-Mega/resolve/main/proxy/verification.js
23.6 kB
| 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); | |
| } | |