vanseleem commited on
Commit
649be4b
·
verified ·
1 Parent(s): 689310e

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +135 -200
index.js CHANGED
@@ -1,200 +1,135 @@
1
- const express = require('express');
2
- const fetch = require('node-fetch');
3
-
4
- const app = express();
5
- const PORT = process.env.PORT || 7860;
6
-
7
- app.use((req, res, next) => {
8
- res.header('Access-Control-Allow-Origin', '*');
9
- res.header('Access-Control-Allow-Headers', '*');
10
- res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
11
- if (req.method === 'OPTIONS') return res.sendStatus(200);
12
- next();
13
- });
14
-
15
- const UPSTREAM_URLS = [
16
- 'https://torrentio.strem.fun',
17
- 'https://thepiratebay-plus.strem.fun/'
18
- ];
19
-
20
- const MIN_SEEDERS = 1;
21
- const ALLOWED_QUALITIES = ['480p', '576p', '720p', '1080p', 'webrip', 'webdl', 'bluray'];
22
- const MAX_SIZE_GB = 4;
23
-
24
- const SOURCE_CONFIG = [
25
- { name: 'yts', display: 'YTS', order: 1 },
26
- { name: 'yify', display: 'YTS', order: 1 },
27
- { name: 'eztv', display: 'EZTV', order: 2 },
28
- { name: 'nyaasi', display: 'Nyaa', order: 3 },
29
- { name: 'thepiratebay', display: 'TPB', order: 4 },
30
- { name: 'piratebay', display: 'TPB', order: 4 },
31
- { name: 'the piratebay', display: 'TPB', order: 4 },
32
- { name: 'tpb', display: 'TPB', order: 4 },
33
- { name: '1337x', display: '1337x', order: 5 },
34
- { name: 'torrentgalaxy', display: 'TGx', order: 6 },
35
- { name: 'rarbg', display: 'RARBG', order: 7 },
36
- { name: 'rutor', display: 'Rutor', order: 8 },
37
- { name: 'rutracker', display: 'Rutracker', order: 9 }
38
- ];
39
-
40
- const QUALITY_RANK = {
41
- '1080p': 1,
42
- '720p': 2,
43
- 'webdl': 3,
44
- 'webrip': 4,
45
- '576p': 5,
46
- '480p': 6,
47
- 'bluray': 7
48
- };
49
-
50
- const LINKS_PER_QUALITY = 5;
51
-
52
- function getQuality(title = '') {
53
- const lower = title.toLowerCase();
54
- if (lower.includes('1080p')) return '1080p';
55
- if (lower.includes('720p')) return '720p';
56
- if (lower.includes('480p')) return '480p';
57
- if (lower.includes('576p')) return '576p';
58
- if (lower.includes('webrip')) return 'webrip';
59
- if (lower.includes('webdl') || lower.includes('web-dl')) return 'webdl';
60
- if (lower.includes('bluray')) return 'bluray';
61
- return null;
62
- }
63
-
64
- function getSeeders(stream) {
65
- if (stream.behaviorHints?.seeders) return stream.behaviorHints.seeders;
66
- const match = stream.title?.match(/👤\s*(\d+)/);
67
- return match ? parseInt(match[1], 10) : 0;
68
- }
69
-
70
- function getSizeInGB(stream) {
71
- const title = stream.title || '';
72
- const sizeMatch = title.match(/💾\s*([\d.]+)\s*(GB|MB)/i) ||
73
- title.match(/([\d.]+)\s*(GB|MB)/i);
74
- if (!sizeMatch) return null;
75
-
76
- const value = parseFloat(sizeMatch[1]);
77
- const unit = sizeMatch[2].toUpperCase();
78
-
79
- if (unit === 'GB') return value;
80
- if (unit === 'MB') return value / 1024;
81
- return null;
82
- }
83
-
84
- function detectSource(title = '') {
85
- const lower = title.toLowerCase();
86
- for (const src of SOURCE_CONFIG) {
87
- if (lower.includes(src.name)) return src.name;
88
- }
89
- return null;
90
- }
91
-
92
- function getSourceOrder(sourceName) {
93
- const config = SOURCE_CONFIG.find(c => c.name === sourceName);
94
- return config ? config.order : 999;
95
- }
96
-
97
- function filterStream(stream) {
98
- const title = stream.title || '';
99
- if (!stream.url && !stream.infoHash) return false;
100
-
101
- const quality = getQuality(title);
102
- if (!ALLOWED_QUALITIES.includes(quality)) return false;
103
-
104
- if (getSeeders(stream) < MIN_SEEDERS) return false;
105
-
106
- if (!detectSource(title)) return false;
107
-
108
- const sizeGB = getSizeInGB(stream);
109
- if (sizeGB !== null && sizeGB > MAX_SIZE_GB) return false;
110
-
111
- return true;
112
- }
113
-
114
- async function fetchUpstream(type, id) {
115
- for (const baseUrl of UPSTREAM_URLS) {
116
- try {
117
- const controller = new AbortController();
118
- const timeout = setTimeout(() => controller.abort(), 8000);
119
- const res = await fetch(`${baseUrl}/stream/${type}/${id}.json`, { signal: controller.signal });
120
- clearTimeout(timeout);
121
- if (res.ok) {
122
- const data = await res.json();
123
- if (data.streams?.length) return data.streams;
124
- }
125
- } catch (e) {}
126
- }
127
- return [];
128
- }
129
-
130
- function sortAndGroup(streams) {
131
- const filtered = streams.filter(filterStream);
132
-
133
- const byQuality = {};
134
- filtered.forEach(stream => {
135
- const quality = getQuality(stream.title);
136
- if (!byQuality[quality]) byQuality[quality] = [];
137
- byQuality[quality].push(stream);
138
- });
139
-
140
- for (const quality in byQuality) {
141
- byQuality[quality].sort((a, b) => {
142
- const sourceA = detectSource(a.title);
143
- const sourceB = detectSource(b.title);
144
- const orderA = getSourceOrder(sourceA);
145
- const orderB = getSourceOrder(sourceB);
146
- if (orderA !== orderB) return orderA - orderB;
147
- return getSeeders(b) - getSeeders(a);
148
- });
149
- }
150
-
151
- const result = [];
152
- const sortedQualities = Object.keys(byQuality).sort((a, b) => {
153
- const rankA = QUALITY_RANK[a] || 999;
154
- const rankB = QUALITY_RANK[b] || 999;
155
- return rankA - rankB;
156
- });
157
-
158
- sortedQualities.forEach(quality => {
159
- const topLinks = byQuality[quality].slice(0, LINKS_PER_QUALITY);
160
- result.push(...topLinks);
161
- });
162
-
163
- return result;
164
- }
165
-
166
- app.get('/manifest.json', (req, res) => {
167
- res.json({
168
- id: 'org.ghostream.platinum',
169
- name: 'Ghostream 🚀',
170
- description: 'Stream Everything You Want\nMade With 🤍 By Van',
171
- version: '7.3.0',
172
- resources: ['stream'],
173
- types: ['movie', 'series'],
174
- idPrefixes: ['tt'],
175
- catalogs: []
176
- });
177
- });
178
-
179
- app.get('/stream/:type/:id.json', async (req, res) => {
180
- const { type, id } = req.params;
181
- try {
182
- const streams = await fetchUpstream(type, id);
183
- const sorted = sortAndGroup(streams);
184
- res.json({ streams: sorted });
185
- } catch (e) {
186
- res.json({ streams: [] });
187
- }
188
- });
189
-
190
- app.get('/debug/:type/:id.json', async (req, res) => {
191
- const { type, id } = req.params;
192
- try {
193
- const streams = await fetchUpstream(type, id);
194
- res.json({ streams });
195
- } catch (e) {
196
- res.status(500).json({ error: e.message });
197
- }
198
- });
199
-
200
- app.listen(PORT, () => console.log(`Ghostream 🚀 on ${PORT}`));
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.responses import JSONResponse, FileResponse
3
+ import httpx
4
+ import re
5
+ import os
6
+ from typing import List, Dict, Any
7
+
8
+ app = FastAPI()
9
+
10
+ UPSTREAM_URLS = [
11
+ "https://torrentio.strem.fun",
12
+ "https://thepiratebay-plus.strem.fun",
13
+ ]
14
+
15
+ # Quality order: highest first. Exclude 4K, screeners, cam, 3D.
16
+ QUALITY_ORDER = [
17
+ "1080p", "720p", "576p", "480p", "360p", "240p",
18
+ "1080", "720", "480", "360",
19
+ "hdrip", "webrip", "brrip", "hdtv", "bluray", "dvdrip",
20
+ # all others will be appended at the end (lowest)
21
+ ]
22
+
23
+ # Blacklist keywords (case‑insensitive)
24
+ EXCLUDE_KEYWORDS = ["4k", "2160p", "screenr", "screener", "cam", "3d"]
25
+
26
+ MAX_MOVIE_SIZE_GB = 4
27
+ MAX_SERIES_SIZE_GB = 1.5
28
+ LINKS_PER_QUALITY = 5
29
+
30
+ def parse_size_to_gb(size_str: str) -> float:
31
+ """Convert size string like '2.5 GB' or '800 MB' to GB float."""
32
+ if not size_str:
33
+ return None
34
+ match = re.match(r"([\d.]+)\s*(GB|MB|TB)", size_str, re.IGNORECASE)
35
+ if not match:
36
+ return None
37
+ value = float(match.group(1))
38
+ unit = match.group(2).upper()
39
+ if unit == "TB":
40
+ return value * 1024
41
+ elif unit == "GB":
42
+ return value
43
+ elif unit == "MB":
44
+ return value / 1024
45
+ return None
46
+
47
+ def extract_quality(name: str) -> str:
48
+ """Extract a quality label like '1080p' from a stream name."""
49
+ # Try pattern like 1080p, 720p, etc.
50
+ m = re.search(r"\b(\d{3,4}p)\b", name)
51
+ if m:
52
+ return m.group(1)
53
+ # Fallback: look for known labels
54
+ name_lower = name.lower()
55
+ for q in QUALITY_ORDER:
56
+ if q in name_lower:
57
+ return q
58
+ # If nothing found, treat as 'other'
59
+ return "other"
60
+
61
+ def quality_sort_key(quality: str) -> int:
62
+ """Return a sortable number for the quality."""
63
+ try:
64
+ return QUALITY_ORDER.index(quality)
65
+ except ValueError:
66
+ return len(QUALITY_ORDER) # push unknown to the end
67
+
68
+ def filter_and_sort(streams: List[Dict], is_series: bool) -> List[Dict]:
69
+ """Apply size, quality exclusion, per‑quality limit, and sorting."""
70
+ max_size_gb = MAX_SERIES_SIZE_GB if is_series else MAX_MOVIE_SIZE_GB
71
+ filtered = []
72
+
73
+ for s in streams:
74
+ # Size filter
75
+ size_str = s.get("title", "").split("\n")[0] # typical format: '📦 1.2 GB' at start
76
+ size_match = re.search(r"💾\s*([\d.]+)\s*(GB|MB|TB)", s.get("title", ""), re.IGNORECASE)
77
+ if not size_match:
78
+ # try other common pattern: 'size: X GB'
79
+ size_match = re.search(r"size:\s*([\d.]+)\s*(GB|MB|TB)", s.get("title", ""), re.IGNORECASE)
80
+ size_gb = parse_size_to_gb(size_match.group(0) if size_match else None)
81
+
82
+ if size_gb is not None and size_gb > max_size_gb:
83
+ continue # too big
84
+
85
+ # Quality exclusion
86
+ quality = extract_quality(s.get("title") or s.get("name", ""))
87
+ if any(excl in (s.get("title") + (s.get("name") or "")).lower() for excl in EXCLUDE_KEYWORDS):
88
+ continue # blacklisted
89
+
90
+ s["_quality"] = quality
91
+ filtered.append(s)
92
+
93
+ # Group by quality, keep only LINKS_PER_QUALITY
94
+ groups = {}
95
+ for s in filtered:
96
+ q = s["_quality"]
97
+ groups.setdefault(q, []).append(s)
98
+ limited = []
99
+ for q, streams_list in groups.items():
100
+ limited.extend(streams_list[:LINKS_PER_QUALITY])
101
+
102
+ # Sort by quality (1080p first, etc.)
103
+ limited.sort(key=lambda x: quality_sort_key(x["_quality"]))
104
+
105
+ # Remove the temporary quality field
106
+ for s in limited:
107
+ s.pop("_quality", None)
108
+ return limited
109
+
110
+ @app.get("/manifest.json")
111
+ async def manifest():
112
+ return FileResponse("manifest.json")
113
+
114
+ @app.get("/stream/{type}/{id}.json")
115
+ async def stream(type: str, id: str, request: Request):
116
+ params = dict(request.query_params)
117
+ merged = []
118
+ async with httpx.AsyncClient(timeout=15) as client:
119
+ for base_url in UPSTREAM_URLS:
120
+ url = f"{base_url}/stream/{type}/{id}.json"
121
+ try:
122
+ resp = await client.get(url, params=params)
123
+ if resp.status_code == 200:
124
+ data = resp.json()
125
+ merged.extend(data.get("streams", []))
126
+ except Exception:
127
+ pass # upstream fails silently
128
+
129
+ is_series = (type == "series")
130
+ final_streams = filter_and_sort(merged, is_series)
131
+ return JSONResponse({"streams": final_streams})
132
+
133
+ if __name__ == "__main__":
134
+ import uvicorn
135
+ uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))