ghostream / index.js
vanseleem's picture
Update index.js
bc5d87d verified
Raw History Blame Contribute Delete
5.91 kB
const express = require('express');
const fetch = require('node-fetch');
const app = express();
const PORT = process.env.PORT || 7860;
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', '*');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
if (req.method === 'OPTIONS') return res.sendStatus(200);
next();
});
const UPSTREAM_URLS = [
'https://torrentio.strem.fun',
'https://torrentio.stremio.my.id'
];
const MIN_SEEDERS = 1;
const ALLOWED_QUALITIES = ['480p', '576p', '720p', '1080p', 'webrip', 'webdl', 'bluray'];
const MAX_SIZE_GB_MOVIE = 4;
const MAX_SIZE_GB_SERIES = 1.5;
const SOURCE_CONFIG = [
{ name: 'yts', display: 'YTS', order: 1 },
{ name: 'yify', display: 'YTS', order: 1 },
{ name: 'eztv', display: 'EZTV', order: 2 },
{ name: 'nyaasi', display: 'Nyaa', order: 3 },
{ name: 'thepiratebay', display: 'TPB', order: 4 },
{ name: 'piratebay', display: 'TPB', order: 4 },
{ name: 'the piratebay', display: 'TPB', order: 4 },
{ name: 'tpb', display: 'TPB', order: 4 },
{ name: '1337x', display: '1337x', order: 5 },
{ name: 'torrentgalaxy', display: 'TGx', order: 6 },
{ name: 'rarbg', display: 'RARBG', order: 7 },
{ name: 'rutor', display: 'Rutor', order: 8 },
{ name: 'rutracker', display: 'Rutracker', order: 9 }
];
const QUALITY_RANK = {
'1080p': 1,
'720p': 2,
'webdl': 3,
'webrip': 4,
'576p': 5,
'480p': 6,
'bluray': 7
};
const LINKS_PER_QUALITY = 5;
function getQuality(title = '') {
const lower = title.toLowerCase();
if (lower.includes('1080p')) return '1080p';
if (lower.includes('720p')) return '720p';
if (lower.includes('480p')) return '480p';
if (lower.includes('576p')) return '576p';
if (lower.includes('webrip')) return 'webrip';
if (lower.includes('webdl') || lower.includes('web-dl')) return 'webdl';
if (lower.includes('bluray')) return 'bluray';
return null;
}
function getSeeders(stream) {
if (stream.behaviorHints?.seeders) return stream.behaviorHints.seeders;
const match = stream.title?.match(/πŸ‘€\s*(\d+)/);
return match ? parseInt(match[1], 10) : 0;
}
function getSizeInGB(stream) {
const title = stream.title || '';
const sizeMatch = title.match(/πŸ’Ύ\s*([\d.]+)\s*(GB|MB)/i) ||
title.match(/([\d.]+)\s*(GB|MB)/i);
if (!sizeMatch) return null;
const value = parseFloat(sizeMatch[1]);
const unit = sizeMatch[2].toUpperCase();
if (unit === 'GB') return value;
if (unit === 'MB') return value / 1024;
return null;
}
function detectSource(title = '') {
const lower = title.toLowerCase();
for (const src of SOURCE_CONFIG) {
if (lower.includes(src.name)) return src.name;
}
return null;
}
function getSourceOrder(sourceName) {
const config = SOURCE_CONFIG.find(c => c.name === sourceName);
return config ? config.order : 999;
}
function filterStream(stream, type = 'movie') {
const title = stream.title || '';
if (!stream.url && !stream.infoHash) return false;
const quality = getQuality(title);
if (!ALLOWED_QUALITIES.includes(quality)) return false;
if (getSeeders(stream) < MIN_SEEDERS) return false;
if (!detectSource(title)) return false;
const maxSize = type === 'series' ? MAX_SIZE_GB_SERIES : MAX_SIZE_GB_MOVIE;
const sizeGB = getSizeInGB(stream);
if (sizeGB !== null && sizeGB > maxSize) return false;
return true;
}
async function fetchUpstream(type, id) {
for (const baseUrl of UPSTREAM_URLS) {
try {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 8000);
const res = await fetch(`${baseUrl}/stream/${type}/${id}.json`, { signal: controller.signal });
clearTimeout(timeout);
if (res.ok) {
const data = await res.json();
if (data.streams?.length) return data.streams;
}
} catch (e) {}
}
return [];
}
function sortAndGroup(streams, type = 'movie') {
const filtered = streams.filter(s => filterStream(s, type));
const byQuality = {};
filtered.forEach(stream => {
const quality = getQuality(stream.title);
if (!byQuality[quality]) byQuality[quality] = [];
byQuality[quality].push(stream);
});
for (const quality in byQuality) {
byQuality[quality].sort((a, b) => {
const sourceA = detectSource(a.title);
const sourceB = detectSource(b.title);
const orderA = getSourceOrder(sourceA);
const orderB = getSourceOrder(sourceB);
if (orderA !== orderB) return orderA - orderB;
return getSeeders(b) - getSeeders(a);
});
}
const result = [];
const sortedQualities = Object.keys(byQuality).sort((a, b) => {
const rankA = QUALITY_RANK[a] || 999;
const rankB = QUALITY_RANK[b] || 999;
return rankA - rankB;
});
sortedQualities.forEach(quality => {
const topLinks = byQuality[quality].slice(0, LINKS_PER_QUALITY);
result.push(...topLinks);
});
return result;
}
app.get('/manifest.json', (req, res) => {
res.json({
id: 'org.ghostream.platinum',
name: 'Ghostream πŸš€',
description: 'Stream Everything You Want\nMade With 🀍 By Van',
version: '7.3.0',
resources: ['stream'],
types: ['movie', 'series'],
idPrefixes: ['tt'],
catalogs: []
});
});
app.get('/stream/:type/:id.json', async (req, res) => {
const { type, id } = req.params;
try {
const streams = await fetchUpstream(type, id);
const sorted = sortAndGroup(streams, type);
res.json({ streams: sorted });
} catch (e) {
res.json({ streams: [] });
}
});
app.get('/debug/:type/:id.json', async (req, res) => {
const { type, id } = req.params;
try {
const streams = await fetchUpstream(type, id);
res.json({ streams });
} catch (e) {
res.status(500).json({ error: e.message });
}
});
app.listen(PORT, () => console.log(`Ghostream πŸš€ on ${PORT}`));