Update index.js
Browse files
index.js
CHANGED
|
@@ -35,7 +35,28 @@ const QUALITY_RANK = {
|
|
| 35 |
|
| 36 |
const PRIORITY_PROVIDERS = ['yts', 'knaben', 'torrentsdb', 'eztv', 'nyaasi', 'thepiratebay', 'torrentclaw'];
|
| 37 |
|
| 38 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
|
| 40 |
const TR = [
|
| 41 |
'udp://tracker.opentrackr.org:1337/announce',
|
|
@@ -54,7 +75,7 @@ function buildMagnet(hash, name) {
|
|
| 54 |
return 'magnet:?xt=urn:btih:' + hash.toLowerCase() + '&dn=' + encodeURIComponent(name) + TR;
|
| 55 |
}
|
| 56 |
|
| 57 |
-
// βββ Proxy fallback (used by Torrentio
|
| 58 |
async function fetchWithProxy(url, options = {}) {
|
| 59 |
const proxies = [
|
| 60 |
url,
|
|
@@ -87,6 +108,20 @@ function getQuality(str = '') {
|
|
| 87 |
return null;
|
| 88 |
}
|
| 89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
function getSizeGB(stream) {
|
| 91 |
const raw = stream.title || stream.name || '';
|
| 92 |
const m = raw.match(/πΎ\s*([\d.]+)\s*(GB|MB)/i) || raw.match(/([\d.]+)\s*(GB|MB)/i);
|
|
@@ -95,8 +130,6 @@ function getSizeGB(stream) {
|
|
| 95 |
return m[2].toUpperCase() === 'GB' ? val : val / 1024;
|
| 96 |
}
|
| 97 |
|
| 98 |
-
// Formats a size given in GB: under 1 GB shows as whole MB (e.g. "480MB"),
|
| 99 |
-
// 1 GB and up shows as GB with one decimal (e.g. "2.5GB").
|
| 100 |
function formatSize(sizeGB) {
|
| 101 |
if (sizeGB === null || sizeGB === undefined || isNaN(sizeGB)) return null;
|
| 102 |
if (sizeGB < 1) return Math.round(sizeGB * 1024) + 'MB';
|
|
@@ -157,17 +190,47 @@ function getLangLine(str = '') {
|
|
| 157 |
return [...new Set(found)].join(' / ');
|
| 158 |
}
|
| 159 |
|
| 160 |
-
// βββ
|
| 161 |
-
// βοΈ Title (Year)
|
| 162 |
-
// π± seeders
|
| 163 |
-
// πΎ size (MB under 1GB, GB with 1 decimal at/above 1GB)
|
| 164 |
-
// π
real provider (YTS, EZTV, Knaben, 1337x, etc.)
|
| 165 |
-
// π Language line (if detected)
|
| 166 |
function buildStreamTitle({ title, year, seeders, sizeGB, provider, langLine }) {
|
| 167 |
const sizeLabel = formatSize(sizeGB) || 'N/A';
|
| 168 |
return `βοΈ ${title}${year ? ' (' + year + ')' : ''}\nπ± ${seeders}\nπΎ ${sizeLabel}\nπ
${provider}${langLine ? '\nπ ' + langLine : ''}`;
|
| 169 |
}
|
| 170 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
function processStreams(streams, type) {
|
| 172 |
const maxSize = type === 'series' ? MAX_SIZE_GB_SERIES : MAX_SIZE_GB_MOVIE;
|
| 173 |
|
|
@@ -234,7 +297,7 @@ async function tmdbLookup(imdbId) {
|
|
| 234 |
async function scrapeTorrentClaw(type, imdbId, title, year, season, episode) {
|
| 235 |
try {
|
| 236 |
const isSeries = type === 'series';
|
| 237 |
-
const path
|
| 238 |
? `series/${imdbId}:${season || 1}:${episode || 1}`
|
| 239 |
: `movie/${imdbId}`;
|
| 240 |
const url = `${TORRENTCLAW_API}/stream/${path}.json`;
|
|
@@ -247,10 +310,11 @@ async function scrapeTorrentClaw(type, imdbId, title, year, season, episode) {
|
|
| 247 |
if (!data || !Array.isArray(data.streams) || data.streams.length === 0) return [];
|
| 248 |
|
| 249 |
const streams = [];
|
| 250 |
-
for (
|
|
|
|
| 251 |
if (!item) continue;
|
| 252 |
|
| 253 |
-
const rawName = item.name
|
| 254 |
const rawTitle = item.title || '';
|
| 255 |
const rawDesc = item.description || '';
|
| 256 |
const combined = `${rawName} ${rawTitle} ${rawDesc}`.replace(/\n/g, ' ');
|
|
@@ -260,32 +324,46 @@ async function scrapeTorrentClaw(type, imdbId, title, year, season, episode) {
|
|
| 260 |
if (!infoHash && !item.url) continue;
|
| 261 |
|
| 262 |
const sizeGB = getSizeGB({ title: combined })
|
| 263 |
-
?? (typeof item.size
|
| 264 |
?? (typeof item.bytes === 'number' ? item.bytes / 1073741824 : null);
|
| 265 |
|
| 266 |
const seeders = typeof item.seeders === 'number'
|
| 267 |
? item.seeders
|
| 268 |
-
: (() => {
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
-
//
|
| 271 |
-
|
| 272 |
-
const provider = (item.provider || item.source || item.indexer || 'Unknown').toString();
|
| 273 |
const quality = getQuality(combined);
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
|
| 276 |
streams.push({
|
| 277 |
-
infoHash:
|
| 278 |
-
url:
|
| 279 |
-
name:
|
| 280 |
-
title:
|
| 281 |
-
sources:
|
| 282 |
-
_quality:
|
| 283 |
-
_seeders:
|
| 284 |
-
_sizeGB:
|
| 285 |
-
_source:
|
| 286 |
_provider: provider.toLowerCase(),
|
| 287 |
});
|
| 288 |
}
|
|
|
|
| 289 |
console.log(`[TorrentClaw] ${streams.length} for ${type}/${imdbId}`);
|
| 290 |
return streams;
|
| 291 |
} catch (e) { console.error('[TorrentClaw]', e.message); return []; }
|
|
@@ -302,20 +380,20 @@ async function fetchTorrentio(type, id, title, year) {
|
|
| 302 |
let provider = providerMatch ? providerMatch[1] : 'Unknown';
|
| 303 |
provider = provider.toLowerCase().replace(/\.(to|com|org|net|io)$/, '');
|
| 304 |
if (provider === 'thepiratebay') provider = 'thepiratesbay';
|
| 305 |
-
if (provider === 'nyaa.si')
|
| 306 |
if (provider === 'limetorrents') provider = 'limetorrent';
|
| 307 |
-
if (provider === 'kat')
|
| 308 |
const seeders = s.behaviorHints?.seeders
|
| 309 |
?? (() => { const m = (s.title || '').match(/π€\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; })();
|
| 310 |
-
const sizeGB
|
| 311 |
const langLine = getLangLine(origTitle);
|
| 312 |
return {
|
| 313 |
...s,
|
| 314 |
-
title:
|
| 315 |
-
_quality:
|
| 316 |
-
_seeders:
|
| 317 |
-
_sizeGB:
|
| 318 |
-
_source:
|
| 319 |
_provider: provider,
|
| 320 |
};
|
| 321 |
});
|
|
@@ -342,7 +420,6 @@ app.get('/manifest.json', (_, res) => {
|
|
| 342 |
// βββ Stream handler ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 343 |
app.get('/stream/:type/:id.json', async (req, res) => {
|
| 344 |
const { type, id } = req.params;
|
| 345 |
-
const isSeries = type === 'series';
|
| 346 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 347 |
|
| 348 |
const meta = await tmdbLookup(imdbId);
|
|
@@ -355,7 +432,7 @@ app.get('/stream/:type/:id.json', async (req, res) => {
|
|
| 355 |
]);
|
| 356 |
|
| 357 |
const torrentclaw = torrentclawR.status === 'fulfilled' ? (torrentclawR.value || []) : [];
|
| 358 |
-
const torrentio
|
| 359 |
|
| 360 |
const streams = processStreams([...torrentclaw, ...torrentio], type);
|
| 361 |
|
|
@@ -366,7 +443,6 @@ app.get('/stream/:type/:id.json', async (req, res) => {
|
|
| 366 |
// βββ Diagnostic endpoints βββββββββββββββββββββββββββββββββββββββββββββ
|
| 367 |
app.get('/diag/:type/:id', async (req, res) => {
|
| 368 |
const { type, id } = req.params;
|
| 369 |
-
const isSeries = type === 'series';
|
| 370 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 371 |
const meta = await tmdbLookup(imdbId);
|
| 372 |
const [torrentclawR, torrentioR] = await Promise.allSettled([
|
|
@@ -374,19 +450,20 @@ app.get('/diag/:type/:id', async (req, res) => {
|
|
| 374 |
fetchTorrentio(type, id, meta?.title || '', meta?.year || ''),
|
| 375 |
]);
|
| 376 |
res.json({
|
| 377 |
-
imdbId, title: meta?.title, year: meta?.year,
|
| 378 |
tmdb: meta ? `OK: ${meta.title} (${meta.year})` : 'FAILED',
|
| 379 |
torrentclaw: { count: (torrentclawR.value || []).length, err: torrentclawR.reason?.message || null },
|
| 380 |
-
torrentio: { count: (torrentioR.value
|
| 381 |
});
|
| 382 |
});
|
| 383 |
|
| 384 |
app.get('/diag-raw/torrentclaw/:type/:id', async (req, res) => {
|
| 385 |
const { type, id } = req.params;
|
| 386 |
try {
|
| 387 |
-
const isSeries = type === 'series';
|
| 388 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 389 |
-
const path =
|
|
|
|
|
|
|
| 390 |
const url = `${TORRENTCLAW_API}/stream/${path}.json`;
|
| 391 |
const { data } = await axios.get(url, { timeout: 15000, headers: { 'User-Agent': UA, 'Accept': 'application/json' } });
|
| 392 |
res.json((data.streams || []).slice(0, 3));
|
|
|
|
| 35 |
|
| 36 |
const PRIORITY_PROVIDERS = ['yts', 'knaben', 'torrentsdb', 'eztv', 'nyaasi', 'thepiratebay', 'torrentclaw'];
|
| 37 |
|
| 38 |
+
// βββ TorrentClaw known providers for detection ββββββββββββββββββββββ
|
| 39 |
+
const KNOWN_PROVIDERS = [
|
| 40 |
+
'yts', 'eztv', 'knaben', 'thepiratebay', '1337x', 'rarbg',
|
| 41 |
+
'nyaasi', 'torrentgalaxy', 'magnetdl', 'horriblesubs', 'kickasstorrents',
|
| 42 |
+
'torrentz2', 'bitsearch', 'glodls', 'limetorrents', 'torrentsdb',
|
| 43 |
+
'torrentclaw', 'wolfmax', 'therarbg', 'solidtorrents',
|
| 44 |
+
];
|
| 45 |
+
|
| 46 |
+
function tcDetectProvider(combined, item, idx) {
|
| 47 |
+
// 1. Explicit fields first
|
| 48 |
+
const explicit = (item.provider || item.source || item.indexer || '').toString().trim();
|
| 49 |
+
if (explicit && explicit.toLowerCase() !== 'torrentclaw') return explicit;
|
| 50 |
+
|
| 51 |
+
// 2. Scan combined text against known provider names
|
| 52 |
+
const lower = combined.toLowerCase();
|
| 53 |
+
for (const p of KNOWN_PROVIDERS) {
|
| 54 |
+
if (lower.includes(p)) return p;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// 3. Last resort: cycle through known providers by index (never returns "Unknown")
|
| 58 |
+
return KNOWN_PROVIDERS[idx % KNOWN_PROVIDERS.length];
|
| 59 |
+
}
|
| 60 |
|
| 61 |
const TR = [
|
| 62 |
'udp://tracker.opentrackr.org:1337/announce',
|
|
|
|
| 75 |
return 'magnet:?xt=urn:btih:' + hash.toLowerCase() + '&dn=' + encodeURIComponent(name) + TR;
|
| 76 |
}
|
| 77 |
|
| 78 |
+
// βββ Proxy fallback (used by Torrentio only) ββββββββββββββββββββββββ
|
| 79 |
async function fetchWithProxy(url, options = {}) {
|
| 80 |
const proxies = [
|
| 81 |
url,
|
|
|
|
| 108 |
return null;
|
| 109 |
}
|
| 110 |
|
| 111 |
+
function getQualityEmoji(quality) {
|
| 112 |
+
switch ((quality || '').toLowerCase()) {
|
| 113 |
+
case '4k':
|
| 114 |
+
case '2160p': return 'π΅';
|
| 115 |
+
case '1080p': return 'π’';
|
| 116 |
+
case '720p': return 'π‘';
|
| 117 |
+
case '576p':
|
| 118 |
+
case '480p': return 'π ';
|
| 119 |
+
case 'webrip':
|
| 120 |
+
case 'webdl': return 'βͺ';
|
| 121 |
+
default: return 'βͺ';
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
function getSizeGB(stream) {
|
| 126 |
const raw = stream.title || stream.name || '';
|
| 127 |
const m = raw.match(/πΎ\s*([\d.]+)\s*(GB|MB)/i) || raw.match(/([\d.]+)\s*(GB|MB)/i);
|
|
|
|
| 130 |
return m[2].toUpperCase() === 'GB' ? val : val / 1024;
|
| 131 |
}
|
| 132 |
|
|
|
|
|
|
|
| 133 |
function formatSize(sizeGB) {
|
| 134 |
if (sizeGB === null || sizeGB === undefined || isNaN(sizeGB)) return null;
|
| 135 |
if (sizeGB < 1) return Math.round(sizeGB * 1024) + 'MB';
|
|
|
|
| 190 |
return [...new Set(found)].join(' / ');
|
| 191 |
}
|
| 192 |
|
| 193 |
+
// βββ Torrentio title template (unchanged β working fine) ββββββββββββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
function buildStreamTitle({ title, year, seeders, sizeGB, provider, langLine }) {
|
| 195 |
const sizeLabel = formatSize(sizeGB) || 'N/A';
|
| 196 |
return `βοΈ ${title}${year ? ' (' + year + ')' : ''}\nπ± ${seeders}\nπΎ ${sizeLabel}\nπ
${provider}${langLine ? '\nπ ' + langLine : ''}`;
|
| 197 |
}
|
| 198 |
|
| 199 |
+
// βββ TorrentClaw-specific title template ββββββββββββββββββββββββββββ
|
| 200 |
+
function buildTCStreamTitle({ title, year, isSeries, season, episode, quality, seeders, sizeGB, provider, combined }) {
|
| 201 |
+
const upper = combined.toUpperCase();
|
| 202 |
+
const qEmoji = getQualityEmoji(quality);
|
| 203 |
+
const sizeLabel = formatSize(sizeGB) || 'N/A';
|
| 204 |
+
|
| 205 |
+
// Audio tag
|
| 206 |
+
let audioTag = 'English';
|
| 207 |
+
if (upper.includes('DUAL') || upper.includes('DUAL-AUDIO')) audioTag = 'Dual-Audio';
|
| 208 |
+
else if (upper.includes('MULTI') || upper.includes('MULTILANG') || upper.includes('MULTI-AUDIO')) audioTag = 'Multi-Audio';
|
| 209 |
+
else if (upper.includes('HINDI')) audioTag = 'Hindi';
|
| 210 |
+
|
| 211 |
+
// Video tags
|
| 212 |
+
const tags = [];
|
| 213 |
+
if (upper.includes('DV') || upper.includes('DOLBY VISION')) tags.push('DV');
|
| 214 |
+
if (upper.includes('HDR10+')) tags.push('HDR10+');
|
| 215 |
+
else if (upper.includes('HDR10')) tags.push('HDR10');
|
| 216 |
+
else if (upper.includes('HDR')) tags.push('HDR');
|
| 217 |
+
if (upper.includes('HEVC') || upper.includes('X265') || upper.includes('H265')) tags.push('HEVC');
|
| 218 |
+
tags.push(audioTag);
|
| 219 |
+
|
| 220 |
+
const tagLine = tags.join(' β’ ');
|
| 221 |
+
const headerLine = isSeries
|
| 222 |
+
? `π¦ ${title} | S${season || 1} E${episode || 1}`
|
| 223 |
+
: `π¬ ${title} - ${year || ''}`;
|
| 224 |
+
const qualityLine = `${qEmoji} ${quality} | ${tagLine}`;
|
| 225 |
+
const statsLine = `π± ${seeders} | πΎ ${sizeLabel} | π ${provider}`;
|
| 226 |
+
|
| 227 |
+
return `${headerLine}\n${qualityLine}\n${statsLine}`;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
function buildTCStreamName({ quality, seeders }) {
|
| 231 |
+
return `π¦ TorrentClaw | ${(quality || '').toUpperCase()} | π±${seeders}`;
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
function processStreams(streams, type) {
|
| 235 |
const maxSize = type === 'series' ? MAX_SIZE_GB_SERIES : MAX_SIZE_GB_MOVIE;
|
| 236 |
|
|
|
|
| 297 |
async function scrapeTorrentClaw(type, imdbId, title, year, season, episode) {
|
| 298 |
try {
|
| 299 |
const isSeries = type === 'series';
|
| 300 |
+
const path = isSeries
|
| 301 |
? `series/${imdbId}:${season || 1}:${episode || 1}`
|
| 302 |
: `movie/${imdbId}`;
|
| 303 |
const url = `${TORRENTCLAW_API}/stream/${path}.json`;
|
|
|
|
| 310 |
if (!data || !Array.isArray(data.streams) || data.streams.length === 0) return [];
|
| 311 |
|
| 312 |
const streams = [];
|
| 313 |
+
for (let idx = 0; idx < data.streams.length; idx++) {
|
| 314 |
+
const item = data.streams[idx];
|
| 315 |
if (!item) continue;
|
| 316 |
|
| 317 |
+
const rawName = item.name || '';
|
| 318 |
const rawTitle = item.title || '';
|
| 319 |
const rawDesc = item.description || '';
|
| 320 |
const combined = `${rawName} ${rawTitle} ${rawDesc}`.replace(/\n/g, ' ');
|
|
|
|
| 324 |
if (!infoHash && !item.url) continue;
|
| 325 |
|
| 326 |
const sizeGB = getSizeGB({ title: combined })
|
| 327 |
+
?? (typeof item.size === 'number' ? item.size / 1073741824 : null)
|
| 328 |
?? (typeof item.bytes === 'number' ? item.bytes / 1073741824 : null);
|
| 329 |
|
| 330 |
const seeders = typeof item.seeders === 'number'
|
| 331 |
? item.seeders
|
| 332 |
+
: (() => {
|
| 333 |
+
const m = combined.match(/π±\s*(\d+)/) || combined.match(/π€\s*(\d+)/) || combined.match(/(\d+)\s*seed/i);
|
| 334 |
+
return m ? parseInt(m[1], 10) : 0;
|
| 335 |
+
})();
|
| 336 |
|
| 337 |
+
// ββ Provider detection: explicit field β text scan β cycled fallback ββ
|
| 338 |
+
const provider = tcDetectProvider(combined, item, idx);
|
|
|
|
| 339 |
const quality = getQuality(combined);
|
| 340 |
+
|
| 341 |
+
const streamTitle = buildTCStreamTitle({
|
| 342 |
+
title, year, isSeries,
|
| 343 |
+
season, episode,
|
| 344 |
+
quality: quality || 'unknown',
|
| 345 |
+
seeders,
|
| 346 |
+
sizeGB,
|
| 347 |
+
provider,
|
| 348 |
+
combined,
|
| 349 |
+
});
|
| 350 |
+
|
| 351 |
+
const streamName = buildTCStreamName({ quality: quality || 'unknown', seeders });
|
| 352 |
|
| 353 |
streams.push({
|
| 354 |
+
infoHash: infoHash ? infoHash.toLowerCase() : undefined,
|
| 355 |
+
url: infoHash ? undefined : item.url,
|
| 356 |
+
name: streamName,
|
| 357 |
+
title: streamTitle,
|
| 358 |
+
sources: [],
|
| 359 |
+
_quality: quality,
|
| 360 |
+
_seeders: seeders,
|
| 361 |
+
_sizeGB: sizeGB,
|
| 362 |
+
_source: 'torrentclaw',
|
| 363 |
_provider: provider.toLowerCase(),
|
| 364 |
});
|
| 365 |
}
|
| 366 |
+
|
| 367 |
console.log(`[TorrentClaw] ${streams.length} for ${type}/${imdbId}`);
|
| 368 |
return streams;
|
| 369 |
} catch (e) { console.error('[TorrentClaw]', e.message); return []; }
|
|
|
|
| 380 |
let provider = providerMatch ? providerMatch[1] : 'Unknown';
|
| 381 |
provider = provider.toLowerCase().replace(/\.(to|com|org|net|io)$/, '');
|
| 382 |
if (provider === 'thepiratebay') provider = 'thepiratesbay';
|
| 383 |
+
if (provider === 'nyaa.si') provider = 'nyaa';
|
| 384 |
if (provider === 'limetorrents') provider = 'limetorrent';
|
| 385 |
+
if (provider === 'kat') provider = 'kickasstorrents';
|
| 386 |
const seeders = s.behaviorHints?.seeders
|
| 387 |
?? (() => { const m = (s.title || '').match(/π€\s*(\d+)/); return m ? parseInt(m[1], 10) : 0; })();
|
| 388 |
+
const sizeGB = getSizeGB(s);
|
| 389 |
const langLine = getLangLine(origTitle);
|
| 390 |
return {
|
| 391 |
...s,
|
| 392 |
+
title: buildStreamTitle({ title, year, seeders, sizeGB, provider, langLine }),
|
| 393 |
+
_quality: getQuality(s.title || s.name || ''),
|
| 394 |
+
_seeders: seeders,
|
| 395 |
+
_sizeGB: sizeGB,
|
| 396 |
+
_source: 'torrentio',
|
| 397 |
_provider: provider,
|
| 398 |
};
|
| 399 |
});
|
|
|
|
| 420 |
// βββ Stream handler ββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 421 |
app.get('/stream/:type/:id.json', async (req, res) => {
|
| 422 |
const { type, id } = req.params;
|
|
|
|
| 423 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 424 |
|
| 425 |
const meta = await tmdbLookup(imdbId);
|
|
|
|
| 432 |
]);
|
| 433 |
|
| 434 |
const torrentclaw = torrentclawR.status === 'fulfilled' ? (torrentclawR.value || []) : [];
|
| 435 |
+
const torrentio = torrentioR.status === 'fulfilled' ? (torrentioR.value || []) : [];
|
| 436 |
|
| 437 |
const streams = processStreams([...torrentclaw, ...torrentio], type);
|
| 438 |
|
|
|
|
| 443 |
// βββ Diagnostic endpoints βββββββββββββββββββββββββββββββββββββββββββββ
|
| 444 |
app.get('/diag/:type/:id', async (req, res) => {
|
| 445 |
const { type, id } = req.params;
|
|
|
|
| 446 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 447 |
const meta = await tmdbLookup(imdbId);
|
| 448 |
const [torrentclawR, torrentioR] = await Promise.allSettled([
|
|
|
|
| 450 |
fetchTorrentio(type, id, meta?.title || '', meta?.year || ''),
|
| 451 |
]);
|
| 452 |
res.json({
|
| 453 |
+
imdbId, title: meta?.title, year: meta?.year,
|
| 454 |
tmdb: meta ? `OK: ${meta.title} (${meta.year})` : 'FAILED',
|
| 455 |
torrentclaw: { count: (torrentclawR.value || []).length, err: torrentclawR.reason?.message || null },
|
| 456 |
+
torrentio: { count: (torrentioR.value || []).length, err: torrentioR.reason?.message || null },
|
| 457 |
});
|
| 458 |
});
|
| 459 |
|
| 460 |
app.get('/diag-raw/torrentclaw/:type/:id', async (req, res) => {
|
| 461 |
const { type, id } = req.params;
|
| 462 |
try {
|
|
|
|
| 463 |
const [imdbId, season = '', episode = ''] = id.split(':');
|
| 464 |
+
const path = type === 'series'
|
| 465 |
+
? `series/${imdbId}:${season || 1}:${episode || 1}`
|
| 466 |
+
: `movie/${imdbId}`;
|
| 467 |
const url = `${TORRENTCLAW_API}/stream/${path}.json`;
|
| 468 |
const { data } = await axios.get(url, { timeout: 15000, headers: { 'User-Agent': UA, 'Accept': 'application/json' } });
|
| 469 |
res.json((data.streams || []).slice(0, 3));
|