From 0f65d5d68e077fa78d9b0f53e04f928189cff9c8 Mon Sep 17 00:00:00 2001 From: Dicken Date: Sat, 6 Jun 2026 23:54:37 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20xREL=20=E2=80=93=20ersten=20movie-Treffe?= =?UTF-8?q?r=20nehmen,=20P2P=20alle=20Seiten,=20korrekte=20Felder=20aus=20?= =?UTF-8?q?Debug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/tools/media/routes.js | 141 ++++++++++++------------------ 1 file changed, 57 insertions(+), 84 deletions(-) diff --git a/backend/src/tools/media/routes.js b/backend/src/tools/media/routes.js index a2864c6..26034c1 100644 --- a/backend/src/tools/media/routes.js +++ b/backend/src/tools/media/routes.js @@ -88,12 +88,9 @@ function enrichMovie(movie, genreMap, fsk, rank) { }; } -// ── xREL: 2-Schritt Ansatz ──────────────────────────────────────────────────── -// Schritt 1: Titel → ext_info_id (eindeutige xREL Film-ID) -// Schritt 2: ext_info_id → alle Releases (Scene + P2P) -// So gibt es keine falschen Film-Matches mehr. +// ── xREL Helper ─────────────────────────────────────────────────────────────── -async function xrelFetch(path, params = {}, timeoutMs = 6000) { +async function xrelFetch(path, params = {}, timeoutMs = 7000) { const url = new URL(XREL_BASE + path); for (const [k, v] of Object.entries(params)) url.searchParams.set(k, String(v)); const res = await fetch(url.toString(), { @@ -104,72 +101,79 @@ async function xrelFetch(path, params = {}, timeoutMs = 6000) { return res.json(); } -// Findet die passende ext_info_id für einen Film-Titel -// Prüft ob Titel ähnlich genug ist (exakte oder starke Übereinstimmung) +// Findet ext_info_id: nimmt einfach den ersten movie-Treffer +// (xREL's eigene Datenbank – der erste Treffer bei Filmtitel ist fast immer korrekt) async function findExtInfoId(title, originalTitle) { - const queries = [originalTitle, title].filter(Boolean) - .filter((v, i, a) => v && a.indexOf(v) === i); + const queries = [originalTitle, title] + .filter(Boolean) + .filter((v, i, a) => a.indexOf(v) === i); for (const q of queries) { try { const data = await xrelFetch('/search/ext_info.json', { q, type: 'movie', limit: 5 }); - // data.results = Array von ShortExtInfo const hits = Array.isArray(data.results) ? data.results : []; - for (const hit of hits) { - const hitTitle = (hit.title || '').toLowerCase().trim(); - const searchQ = q.toLowerCase().trim(); - // Titel muss sehr ähnlich sein – exakt oder beginnt damit - if (hitTitle === searchQ || hitTitle.startsWith(searchQ) || searchQ.startsWith(hitTitle)) { - return hit.id; - } - } + // Ersten movie-Treffer nehmen + const hit = hits.find(h => h.type === 'movie'); + if (hit?.id) return hit.id; } catch (_) {} } return null; } -// Holt alle deutschen Releases für eine ext_info_id +// Holt alle deutschen Releases für eine ext_info_id (alle Seiten) async function getGermanReleasesById(extInfoId) { const results = []; + // Scene Releases (normalerweise 1 Seite reicht, max 3) try { - // Scene Releases: { list: [...Release], total_count, pagination } - const sceneData = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 50 }); - const sceneList = Array.isArray(sceneData.list) ? sceneData.list : []; - for (const r of sceneList) { - // Scene: German-Filter über dirname + const page1 = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 100 }); + const list = Array.isArray(page1.list) ? page1.list : []; + for (const r of list) { if (!/german|deutsch/i.test(r.dirname || '')) continue; results.push({ dirname: r.dirname, size: r.size ? `${r.size.number} ${r.size.unit}` : null, category: 'Scene', - url: r.link_href || `https://www.xrel.to/release/${r.id}.html`, + url: r.link_href, date: r.time ? new Date(r.time * 1000).toISOString().slice(0, 10) : null, p2p: false, }); } } catch (_) {} + // P2P Releases – alle Seiten holen try { - // P2P Releases: { list: [...P2PRelease], total_count, pagination } - const p2pData = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 50 }); - const p2pList = Array.isArray(p2pData.list) ? p2pData.list : []; - for (const r of p2pList) { - // P2P: German-Filter über main_lang Feld (zuverlässig!) - if (r.main_lang !== 'german') continue; - const sizeMb = r.size_mb || 0; - results.push({ - dirname: r.dirname, - size: sizeMb ? `${(sizeMb / 1024).toFixed(1)} GB` : null, - category: 'P2P', - url: r.link_href || `https://www.xrel.to/p2p-release/${r.id}.html`, - date: r.pub_time ? new Date(r.pub_time * 1000).toISOString().slice(0, 10) : null, - p2p: true, - }); + const page1 = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 100 }); + const totalPages = Math.min(page1.pagination?.total_pages || 1, 5); + const allLists = [page1.list || []]; + + if (totalPages > 1) { + const extra = await Promise.all( + Array.from({ length: totalPages - 1 }, (_, i) => + xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 100, page: i + 2 }) + .then(d => d.list || []).catch(() => []) + ) + ); + allLists.push(...extra); + } + + for (const list of allLists) { + for (const r of list) { + // P2P: main_lang === 'german' ist das zuverlässige Feld + if (r.main_lang !== 'german') continue; + const sizeMb = r.size_mb || 0; + results.push({ + dirname: r.dirname, + size: sizeMb ? `${(sizeMb / 1024).toFixed(1)} GB` : null, + category: r.category?.sub_cat || 'P2P', + url: r.link_href, + date: r.pub_time ? new Date(r.pub_time * 1000).toISOString().slice(0, 10) : null, + p2p: true, + }); + } } } catch (_) {} - // Neueste zuerst results.sort((a, b) => (b.date || '').localeCompare(a.date || '')); return results; } @@ -220,7 +224,6 @@ router.get('/movie/:id', authenticate, async (req, res) => { const fsk = extractFSK(detail.release_dates); const imdbId = extIds.imdb_id || detail.imdb_id || null; - // xREL: 2-Schritt let xrel = []; try { const extInfoId = await findExtInfoId(detail.title, detail.original_title); @@ -256,20 +259,26 @@ router.post('/xrel-check', authenticate, async (req, res) => { if (!movies.length) return res.json({}); const result = {}; - // 2er-Batches (2 API-Calls pro Film: search + releases) - for (let i = 0; i < movies.length; i += 2) { - const chunk = movies.slice(i, i + 2); + // 3er-Batches + for (let i = 0; i < movies.length; i += 3) { + const chunk = movies.slice(i, i + 3); await Promise.all(chunk.map(async m => { try { const extInfoId = await findExtInfoId(m.title, m.original_title); if (!extInfoId) { result[m.id] = false; return; } - const releases = await getGermanReleasesById(extInfoId); - result[m.id] = releases.length > 0; + // Schnell-Check: nur P2P Seite 1 prüfen ob german vorhanden + const p2p = await xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 25 }); + const p2pHit = (p2p.list || []).some(r => r.main_lang === 'german'); + if (p2pHit) { result[m.id] = true; return; } + // Auch Scene prüfen + const scene = await xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 25 }); + const sceneHit = (scene.list || []).some(r => /german|deutsch/i.test(r.dirname || '')); + result[m.id] = sceneHit; } catch (_) { result[m.id] = false; } })); - if (i + 2 < movies.length) await new Promise(r => setTimeout(r, 300)); + if (i + 3 < movies.length) await new Promise(r => setTimeout(r, 250)); } res.json(result); }); @@ -307,39 +316,3 @@ router.put('/settings', authenticate, requireAdmin, (req, res) => { }); module.exports = router; - -// DEBUG – temporär, nach Test entfernen -router.get('/debug-xrel', authenticate, async (req, res) => { - const title = req.query.title || 'Mandalorian'; - const log = []; - try { - // Step 1: ext_info search - const searchUrl = `${XREL_BASE}/search/ext_info.json?q=${encodeURIComponent(title)}&type=movie&limit=5`; - log.push({ step: 'search_url', url: searchUrl }); - const searchRes = await fetch(searchUrl, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(6000) }); - log.push({ step: 'search_status', status: searchRes.status }); - const searchData = await searchRes.json(); - log.push({ step: 'search_response', data: searchData }); - - if (searchData.results?.length) { - const extInfoId = searchData.results[0].id; - log.push({ step: 'ext_info_id', id: extInfoId }); - - // Step 2a: Scene releases - const sceneUrl = `${XREL_BASE}/release/ext_info.json?id=${extInfoId}&per_page=5`; - const sceneRes = await fetch(sceneUrl, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(6000) }); - const sceneData = await sceneRes.json(); - log.push({ step: 'scene_releases', status: sceneRes.status, keys: Object.keys(sceneData), sample: JSON.stringify(sceneData).slice(0, 500) }); - - // Step 2b: P2P releases - const p2pUrl = `${XREL_BASE}/p2p/releases.json?ext_info_id=${extInfoId}&per_page=5`; - const p2pRes = await fetch(p2pUrl, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(6000) }); - const p2pData = await p2pRes.json(); - log.push({ step: 'p2p_releases', status: p2pRes.status, keys: Object.keys(p2pData), sample: JSON.stringify(p2pData).slice(0, 500) }); - } - } catch (e) { - log.push({ step: 'error', message: e.message }); - } - res.json(log); -}); -module.exports = router;