vanseleem commited on
Commit
92db317
Β·
verified Β·
1 Parent(s): 49d0a0f

Update index.js

Browse files
Files changed (1) hide show
  1. index.js +67 -151
index.js CHANGED
@@ -14,13 +14,9 @@ app.use((req, res, next) => {
14
  next();
15
  });
16
 
17
- const TMDB_KEY = '83d364331c40bfbe29858aeed82f45cc';
18
- 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';
19
- const TORRENTIO_BASE = 'https://torrentio.strem.fun';
20
-
21
- const PROXY_1 = 'https://corsproxy.io/?url=';
22
- const PROXY_2 = 'https://api.codetabs.com/v1/proxy?quest=';
23
- const PROXY_3 = 'https://thingproxy.freeboard.io/fetch/';
24
 
25
  const MAX_SIZE_GB_MOVIE = 4;
26
  const MAX_SIZE_GB_SERIES = 1.5;
@@ -32,7 +28,7 @@ const QUALITY_RANK = {
32
  '4k': 5, '2160p': 5, 'webrip': 6, 'webdl': 6,
33
  };
34
 
35
- const PRIORITY_PROVIDERS = ['yts', 'knaben', 'torrentsdb', 'eztv', 'nyaasi', 'thepiratebay'];
36
 
37
  const ALLOWED_PROVIDERS = [
38
  'yts',
@@ -46,7 +42,9 @@ const ALLOWED_PROVIDERS = [
46
  'limetorrent',
47
  'kickasstorrents',
48
  'animetosho',
49
- 'tokyotosho'
 
 
50
  ];
51
 
52
  const TR = [
@@ -62,27 +60,6 @@ const TR = [
62
  'https://tracker.zhuqiy.com:443/announce',
63
  ].map(t => '&tr=' + encodeURIComponent(t)).join('');
64
 
65
- // ─── Proxy fallback function ────────────────────────────────────────
66
- async function fetchWithProxy(url, options = {}) {
67
- const proxies = [
68
- url,
69
- PROXY_1 + encodeURIComponent(url),
70
- PROXY_2 + encodeURIComponent(url),
71
- PROXY_3 + encodeURIComponent(url),
72
- ];
73
-
74
- for (const proxyUrl of proxies) {
75
- try {
76
- const response = await axios.get(proxyUrl, { timeout: 12000, headers: { 'User-Agent': UA }, ...options });
77
- return response;
78
- } catch (e) {
79
- console.warn(`[Proxy] Failed: ${proxyUrl.substring(0, 50)}... Error: ${e.message}`);
80
- continue;
81
- }
82
- }
83
- throw new Error('All proxy attempts failed');
84
- }
85
-
86
  function buildMagnet(hash, name) {
87
  return 'magnet:?xt=urn:btih:' + hash.toLowerCase() + '&dn=' + encodeURIComponent(name) + TR;
88
  }
@@ -224,111 +201,63 @@ async function tmdbLookup(imdbId) {
224
  } catch (e) { console.error('[TMDB]', e.message); return null; }
225
  }
226
 
227
- async function scrapeYTS(imdbId) {
 
228
  try {
229
- const { data } = await axios.get(
230
- `https://movies-api.accel.li/api/v2/list_movies.json?query_term=${imdbId}&limit=10`,
231
- { timeout: 12000, headers: { 'User-Agent': UA } }
232
- );
233
- if (data.status !== 'ok' || !data.data?.movies?.length) return [];
234
- const streams = [];
235
- for (const movie of data.data.movies) {
236
- if (movie.imdb_code && movie.imdb_code !== imdbId) continue;
237
- for (const t of (movie.torrents || [])) {
238
- if (!t.hash) continue;
239
- const qualityStr = `${t.quality} ${t.type || ''}`.trim();
240
- const sizeGB = t.size_bytes ? t.size_bytes / 1073741824 : null;
241
- const sizeStr = sizeGB ? sizeGB.toFixed(2) + ' GB' : (t.size || '');
242
- const langLine = getLangLine(qualityStr);
243
- streams.push({
244
- infoHash: t.hash.toLowerCase(),
245
- name: qualityStr,
246
- title: `β˜€οΈ ${movie.title} (${movie.year})\n🌱 ${t.seeds || 0}\nπŸ’Ύ ${sizeStr}\nπŸ… YTS${langLine ? '\nπŸ”Š ' + langLine : ''}`,
247
- sources: [],
248
- _quality: getQuality(qualityStr),
249
- _seeders: t.seeds || 0,
250
- _sizeGB: sizeGB,
251
- _source: 'yts',
252
- _provider: 'yts',
253
- });
254
- }
255
- }
256
- console.log(`[YTS] ${streams.length} for ${imdbId}`);
257
- return streams;
258
- } catch (e) { console.error('[YTS]', e.message); return []; }
259
- }
260
 
261
- async function scrapeKnaben(title, year, isSeries, season, episode) {
262
- if (!title) return [];
263
- try {
264
- let query = title;
265
- if (year && !isSeries) query += ' ' + year;
266
- if (isSeries && season && episode)
267
- query += ` S${String(season).padStart(2, '0')}E${String(episode).padStart(2, '0')}`;
268
- const categories = isSeries ? [5000000, 5001000] : [2000000, 2001000];
269
- const { data } = await axios.post(
270
- 'https://api.knaben.org/v1',
271
- { search_type: '75%', search_field: 'title', query, order_by: 'seeders', order_direction: 'desc', categories, from: 0, size: 40, hide_unsafe: true, hide_xxx: true },
272
- { timeout: 14000, headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'User-Agent': UA } }
273
- );
274
- if (!data?.hits?.length) return [];
275
  const streams = [];
276
- for (const hit of data.hits) {
277
- if (!hit.magnetUrl && !hit.hash) continue;
278
- const magnet = hit.magnetUrl || buildMagnet(hit.hash, hit.title || '');
279
- const hashMatch = magnet.match(/btih:([a-fA-F0-9]{32,40})/i);
280
- const hash = hashMatch ? hashMatch[1] : (hit.hash || '');
281
- if (!hash) continue;
282
- const sizeGB = hit.bytes ? hit.bytes / 1073741824 : null;
283
- const sizeStr = sizeGB ? sizeGB.toFixed(2) + ' GB' : '';
284
- const langLine = getLangLine(hit.title || '');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  streams.push({
286
- infoHash: hash.toLowerCase(),
287
- name: hit.title || '',
288
- title: `β˜€οΈ ${title}${year ? ' (' + year + ')' : ''}\n🌱 ${hit.seeders || 0}\nπŸ’Ύ ${sizeStr}\nπŸ… Knaben${langLine ? '\nπŸ”Š ' + langLine : ''}`,
 
289
  sources: [],
290
- _quality: getQuality(hit.title || ''),
291
- _seeders: hit.seeders || 0,
292
  _sizeGB: sizeGB,
293
- _source: 'knaben',
294
- _provider: 'knaben',
295
  });
296
  }
297
- console.log(`[Knaben] ${streams.length} for "${query}"`);
298
  return streams;
299
- } catch (e) { console.error('[Knaben]', e.message); return []; }
300
- }
301
-
302
- async function fetchTorrentio(type, id, title, year) {
303
- try {
304
- const url = `${TORRENTIO_BASE}/stream/${type}/${id}.json`;
305
- const { data } = await fetchWithProxy(url);
306
- const streams = (data.streams || []).map(s => {
307
- const origTitle = s.title || '';
308
- const providerMatch = (s.title || '').match(/βš™οΈ\s*(\S+)/);
309
- let provider = providerMatch ? providerMatch[1] : 'Unknown';
310
- provider = provider.toLowerCase().replace(/\.(to|com|org|net|io)$/, '');
311
- if (provider === 'thepiratebay') provider = 'thepiratesbay';
312
- if (provider === 'nyaa.si') provider = 'nyaa';
313
- if (provider === 'limetorrents') provider = 'limetorrent';
314
- if (provider === 'kat') provider = 'kickasstorrents';
315
- const seeders = s.behaviorHints?.seeders
316
- ?? (() => { const m = (s.title || '').match(/πŸ‘€\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; })();
317
- const sizeGB = getSizeGB(s);
318
- const langLine = getLangLine(origTitle);
319
- return {
320
- ...s,
321
- title: `β˜€οΈ ${title}${year ? ' (' + year + ')' : ''}\n🌱 ${seeders}\nπŸ’Ύ ${sizeGB ? sizeGB.toFixed(2) + ' GB' : 'N/A'}\nπŸ… ${provider}${langLine ? '\nπŸ”Š ' + langLine : ''}`,
322
- _quality: getQuality(s.title || s.name || ''),
323
- _seeders: seeders,
324
- _sizeGB: sizeGB,
325
- _source: 'torrentio',
326
- _provider: provider,
327
- };
328
- });
329
- console.log(`[Torrentio] ${streams.length} for ${type}/${id}`);
330
- return streams;
331
- } catch (e) { console.error('[Torrentio]', e.message); return []; }
332
  }
333
 
334
  // ─── Manifest ──────────────────────────────────────────────────────────
@@ -356,19 +285,11 @@ app.get('/stream/:type/:id.json', async (req, res) => {
356
  const title = meta?.title || '';
357
  const year = meta?.year || '';
358
 
359
- const [ytsR, knabenR, torrentioR] = await Promise.allSettled([
360
- isSeries ? Promise.resolve([]) : scrapeYTS(imdbId),
361
- scrapeKnaben(title, year, isSeries, season, episode),
362
- fetchTorrentio(type, id, title, year),
363
- ]);
364
-
365
- const yts = ytsR.status === 'fulfilled' ? (ytsR.value || []) : [];
366
- const knaben = knabenR.status === 'fulfilled' ? (knabenR.value || []) : [];
367
- const torrentio = torrentioR.status === 'fulfilled' ? (torrentioR.value || []) : [];
368
 
369
- const streams = processStreams([...yts, ...knaben, ...torrentio], type);
370
 
371
- console.log(`[stream] ${type}/${id} β†’ yts:${yts.length} knaben:${knaben.length} torrentio:${torrentio.length} final:${streams.length}`);
372
  res.json({ streams });
373
  });
374
 
@@ -378,28 +299,23 @@ app.get('/diag/:type/:id', async (req, res) => {
378
  const isSeries = type === 'series';
379
  const [imdbId, season = '', episode = ''] = id.split(':');
380
  const meta = await tmdbLookup(imdbId);
381
- const [ytsR, knabenR, torrentioR] = await Promise.allSettled([
382
- isSeries ? Promise.resolve([]) : scrapeYTS(imdbId),
383
- scrapeKnaben(meta?.title || '', meta?.year || '', isSeries, season, episode),
384
- fetchTorrentio(type, id, meta?.title || '', meta?.year || ''),
385
- ]);
386
  res.json({
387
  imdbId, title: meta?.title, year: meta?.year, isSeries,
388
- tmdb: meta ? `OK: ${meta.title} (${meta.year})` : 'FAILED',
389
- yts: { count: (ytsR.value || []).length, err: ytsR.reason?.message || null },
390
- knaben: { count: (knabenR.value || []).length, err: knabenR.reason?.message || null },
391
- torrentio: { count: (torrentioR.value || []).length, err: torrentioR.reason?.message || null },
392
  });
393
  });
394
 
395
  app.get('/diag-raw/:type/:id', async (req, res) => {
396
  const { type, id } = req.params;
397
  try {
398
- const url = `${TORRENTIO_BASE}/stream/${type}/${id}.json`;
399
- const { data } = await fetchWithProxy(url);
400
- res.json((data.streams || []).slice(0, 3).map(s => ({
401
- name: s.name, title: s.title, behaviorHints: s.behaviorHints,
402
- })));
 
403
  } catch (e) { res.json({ error: e.message }); }
404
  });
405
 
 
14
  next();
15
  });
16
 
17
+ const TMDB_KEY = '83d364331c40bfbe29858aeed82f45cc';
18
+ 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';
19
+ const TORRENTCLAW_API = 'https://torrentclaw.com/api/stremio';
 
 
 
 
20
 
21
  const MAX_SIZE_GB_MOVIE = 4;
22
  const MAX_SIZE_GB_SERIES = 1.5;
 
28
  '4k': 5, '2160p': 5, 'webrip': 6, 'webdl': 6,
29
  };
30
 
31
+ const PRIORITY_PROVIDERS = ['yts', 'knaben', 'torrentsdb', 'eztv', 'nyaasi', 'thepiratebay', 'torrentclaw'];
32
 
33
  const ALLOWED_PROVIDERS = [
34
  'yts',
 
42
  'limetorrent',
43
  'kickasstorrents',
44
  'animetosho',
45
+ 'tokyotosho',
46
+ 'torrentclaw',
47
+ 'unknown'
48
  ];
49
 
50
  const TR = [
 
60
  'https://tracker.zhuqiy.com:443/announce',
61
  ].map(t => '&tr=' + encodeURIComponent(t)).join('');
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  function buildMagnet(hash, name) {
64
  return 'magnet:?xt=urn:btih:' + hash.toLowerCase() + '&dn=' + encodeURIComponent(name) + TR;
65
  }
 
201
  } catch (e) { console.error('[TMDB]', e.message); return null; }
202
  }
203
 
204
+ // ─── TorrentClaw scraper (replaces YTS / Knaben / Torrentio) ───────────
205
+ async function scrapeTorrentClaw(type, imdbId, title, year, season, episode) {
206
  try {
207
+ const isSeries = type === 'series';
208
+ const path = isSeries
209
+ ? `series/${imdbId}:${season || 1}:${episode || 1}`
210
+ : `movie/${imdbId}`;
211
+ const url = `${TORRENTCLAW_API}/stream/${path}.json`;
212
+
213
+ const { data } = await axios.get(url, {
214
+ timeout: 15000,
215
+ headers: { 'User-Agent': UA, 'Accept': 'application/json' },
216
+ });
217
+
218
+ if (!data || !Array.isArray(data.streams) || data.streams.length === 0) return [];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  const streams = [];
221
+ for (const item of data.streams) {
222
+ if (!item) continue;
223
+
224
+ const rawName = item.name || '';
225
+ const rawTitle = item.title || '';
226
+ const rawDesc = item.description || '';
227
+ const combined = `${rawName} ${rawTitle} ${rawDesc}`.replace(/\n/g, ' ');
228
+
229
+ const infoHashMatch = (item.url || '').match(/btih:([a-fA-F0-9]{32,40})/i);
230
+ const infoHash = item.infoHash || (infoHashMatch ? infoHashMatch[1] : null);
231
+ if (!infoHash && !item.url) continue;
232
+
233
+ const sizeGB = getSizeGB({ title: combined })
234
+ ?? (typeof item.size === 'number' ? item.size / 1073741824 : null)
235
+ ?? (typeof item.bytes === 'number' ? item.bytes / 1073741824 : null);
236
+ const sizeStr = sizeGB ? sizeGB.toFixed(2) + ' GB' : 'N/A';
237
+
238
+ const seeders = typeof item.seeders === 'number'
239
+ ? item.seeders
240
+ : (() => { const m = combined.match(/🌱\s*(\d+)/) || combined.match(/πŸ‘€\s*(\d+)/) || combined.match(/(\d+)\s*seed/i); return m ? parseInt(m[1], 10) : 0; })();
241
+
242
+ const provider = (item.provider || item.source || item.indexer || 'TorrentClaw').toString().toLowerCase();
243
+ const langLine = getLangLine(combined);
244
+
245
  streams.push({
246
+ infoHash: infoHash ? infoHash.toLowerCase() : undefined,
247
+ url: infoHash ? undefined : item.url,
248
+ name: rawName || rawTitle,
249
+ title: `β˜€οΈ ${title}${year ? ' (' + year + ')' : ''}\n🌱 ${seeders}\nπŸ’Ύ ${sizeStr}\nπŸ… TorrentClaw${langLine ? '\nπŸ”Š ' + langLine : ''}`,
250
  sources: [],
251
+ _quality: getQuality(combined),
252
+ _seeders: seeders,
253
  _sizeGB: sizeGB,
254
+ _source: 'torrentclaw',
255
+ _provider: 'torrentclaw',
256
  });
257
  }
258
+ console.log(`[TorrentClaw] ${streams.length} for ${type}/${imdbId}`);
259
  return streams;
260
+ } catch (e) { console.error('[TorrentClaw]', e.message); return []; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
261
  }
262
 
263
  // ─── Manifest ──────────────────────────────────────────────────────────
 
285
  const title = meta?.title || '';
286
  const year = meta?.year || '';
287
 
288
+ const torrentclaw = await scrapeTorrentClaw(type, imdbId, title, year, season, episode);
 
 
 
 
 
 
 
 
289
 
290
+ const streams = processStreams(torrentclaw, type);
291
 
292
+ console.log(`[stream] ${type}/${id} β†’ torrentclaw:${torrentclaw.length} final:${streams.length}`);
293
  res.json({ streams });
294
  });
295
 
 
299
  const isSeries = type === 'series';
300
  const [imdbId, season = '', episode = ''] = id.split(':');
301
  const meta = await tmdbLookup(imdbId);
302
+ const torrentclaw = await scrapeTorrentClaw(type, imdbId, meta?.title || '', meta?.year || '', season, episode);
 
 
 
 
303
  res.json({
304
  imdbId, title: meta?.title, year: meta?.year, isSeries,
305
+ tmdb: meta ? `OK: ${meta.title} (${meta.year})` : 'FAILED',
306
+ torrentclaw: { count: torrentclaw.length },
 
 
307
  });
308
  });
309
 
310
  app.get('/diag-raw/:type/:id', async (req, res) => {
311
  const { type, id } = req.params;
312
  try {
313
+ const isSeries = type === 'series';
314
+ const [imdbId, season = '', episode = ''] = id.split(':');
315
+ const path = isSeries ? `series/${imdbId}:${season || 1}:${episode || 1}` : `movie/${imdbId}`;
316
+ const url = `${TORRENTCLAW_API}/stream/${path}.json`;
317
+ const { data } = await axios.get(url, { timeout: 15000, headers: { 'User-Agent': UA, 'Accept': 'application/json' } });
318
+ res.json((data.streams || []).slice(0, 3));
319
  } catch (e) { res.json({ error: e.message }); }
320
  });
321