AIOStreams-Mega / proxy /verification.js
site0230's picture
Update proxy/verification.js
5fa5306 verified
Raw History Blame Contribute Delete
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);
}