vanseleem commited on
Commit
3618b35
Β·
verified Β·
1 Parent(s): 7e50a6b

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +163 -82
index.js CHANGED
@@ -1,121 +1,202 @@
1
  const express = require('express');
2
- const axios = require('axios');
3
- const app = express();
4
 
 
5
  const PORT = process.env.PORT || 7860;
6
 
7
- // Quality order: highest first
8
- const QUALITY_ORDER = [
9
- '1080p', '720p', '576p', '480p', '360p', '240p',
10
- '1080', '720', '480', '360',
11
- 'hdrip', 'webrip', 'brrip', 'hdtv', 'bluray', 'dvdrip',
 
 
 
 
 
 
12
  ];
13
 
14
- // Quality keywords to exclude (4K, screener, CAM, 3D)
15
- const EXCLUDE_KEYWORDS = ['4k', '2160p', 'screenr', 'screener', 'cam', '3d'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
- // Maximum 5 torrent links per quality tier
18
  const LINKS_PER_QUALITY = 5;
19
 
20
- // Extract quality from stream title
21
- function extractQuality(title) {
22
- const match = title.match(/\b(\d{3,4}p)\b/i);
23
- if (match) return match[1].toLowerCase();
24
  const lower = title.toLowerCase();
25
- return QUALITY_ORDER.find(q => lower.includes(q)) || 'other';
 
 
 
 
 
 
 
26
  }
27
 
28
- // Sort quality: higher = earlier in list
29
- function qualitySortKey(quality) {
30
- const idx = QUALITY_ORDER.indexOf(quality);
31
- return idx !== -1 ? idx : QUALITY_ORDER.length;
32
  }
33
 
34
- // Filter blacklisted qualities, group by quality, limit per quality, sort
35
- function filterAndArrange(streams) {
36
- // Remove blacklisted qualities
37
- const filtered = streams.filter(s => {
38
- const title = (s.title || s.name || '').toLowerCase();
39
- return !EXCLUDE_KEYWORDS.some(kw => title.includes(kw));
40
- });
 
 
 
 
 
 
41
 
42
- // Group streams by quality
43
- const groups = {};
44
- for (const s of filtered) {
45
- const q = extractQuality(s.title || s.name || '');
46
- if (!groups[q]) groups[q] = [];
47
- groups[q].push(s);
48
  }
 
 
49
 
50
- // Limit to 5 per quality and flatten
51
- const result = [];
52
- for (const [q, items] of Object.entries(groups)) {
53
- result.push(...items.slice(0, LINKS_PER_QUALITY));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  }
 
 
55
 
56
- // Sort from highest quality to lowest
57
- result.sort((a, b) => {
58
- const qA = extractQuality(a.title || a.name || '');
59
- const qB = extractQuality(b.title || b.name || '');
60
- return qualitySortKey(qA) - qualitySortKey(qB);
 
 
 
61
  });
62
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  return result;
64
  }
65
 
66
- // Serve manifest.json
67
  app.get('/manifest.json', (req, res) => {
68
  res.json({
69
- id: 'org.filterproxy.streams',
70
- name: 'Filter Proxy',
71
- description: 'Merges Torrentio + TPB+, filters by size and quality, sorts 1080p→lower, excludes 4K/CAM/3D',
72
- version: '1.0.0',
73
  resources: ['stream'],
74
  types: ['movie', 'series'],
75
  idPrefixes: ['tt'],
76
- catalogs: [],
77
  });
78
  });
79
 
80
- // Handle stream requests
81
  app.get('/stream/:type/:id.json', async (req, res) => {
82
  const { type, id } = req.params;
83
- const queryString = req.url.split('?')[1] || '';
84
-
85
  try {
86
- // Fetch from both upstream addons with Torrentio size limits applied
87
- const results = await Promise.allSettled([
88
- axios.get(
89
- `https://torrentio.strem.fun/stream/${type}/${id}.json?${queryString}&maxSize=4GB,1.5GB`,
90
- { timeout: 15000 }
91
- ),
92
- axios.get(
93
- `https://thepiratebay-plus.strem.fun/stream/${type}/${id}.json?${queryString}`,
94
- { timeout: 15000 }
95
- ),
96
- ]);
97
-
98
- // Merge all streams
99
- let allStreams = [];
100
- for (const r of results) {
101
- if (r.status === 'fulfilled' && r.value.data?.streams) {
102
- allStreams = allStreams.concat(r.value.data.streams);
103
- }
104
- }
105
-
106
- // Apply filtering and sorting
107
- const finalStreams = filterAndArrange(allStreams);
108
- res.json({ streams: finalStreams });
109
- } catch (err) {
110
- res.status(500).json({ error: 'Failed to fetch streams' });
111
  }
112
  });
113
 
114
- // Health check
115
- app.get('/', (req, res) => {
116
- res.send('Filter Proxy Running. Add /manifest.json to Stremio.');
 
 
 
 
 
117
  });
118
 
119
- app.listen(PORT, '0.0.0.0', () => {
120
- console.log(`Filter proxy running on port ${PORT}`);
121
- });
 
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://torrentio.stremio.my.id'
18
  ];
19
 
20
+ const MIN_SEEDERS = 1;
21
+ const ALLOWED_QUALITIES = ['480p', '576p', '720p', '1080p', 'webrip', 'webdl', 'bluray'];
22
+ const MAX_SIZE_GB_MOVIE = 4;
23
+ const MAX_SIZE_GB_SERIES = 1.5;
24
+
25
+ const SOURCE_CONFIG = [
26
+ { name: 'yts', display: 'YTS', order: 1 },
27
+ { name: 'yify', display: 'YTS', order: 1 },
28
+ { name: 'eztv', display: 'EZTV', order: 2 },
29
+ { name: 'nyaasi', display: 'Nyaa', order: 3 },
30
+ { name: 'thepiratebay', display: 'TPB', order: 4 },
31
+ { name: 'piratebay', display: 'TPB', order: 4 },
32
+ { name: 'the piratebay', display: 'TPB', order: 4 },
33
+ { name: 'tpb', display: 'TPB', order: 4 },
34
+ { name: '1337x', display: '1337x', order: 5 },
35
+ { name: 'torrentgalaxy', display: 'TGx', order: 6 },
36
+ { name: 'rarbg', display: 'RARBG', order: 7 },
37
+ { name: 'rutor', display: 'Rutor', order: 8 },
38
+ { name: 'rutracker', display: 'Rutracker', order: 9 }
39
+ ];
40
+
41
+ const QUALITY_RANK = {
42
+ '1080p': 1,
43
+ '720p': 2,
44
+ 'webdl': 3,
45
+ 'webrip': 4,
46
+ '576p': 5,
47
+ '480p': 6,
48
+ 'bluray': 7
49
+ };
50
 
 
51
  const LINKS_PER_QUALITY = 5;
52
 
53
+ function getQuality(title = '') {
 
 
 
54
  const lower = title.toLowerCase();
55
+ if (lower.includes('1080p')) return '1080p';
56
+ if (lower.includes('720p')) return '720p';
57
+ if (lower.includes('480p')) return '480p';
58
+ if (lower.includes('576p')) return '576p';
59
+ if (lower.includes('webrip')) return 'webrip';
60
+ if (lower.includes('webdl') || lower.includes('web-dl')) return 'webdl';
61
+ if (lower.includes('bluray')) return 'bluray';
62
+ return null;
63
  }
64
 
65
+ function getSeeders(stream) {
66
+ if (stream.behaviorHints?.seeders) return stream.behaviorHints.seeders;
67
+ const match = stream.title?.match(/πŸ‘€\s*(\d+)/);
68
+ return match ? parseInt(match[1], 10) : 0;
69
  }
70
 
71
+ function getSizeInGB(stream) {
72
+ const title = stream.title || '';
73
+ const sizeMatch = title.match(/πŸ’Ύ\s*([\d.]+)\s*(GB|MB)/i) ||
74
+ title.match(/([\d.]+)\s*(GB|MB)/i);
75
+ if (!sizeMatch) return null;
76
+
77
+ const value = parseFloat(sizeMatch[1]);
78
+ const unit = sizeMatch[2].toUpperCase();
79
+
80
+ if (unit === 'GB') return value;
81
+ if (unit === 'MB') return value / 1024;
82
+ return null;
83
+ }
84
 
85
+ function detectSource(title = '') {
86
+ const lower = title.toLowerCase();
87
+ for (const src of SOURCE_CONFIG) {
88
+ if (lower.includes(src.name)) return src.name;
 
 
89
  }
90
+ return null;
91
+ }
92
 
93
+ function getSourceOrder(sourceName) {
94
+ const config = SOURCE_CONFIG.find(c => c.name === sourceName);
95
+ return config ? config.order : 999;
96
+ }
97
+
98
+ function filterStream(stream, type = 'movie') {
99
+ const title = stream.title || '';
100
+ if (!stream.url && !stream.infoHash) return false;
101
+
102
+ const quality = getQuality(title);
103
+ if (!ALLOWED_QUALITIES.includes(quality)) return false;
104
+
105
+ if (getSeeders(stream) < MIN_SEEDERS) return false;
106
+
107
+ if (!detectSource(title)) return false;
108
+
109
+ const maxSize = type === 'series' ? MAX_SIZE_GB_SERIES : MAX_SIZE_GB_MOVIE;
110
+ const sizeGB = getSizeInGB(stream);
111
+ if (sizeGB !== null && sizeGB > maxSize) return false;
112
+
113
+ return true;
114
+ }
115
+
116
+ async function fetchUpstream(type, id) {
117
+ for (const baseUrl of UPSTREAM_URLS) {
118
+ try {
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), 8000);
121
+ const res = await fetch(`${baseUrl}/stream/${type}/${id}.json`, { signal: controller.signal });
122
+ clearTimeout(timeout);
123
+ if (res.ok) {
124
+ const data = await res.json();
125
+ if (data.streams?.length) return data.streams;
126
+ }
127
+ } catch (e) {}
128
  }
129
+ return [];
130
+ }
131
 
132
+ function sortAndGroup(streams, type = 'movie') {
133
+ const filtered = streams.filter(s => filterStream(s, type));
134
+
135
+ const byQuality = {};
136
+ filtered.forEach(stream => {
137
+ const quality = getQuality(stream.title);
138
+ if (!byQuality[quality]) byQuality[quality] = [];
139
+ byQuality[quality].push(stream);
140
  });
141
+
142
+ for (const quality in byQuality) {
143
+ byQuality[quality].sort((a, b) => {
144
+ const sourceA = detectSource(a.title);
145
+ const sourceB = detectSource(b.title);
146
+ const orderA = getSourceOrder(sourceA);
147
+ const orderB = getSourceOrder(sourceB);
148
+ if (orderA !== orderB) return orderA - orderB;
149
+ return getSeeders(b) - getSeeders(a);
150
+ });
151
+ }
152
+
153
+ const result = [];
154
+ const sortedQualities = Object.keys(byQuality).sort((a, b) => {
155
+ const rankA = QUALITY_RANK[a] || 999;
156
+ const rankB = QUALITY_RANK[b] || 999;
157
+ return rankA - rankB;
158
+ });
159
+
160
+ sortedQualities.forEach(quality => {
161
+ const topLinks = byQuality[quality].slice(0, LINKS_PER_QUALITY);
162
+ result.push(...topLinks);
163
+ });
164
+
165
  return result;
166
  }
167
 
 
168
  app.get('/manifest.json', (req, res) => {
169
  res.json({
170
+ id: 'org.ghostream.platinum',
171
+ name: 'Ghostream πŸš€',
172
+ description: 'Stream Everything You Want\nMade With 🀍 By Van',
173
+ version: '7.3.0',
174
  resources: ['stream'],
175
  types: ['movie', 'series'],
176
  idPrefixes: ['tt'],
177
+ catalogs: []
178
  });
179
  });
180
 
 
181
  app.get('/stream/:type/:id.json', async (req, res) => {
182
  const { type, id } = req.params;
 
 
183
  try {
184
+ const streams = await fetchUpstream(type, id);
185
+ const sorted = sortAndGroup(streams, type);
186
+ res.json({ streams: sorted });
187
+ } catch (e) {
188
+ res.json({ streams: [] });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  }
190
  });
191
 
192
+ app.get('/debug/:type/:id.json', async (req, res) => {
193
+ const { type, id } = req.params;
194
+ try {
195
+ const streams = await fetchUpstream(type, id);
196
+ res.json({ streams });
197
+ } catch (e) {
198
+ res.status(500).json({ error: e.message });
199
+ }
200
  });
201
 
202
+ app.listen(PORT, () => console.log(`Ghostream πŸš€ on ${PORT}`));