feat: Erscheinungsdaten von xREL (us-cine/de-cine/us-web etc.) statt TMDb

This commit is contained in:
2026-06-11 12:13:18 +02:00
parent 38d47d65f0
commit f2aa5cf328
2 changed files with 28 additions and 43 deletions

View File

@@ -125,6 +125,12 @@ function enrichMovie(m, genreMap, fsk, rank) {
}
// ── xREL ─────────────────────────────────────────────────────────────────────
const XREL_DATE_LABEL = {
'us-cine': 'Kinostart (US)', 'de-cine': 'Kinostart (DE)',
'us-web': 'Web (US)', 'de-web': 'Web (DE)',
'us-disc': 'Disc (US)', 'de-disc': 'Disc (DE)',
};
const QUALITY_BLOCKLIST = r => /\b(2160p|4k|uhd|uhdbluray|uhdbd|remux)\b/i.test(r) || /COMPLETE[._](?:BLURAY|BD)/i.test(r);
async function xrelFetch(path, params = {}) {
@@ -145,27 +151,6 @@ async function xrelFetch(path, params = {}) {
// Einzige Funktion für xREL: benutzt immer die ext_info_id und gibt max 5 deutsche ≤1080p Releases
// Cache-Key: tmdb_id (immer eindeutig, konsistent zwischen Badge und Modal)
// xREL Erscheinungsdaten via /ext_info/info.json
const XREL_DATE_LABEL = {
'us-cine': 'Kinostart (US)',
'de-cine': 'Kinostart (DE)',
'us-web': 'Web (US)',
'de-web': 'Web (DE)',
'us-disc': 'Disc (US)',
'de-disc': 'Disc (DE)',
};
async function getXrelReleaseDates(extInfoId) {
try {
const data = await xrelFetch('/ext_info/info.json', { id: extInfoId, type: 'movie' });
if (!Array.isArray(data.release_dates)) return [];
return data.release_dates
.filter(r => XREL_DATE_LABEL[r.type] && r.date)
.map(r => ({ label: XREL_DATE_LABEL[r.type], date: r.date }))
.sort((a, b) => a.date.localeCompare(b.date));
} catch(_) { return []; }
}
async function getXrelData(tmdbId, imdbId, title, originalTitle) {
const cacheKey = `xrel:tmdb:${tmdbId}`;
const cached = cacheGet(cacheKey);
@@ -224,13 +209,19 @@ async function getXrelData(tmdbId, imdbId, title, originalTitle) {
return [];
}
// Releases + Erscheinungsdaten parallel holen
const [sceneData, p2pData, xrelDates] = await Promise.all([
// Releases + ext_info Details parallel holen
const [sceneData, p2pData, infoData] = await Promise.all([
xrelFetch('/release/ext_info.json', { id: extInfoId, per_page: 50 }).catch(()=>({})),
xrelFetch('/p2p/releases.json', { ext_info_id: extInfoId, per_page: 50 }).catch(()=>({})),
getXrelReleaseDates(extInfoId),
xrelFetch('/ext_info/info.json', { id: extInfoId, type: 'movie' }).catch(()=>({})),
]);
// xREL Erscheinungsdaten
const xrel_dates = (infoData.release_dates||[])
.filter(d => XREL_DATE_LABEL[d.type])
.map(d => ({ label: XREL_DATE_LABEL[d.type], date: d.date }))
.sort((a,b) => a.date.localeCompare(b.date));
const results = [];
for (const r of sceneData.list||[]) {
@@ -254,9 +245,8 @@ async function getXrelData(tmdbId, imdbId, title, originalTitle) {
results.sort((a,b) => (b.date||'').localeCompare(a.date||''));
const top5 = results.slice(0, 5);
const payload = { releases: top5, dates: xrelDates };
const ttl = (top5.length > 0 || xrelDates.length > 0) ? 12*60*60*1000 : 60*60*1000;
cacheSet(cacheKey, payload, ttl);
const payload = { releases: top5, xrel_dates };
cacheSet(cacheKey, payload, top5.length > 0 || xrel_dates.length > 0 ? 12*60*60*1000 : 60*60*1000);
return payload;
}
@@ -332,9 +322,9 @@ router.get('/movie/:id/xrel', authenticate, async (req, res) => {
const cached = cacheGet(cacheKey);
if (cached !== undefined) {
const releases = Array.isArray(cached) ? cached : (cached.releases || []);
const dates = Array.isArray(cached) ? [] : (cached.dates || []);
if (releases.length > 0 || dates.length > 0) {
return res.json({ xrel: releases, dates, has_xrel: releases.length > 0 });
const xrel_dates = Array.isArray(cached) ? [] : (cached.xrel_dates || []);
if (releases.length > 0 || xrel_dates.length > 0) {
return res.json({ xrel: releases, xrel_dates, has_xrel: releases.length > 0 });
}
}
}
@@ -359,10 +349,10 @@ router.get('/movie/:id/xrel', authenticate, async (req, res) => {
origTitle = detail.original_title || null;
}
const payload = await getXrelData(req.params.id, imdbId, title, origTitle);
const releases = Array.isArray(payload) ? payload : (payload?.releases || []);
const dates = Array.isArray(payload) ? [] : (payload?.dates || []);
res.json({ xrel: releases, dates, has_xrel: releases.length > 0 });
const data = await getXrelData(req.params.id, imdbId, title, origTitle);
const releases = Array.isArray(data) ? data : (data.releases || []);
const xrel_dates = Array.isArray(data) ? [] : (data.xrel_dates || []);
res.json({ xrel: releases, xrel_dates, has_xrel: releases.length > 0 });
} catch(e) { res.status(500).json({ error: e.message }); }
});
@@ -375,9 +365,7 @@ router.get('/xrel-cached', authenticate, (req, res) => {
const result = {};
for (const id of ids) {
const cached = cacheGet(`xrel:tmdb:${id}`);
if (cached === undefined) { result[id] = false; continue; }
const releases = Array.isArray(cached) ? cached : (cached.releases || []);
result[id] = releases.length > 0;
result[id] = cached !== undefined && (Array.isArray(cached) ? cached.length > 0 : cached.releases?.length > 0);
}
res.json(result);
});