vanseleem commited on
Commit
f93fcc1
Β·
verified Β·
1 Parent(s): 75975d4

Create Torrentio + torrents db only

Browse files
Files changed (1) hide show
  1. Torrentio + torrents db only +230 -0
Torrentio + torrents db only ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use strict';
2
+ const express = require('express');
3
+ const axios = require('axios');
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 TMDB_KEY = '83d364331c40bfbe29858aeed82f45cc';
16
+ const UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36';
17
+ const TORRENTIO_BASE = 'https://torrentsdb.com';
18
+ const TORRENTIO_STREM = 'https://torrentio.strem.fun';
19
+
20
+ const MAX_SIZE_GB_MOVIE = 4;
21
+ const MAX_SIZE_GB_SERIES = 1.5;
22
+ const LINKS_PER_QUALITY = 5;
23
+ const ALLOWED_QUALITIES = ['4k', '1080p', '720p', '576p', '480p', 'webrip'];
24
+ const QUALITY_RANK = {
25
+ '1080p': 1, '720p': 2, '576p': 3, '480p': 4,
26
+ '4k': 5, '2160p': 5, 'webrip': 6, 'webdl': 6,
27
+ };
28
+ const PRIORITY_PROVIDERS = ['yts', 'knaben', 'torrentsdb', 'eztv', 'nyaasi', 'thepiratebay'];
29
+ const ALLOWED_PROVIDERS = [
30
+ 'yts', 'knaben', 'thepiratesbay', 'thepiratebay', 'eztv', 'torrentcsv',
31
+ 'nyaa', 'nyaasi', 'limetorrent', 'kickasstorrents', 'animetosho', 'tokyotosho',
32
+ ];
33
+
34
+ const TR = [
35
+ 'udp://tracker.opentrackr.org:1337/announce',
36
+ 'udp://open.stealth.si:80/announce',
37
+ 'udp://open.demonii.com:1337/announce',
38
+ 'udp://tracker.torrent.eu.org:451/announce',
39
+ 'udp://p4p.arenabg.com:1337/announce',
40
+ 'udp://exodus.desync.com:6969/announce',
41
+ 'udp://tracker.openbittorrent.com:6969/announce',
42
+ 'udp://tracker.dler.org:6969/announce',
43
+ 'https://tracker.moeblog.cn:443/announce',
44
+ 'https://tracker.zhuqiy.com:443/announce',
45
+ ].map(t => '&tr=' + encodeURIComponent(t)).join('');
46
+
47
+ function buildMagnet(hash, name) {
48
+ return 'magnet:?xt=urn:btih:' + hash.toLowerCase() + '&dn=' + encodeURIComponent(name) + TR;
49
+ }
50
+ function getQuality(str = '') {
51
+ const s = str.toLowerCase();
52
+ if (s.includes('4k') || s.includes('2160p') || s.includes('uhd')) return '4k';
53
+ if (s.includes('1080p')) return '1080p';
54
+ if (s.includes('720p')) return '720p';
55
+ if (s.includes('576p')) return '576p';
56
+ if (s.includes('480p')) return '480p';
57
+ if (s.includes('webrip')) return 'webrip';
58
+ if (s.includes('webdl') || s.includes('web-dl')) return 'webdl';
59
+ return null;
60
+ }
61
+ function getSizeGB(stream) {
62
+ const raw = stream.title || stream.name || '';
63
+ const m = raw.match(/πŸ’Ύ\s*([\d.]+)\s*(GB|MB)/i) || raw.match(/([\d.]+)\s*(GB|MB)/i);
64
+ if (!m) return null;
65
+ const val = parseFloat(m[1]);
66
+ return m[2].toUpperCase() === 'GB' ? val : val / 1024;
67
+ }
68
+ function getSeeders(stream) {
69
+ if (stream._seeders != null) return stream._seeders;
70
+ if (stream.behaviorHints?.seeders) return stream.behaviorHints.seeders;
71
+ const m = (stream.title || '').match(/πŸ‘€\s*(\d+)/);
72
+ return m ? parseInt(m[1], 10) : 0;
73
+ }
74
+
75
+ function processStreams(streams, type) {
76
+ const maxSize = type === 'series' ? MAX_SIZE_GB_SERIES : MAX_SIZE_GB_MOVIE;
77
+ const filtered = streams.filter(s => {
78
+ if (!s.infoHash && !s.url) return false;
79
+ if (s._provider && !ALLOWED_PROVIDERS.includes(s._provider.toLowerCase())) return false;
80
+ const q = s._quality || getQuality(s.title || s.name || '');
81
+ if (!q || !ALLOWED_QUALITIES.includes(q)) return false;
82
+ const sizeGB = s._sizeGB ?? getSizeGB(s);
83
+ if (sizeGB !== null && sizeGB > maxSize) return false;
84
+ return true;
85
+ });
86
+ const seen = new Set();
87
+ const unique = filtered.filter(s => {
88
+ const key = (s.infoHash || '').toLowerCase();
89
+ if (!key) return true;
90
+ if (seen.has(key)) return false;
91
+ seen.add(key); return true;
92
+ });
93
+ const byQuality = {};
94
+ unique.forEach(s => {
95
+ const q = s._quality || getQuality(s.title || s.name || '') || 'unknown';
96
+ if (!byQuality[q]) byQuality[q] = [];
97
+ byQuality[q].push(s);
98
+ });
99
+ for (const q in byQuality) {
100
+ byQuality[q].sort((a, b) => {
101
+ const rankA = PRIORITY_PROVIDERS.indexOf((a._source || '').toLowerCase());
102
+ const rankB = PRIORITY_PROVIDERS.indexOf((b._source || '').toLowerCase());
103
+ const pA = rankA === -1 ? 999 : rankA;
104
+ const pB = rankB === -1 ? 999 : rankB;
105
+ if (pA !== pB) return pA - pB;
106
+ return getSeeders(b) - getSeeders(a);
107
+ });
108
+ }
109
+ const sortedQualities = Object.keys(byQuality).sort((a, b) => (QUALITY_RANK[a] || 99) - (QUALITY_RANK[b] || 99));
110
+ const result = [];
111
+ sortedQualities.forEach(q => result.push(...byQuality[q].slice(0, LINKS_PER_QUALITY)));
112
+ return result;
113
+ }
114
+
115
+ async function tmdbLookup(imdbId) {
116
+ try {
117
+ const { data } = await axios.get(
118
+ `https://api.themoviedb.org/3/find/${imdbId}?api_key=${TMDB_KEY}&external_source=imdb_id`,
119
+ { timeout: 8000 }
120
+ );
121
+ const movie = data.movie_results?.[0];
122
+ const tv = data.tv_results?.[0];
123
+ if (movie) return { title: movie.title || movie.original_title, year: (movie.release_date || '').slice(0, 4) };
124
+ if (tv) return { title: tv.name || tv.original_name, year: (tv.first_air_date || '').slice(0, 4) };
125
+ return null;
126
+ } catch (e) { console.error('[TMDB]', e.message); return null; }
127
+ }
128
+
129
+ // ─── shared torrentio stream normaliser ───────────────────────────────
130
+ function normaliseTorrentio(streams, title, year) {
131
+ return streams.map(s => {
132
+ const providerMatch = (s.title || '').match(/βš™οΈ\s*(\S+)/);
133
+ let provider = providerMatch ? providerMatch[1] : 'Unknown';
134
+ provider = provider.toLowerCase().replace(/\.(to|com|org|net|io)$/, '');
135
+ if (provider === 'thepiratebay') provider = 'thepiratesbay';
136
+ if (provider === 'nyaa.si') provider = 'nyaa';
137
+ if (provider === 'limetorrents') provider = 'limetorrent';
138
+ if (provider === 'kat') provider = 'kickasstorrents';
139
+ const seeders = s.behaviorHints?.seeders
140
+ ?? (() => { const m = (s.title || '').match(/πŸ‘€\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; })();
141
+ const sizeGB = getSizeGB(s);
142
+ return {
143
+ ...s,
144
+ title: `β˜€οΈ ${title}${year ? ' (' + year + ')' : ''}\n🌱 ${seeders}\nπŸ’Ύ ${sizeGB ? sizeGB.toFixed(2) + ' GB' : 'N/A'}\nπŸ… ${provider}`,
145
+ _quality: getQuality(s.title || s.name || ''),
146
+ _seeders: seeders,
147
+ _sizeGB: sizeGB,
148
+ _source: 'torrentio',
149
+ _provider: provider,
150
+ };
151
+ });
152
+ }
153
+
154
+ // ─── Source 1: torrentsdb.com (original base) ─────────────────────────
155
+ async function fetchTorrentio(type, id, title, year) {
156
+ try {
157
+ const { data } = await axios.get(`${TORRENTIO_BASE}/stream/${type}/${id}.json`, { timeout: 12000, headers: { 'User-Agent': UA } });
158
+ const streams = normaliseTorrentio(data.streams || [], title, year);
159
+ console.log(`[Torrentio/torrentsdb] ${streams.length} for ${type}/${id}`);
160
+ return streams;
161
+ } catch (e) { console.error('[Torrentio/torrentsdb]', e.message); return []; }
162
+ }
163
+
164
+ // ─── Source 2: torrentio.strem.fun (new) ──────────────────────────────
165
+ async function fetchTorrentioStrem(type, id, title, year) {
166
+ try {
167
+ const { data } = await axios.get(`${TORRENTIO_STREM}/stream/${type}/${id}.json`, { timeout: 12000, headers: { 'User-Agent': UA } });
168
+ const streams = normaliseTorrentio(data.streams || [], title, year);
169
+ console.log(`[Torrentio/strem.fun] ${streams.length} for ${type}/${id}`);
170
+ return streams;
171
+ } catch (e) { console.error('[Torrentio/strem.fun]', e.message); return []; }
172
+ }
173
+
174
+ app.get('/manifest.json', (_, res) => res.json({
175
+ id: 'org.ghostream.plus.v2', name: 'VanStreams+',
176
+ description: 'Stream Everything You Want\nMade With 🀍 By Van',
177
+ version: '15.0.7', resources: ['stream'], types: ['movie', 'series'],
178
+ idPrefixes: ['tt'], catalogs: [],
179
+ }));
180
+
181
+ app.get('/stream/:type/:id.json', async (req, res) => {
182
+ const { type, id } = req.params;
183
+ const [imdbId] = id.split(':');
184
+ const meta = await tmdbLookup(imdbId);
185
+ const title = meta?.title || '';
186
+ const year = meta?.year || '';
187
+
188
+ const [t1R, t2R] = await Promise.allSettled([
189
+ fetchTorrentio(type, id, title, year),
190
+ fetchTorrentioStrem(type, id, title, year),
191
+ ]);
192
+ const t1 = t1R.status === 'fulfilled' ? (t1R.value || []) : [];
193
+ const t2 = t2R.status === 'fulfilled' ? (t2R.value || []) : [];
194
+
195
+ const streams = processStreams([...t1, ...t2], type);
196
+ console.log(`[stream] ${type}/${id} β†’ torrentsdb:${t1.length} strem.fun:${t2.length} final:${streams.length}`);
197
+ res.json({ streams });
198
+ });
199
+
200
+ app.get('/diag/:type/:id', async (req, res) => {
201
+ const { type, id } = req.params;
202
+ const [imdbId] = id.split(':');
203
+ const meta = await tmdbLookup(imdbId);
204
+ const [t1R, t2R] = await Promise.allSettled([
205
+ fetchTorrentio(type, id, meta?.title || '', meta?.year || ''),
206
+ fetchTorrentioStrem(type, id, meta?.title || '', meta?.year || ''),
207
+ ]);
208
+ res.json({
209
+ imdbId, title: meta?.title, year: meta?.year,
210
+ tmdb: meta ? `OK: ${meta.title} (${meta.year})` : 'FAILED',
211
+ torrentsdb: { count: (t1R.value || []).length, err: t1R.reason?.message || null },
212
+ strem_fun: { count: (t2R.value || []).length, err: t2R.reason?.message || null },
213
+ });
214
+ });
215
+
216
+ app.get('/diag-raw/:type/:id', async (req, res) => {
217
+ const { type, id } = req.params;
218
+ try {
219
+ const { data } = await axios.get(`${TORRENTIO_BASE}/stream/${type}/${id}.json`, { timeout: 12000, headers: { 'User-Agent': UA } });
220
+ res.json((data.streams || []).slice(0, 3).map(s => ({ name: s.name, title: s.title, behaviorHints: s.behaviorHints })));
221
+ } catch (e) { res.json({ error: e.message }); }
222
+ });
223
+
224
+ app.get('/', (req, res) => {
225
+ const manifestUrl = 'https://vanstreams.duckdns.org/manifest.json';
226
+ const installUrl = 'stremio://vanstreams.duckdns.org/manifest.json';
227
+ res.type('html').send(`<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>VanStreams+</title><style>*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}body{font-family:-apple-system,'Segoe UI',system-ui,sans-serif;background:#050810;color:#dde6f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:28px 20px;background-image:radial-gradient(ellipse 70% 40% at 50% 0%,rgba(255,0,0,.05) 0%,transparent 70%),radial-gradient(ellipse 40% 30% at 80% 80%,rgba(255,0,0,.03) 0%,transparent 60%)}.wrap{width:100%;max-width:560px;display:flex;flex-direction:column;gap:20px}.logo{font-size:2.6rem;font-weight:800;letter-spacing:-1px;line-height:1}.logo .van{color:#F00}.logo .streams{color:#fff}.badge-row{display:flex;gap:8px;flex-wrap:wrap;margin-top:12px}.badge{display:inline-flex;align-items:center;gap:5px;background:rgba(255,0,0,.08);border:1px solid rgba(255,0,0,.18);color:#fff;padding:4px 11px;border-radius:100px;font-size:.72rem;font-weight:700;letter-spacing:.4px;text-transform:uppercase}.desc{color:#fff;font-size:.9rem;line-height:1.6;margin-top:8px}.card{background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.07);border-radius:16px;padding:20px 22px}.card-label{font-size:.68rem;font-weight:700;text-transform:uppercase;letter-spacing:1px;color:#fff;margin-bottom:10px}.url-box{font-family:'SF Mono','Fira Code',monospace;font-size:.8rem;color:#fff;background:rgba(0,0,0,.25);border:1px solid rgba(255,0,0,.12);border-radius:10px;padding:11px 14px;word-break:break-all;line-height:1.5}.actions{display:flex;gap:10px;margin-top:14px;flex-wrap:wrap}.btn{padding:10px 22px;border-radius:10px;font-size:.84rem;font-weight:600;text-decoration:none;border:none;cursor:pointer;transition:all .15s;font-family:inherit}.btn-install{background:#F00;color:#fff}.btn-install:hover{background:#c00}.btn-copy{background:#fff;color:#050810;border:1px solid #fff}.btn-copy:hover{background:#e6e6e6}.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.stat{background:rgba(255,255,255,.02);border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px 12px;text-align:center}.stat-val{font-size:1.5rem;font-weight:800;color:#fff;line-height:1;margin-bottom:5px}.stat-lbl{font-size:.68rem;color:#aaa;font-weight:600;text-transform:uppercase;letter-spacing:.5px}.made-by{text-align:center;color:#fff;font-size:.85rem;margin-top:6px;opacity:.8}</style></head><body><div class="wrap"><div><div class="logo"><span class="van">Van</span> <span class="streams">Streams+</span></div><div class="badge-row"><span class="badge">🎬 Movies</span><span class="badge">πŸ“Ί Series</span></div><p class="desc">Get Your 🍿 Ready</p></div><div class="card"><div class="card-label">Stremio Install URL</div><div class="url-box">${manifestUrl}</div><div class="actions"><a href="${installUrl}" class="btn btn-install">Install in Stremio</a><button class="btn btn-copy" onclick="navigator.clipboard.writeText('${manifestUrl}').then(()=>{this.textContent='βœ” Copied';setTimeout(()=>this.textContent='Copy URL',2000)})">Copy URL</button></div></div><div class="stats"><div class="stat"><div class="stat-val">100K+</div><div class="stat-lbl">Movies</div></div><div class="stat"><div class="stat-val">40K+</div><div class="stat-lbl">Series</div></div><div class="stat"><div class="stat-val">20K+</div><div class="stat-lbl">Anime</div></div></div><div class="made-by">Made With 🀍 By Van</div></div></body></html>`);
228
+ });
229
+
230
+ app.listen(PORT, '0.0.0.0', () => console.log('VanStreams+ Script1 [torrentsdb+strem.fun] on :' + PORT));