From 945a94782419edd47938b769518f06375337a018 Mon Sep 17 00:00:00 2001 From: Dicken Date: Thu, 11 Jun 2026 15:34:46 +0200 Subject: [PATCH] feat: Originaltitel in Klammern, xREL Jahresfilter, Wildcard-Suche (hulk*2008), Suche neueste zuerst --- backend/src/tools/media/routes.js | 48 +++++++++++++++++++++++++------ frontend/src/tools/media.jsx | 10 +++++++ 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/backend/src/tools/media/routes.js b/backend/src/tools/media/routes.js index 5e5d636..eaf8233 100644 --- a/backend/src/tools/media/routes.js +++ b/backend/src/tools/media/routes.js @@ -159,7 +159,7 @@ 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) -async function getXrelData(tmdbId, imdbId, title, originalTitle) { +async function getXrelData(tmdbId, imdbId, title, originalTitle, movieYear) { const cacheKey = `xrel:tmdb:${tmdbId}`; const cached = cacheGet(cacheKey); if (cached !== undefined) return cached; @@ -232,22 +232,31 @@ async function getXrelData(tmdbId, imdbId, title, originalTitle) { const results = []; + // Jahresfilter: Release darf nicht älter sein als Filmjahr - 1 + const releaseYearOk = (releaseDate) => { + if (!movieYear || !releaseDate) return true; + const y = parseInt(releaseDate.slice(0,4)); + return y >= movieYear - 1; + }; + for (const r of sceneData.list||[]) { if (!/\b(german|deutsch)(\.dl)?\b/i.test(r.dirname||'')) continue; if (QUALITY_BLOCKLIST(r.dirname||'')) continue; + const date = r.time ? new Date(r.time*1000).toISOString().slice(0,10) : null; + if (!releaseYearOk(date)) continue; results.push({ dirname: r.dirname, size: r.size ? `${r.size.number} ${r.size.unit}` : null, - category: 'Scene', url: r.link_href, - date: r.time ? new Date(r.time*1000).toISOString().slice(0,10) : null, p2p: false, + category: 'Scene', url: r.link_href, date, p2p: false, }); } for (const r of p2pData.list||[]) { if (r.main_lang !== 'german') continue; if (QUALITY_BLOCKLIST(r.dirname||'')) continue; + const date = r.pub_time ? new Date(r.pub_time*1000).toISOString().slice(0,10) : null; + if (!releaseYearOk(date)) continue; results.push({ dirname: r.dirname, size: r.size_mb ? `${(r.size_mb/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, + category: r.category?.sub_cat||'P2P', url: r.link_href, date, p2p: true, }); } @@ -291,15 +300,29 @@ router.get('/upcoming', authenticate, async (req, res) => { } catch(e) { res.status(500).json({ error: e.message }); } }); -// GET /api/tools/media/search?q=TITEL – Filmsuche +// GET /api/tools/media/search?q=TITEL – Filmsuche mit Wildcard-Support +// hulk*2008 → sucht "hulk" mit year=2008 router.get('/search', authenticate, async (req, res) => { try { - const q = (req.query.q || '').trim(); + let q = (req.query.q || '').trim(); if (!q || q.length < 2) return res.json([]); + + // Wildcard: z.B. "hulk*2008" → query="hulk", year="2008" + let yearFilter = null; + const wildcardMatch = q.match(/^(.+?)\*(\d{4})$/); + if (wildcardMatch) { + q = wildcardMatch[1].trim(); + yearFilter = wildcardMatch[2]; + } + + const searchParams = { query: q, include_adult: 'false' }; + if (yearFilter) searchParams.year = yearFilter; + const [results, genreMap] = await Promise.all([ - tmdb('/search/movie', { query: q, include_adult: 'false' }), + tmdb('/search/movie', searchParams), getGenreMap(), ]); + const seen = new Set(); const filtered = (results.results || []) .filter(m => { @@ -310,6 +333,9 @@ router.get('/search', authenticate, async (req, res) => { }) .slice(0, 20) .map(m => enrichMovie(m, genreMap, null, null)); + + // Neueste zuerst (Backend sortiert, Frontend sortiert auch – konsistent) + filtered.sort((a,b) => (b.release_date||'').localeCompare(a.release_date||'')); res.json(filtered); } catch(e) { res.status(500).json({ error: e.message }); } }); @@ -380,7 +406,11 @@ router.get('/movie/:id/xrel', authenticate, async (req, res) => { origTitle = detail.original_title || null; } - const data = await getXrelData(req.params.id, imdbId, title, origTitle); + const movieYear = title ? (infoData?.release_dates?.[0]?.date?.slice(0,4) ? null : null) : null; + // Filmjahr aus TMDb release_date + let filmYear = null; + if (req.query.year) filmYear = parseInt(req.query.year); + const data = await getXrelData(req.params.id, imdbId, title, origTitle, filmYear); 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 }); diff --git a/frontend/src/tools/media.jsx b/frontend/src/tools/media.jsx index a0dc9e9..43e05c7 100644 --- a/frontend/src/tools/media.jsx +++ b/frontend/src/tools/media.jsx @@ -214,6 +214,12 @@ function MovieCard({ movie, favIds, hasXrel, onToggleFav, onOpenModal }) { display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}> {movie.title} + {movie.original_title && movie.original_title !== movie.title && ( +
+ ({movie.original_title}) +
+ )} {movie.genres?.length > 0 && (
@@ -384,6 +390,8 @@ function SucheGrid({ onOpenModal, xrelIds, onXrelLoaded }) { setLoading(true); setError(''); setResults(null); try { const list = await api(`/tools/media/search?q=${encodeURIComponent(trimmed)}`); + // Neueste zuerst + list.sort((a,b) => (b.release_date||'').localeCompare(a.release_date||'')); setResults(list); // xREL-Badges aus Cache if (list.length) { @@ -568,6 +576,7 @@ function MovieModal({ tmdbId, onClose, mobile, onHasXrel }) { title: d.title || '', orig: d.original_title || '', imdb_id: d.imdb_id || '', + year: d.release_date ? d.release_date.slice(0,4) : '', }); api(`/tools/media/movie/${tmdbId}/xrel?${params}`) .then(r => { @@ -591,6 +600,7 @@ function MovieModal({ tmdbId, onClose, mobile, onHasXrel }) { title: d.title || '', orig: d.original_title || '', imdb_id: d.imdb_id || '', + year: d.release_date ? d.release_date.slice(0,4) : '', }); if (forceRefresh) params.set('refresh', '1'); const r = await api(`/tools/media/movie/${tmdbId}/xrel?${params}`);