Download index.js from vanseleem/ghostream: direct link, hf CLI and curl.
- Browser
- Download file 5.91 kB
-
https://huggingface.co/spaces/vanseleem/ghostream/resolve/main/index.js
- Command line
-
hf download hf://spaces/vanseleem/ghostream/index.js
-
curl -L -o index.js https://huggingface.co/spaces/vanseleem/ghostream/resolve/main/index.js
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}`)); |