feat: Originaltitel in Klammern, xREL Jahresfilter, Wildcard-Suche (hulk*2008), Suche neueste zuerst
This commit is contained in:
@@ -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
|
// 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)
|
// 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 cacheKey = `xrel:tmdb:${tmdbId}`;
|
||||||
const cached = cacheGet(cacheKey);
|
const cached = cacheGet(cacheKey);
|
||||||
if (cached !== undefined) return cached;
|
if (cached !== undefined) return cached;
|
||||||
@@ -232,22 +232,31 @@ async function getXrelData(tmdbId, imdbId, title, originalTitle) {
|
|||||||
|
|
||||||
const results = [];
|
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||[]) {
|
for (const r of sceneData.list||[]) {
|
||||||
if (!/\b(german|deutsch)(\.dl)?\b/i.test(r.dirname||'')) continue;
|
if (!/\b(german|deutsch)(\.dl)?\b/i.test(r.dirname||'')) continue;
|
||||||
if (QUALITY_BLOCKLIST(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({
|
results.push({
|
||||||
dirname: r.dirname, size: r.size ? `${r.size.number} ${r.size.unit}` : null,
|
dirname: r.dirname, size: r.size ? `${r.size.number} ${r.size.unit}` : null,
|
||||||
category: 'Scene', url: r.link_href,
|
category: 'Scene', url: r.link_href, date, p2p: false,
|
||||||
date: r.time ? new Date(r.time*1000).toISOString().slice(0,10) : null, p2p: false,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const r of p2pData.list||[]) {
|
for (const r of p2pData.list||[]) {
|
||||||
if (r.main_lang !== 'german') continue;
|
if (r.main_lang !== 'german') continue;
|
||||||
if (QUALITY_BLOCKLIST(r.dirname||'')) 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({
|
results.push({
|
||||||
dirname: r.dirname, size: r.size_mb ? `${(r.size_mb/1024).toFixed(1)} GB` : null,
|
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,
|
category: r.category?.sub_cat||'P2P', url: r.link_href, date, p2p: true,
|
||||||
date: r.pub_time ? new Date(r.pub_time*1000).toISOString().slice(0,10) : null, p2p: true,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,15 +300,29 @@ router.get('/upcoming', authenticate, async (req, res) => {
|
|||||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
} 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) => {
|
router.get('/search', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const q = (req.query.q || '').trim();
|
let q = (req.query.q || '').trim();
|
||||||
if (!q || q.length < 2) return res.json([]);
|
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([
|
const [results, genreMap] = await Promise.all([
|
||||||
tmdb('/search/movie', { query: q, include_adult: 'false' }),
|
tmdb('/search/movie', searchParams),
|
||||||
getGenreMap(),
|
getGenreMap(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
const filtered = (results.results || [])
|
const filtered = (results.results || [])
|
||||||
.filter(m => {
|
.filter(m => {
|
||||||
@@ -310,6 +333,9 @@ router.get('/search', authenticate, async (req, res) => {
|
|||||||
})
|
})
|
||||||
.slice(0, 20)
|
.slice(0, 20)
|
||||||
.map(m => enrichMovie(m, genreMap, null, null));
|
.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);
|
res.json(filtered);
|
||||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
} 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;
|
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 releases = Array.isArray(data) ? data : (data.releases || []);
|
||||||
const xrel_dates = Array.isArray(data) ? [] : (data.xrel_dates || []);
|
const xrel_dates = Array.isArray(data) ? [] : (data.xrel_dates || []);
|
||||||
res.json({ xrel: releases, xrel_dates, has_xrel: releases.length > 0 });
|
res.json({ xrel: releases, xrel_dates, has_xrel: releases.length > 0 });
|
||||||
|
|||||||
@@ -214,6 +214,12 @@ function MovieCard({ movie, favIds, hasXrel, onToggleFav, onOpenModal }) {
|
|||||||
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}>
|
display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical' }}>
|
||||||
{movie.title}
|
{movie.title}
|
||||||
</div>
|
</div>
|
||||||
|
{movie.original_title && movie.original_title !== movie.title && (
|
||||||
|
<div style={{ fontSize:9, color:'rgba(255,255,255,0.35)', fontFamily:'monospace',
|
||||||
|
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap', fontStyle:'italic' }}>
|
||||||
|
({movie.original_title})
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{movie.genres?.length > 0 && (
|
{movie.genres?.length > 0 && (
|
||||||
<div style={{ fontSize:9, color:'#4ecdc4', fontFamily:'monospace', overflow:'hidden',
|
<div style={{ fontSize:9, color:'#4ecdc4', fontFamily:'monospace', overflow:'hidden',
|
||||||
textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
||||||
@@ -384,6 +390,8 @@ function SucheGrid({ onOpenModal, xrelIds, onXrelLoaded }) {
|
|||||||
setLoading(true); setError(''); setResults(null);
|
setLoading(true); setError(''); setResults(null);
|
||||||
try {
|
try {
|
||||||
const list = await api(`/tools/media/search?q=${encodeURIComponent(trimmed)}`);
|
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);
|
setResults(list);
|
||||||
// xREL-Badges aus Cache
|
// xREL-Badges aus Cache
|
||||||
if (list.length) {
|
if (list.length) {
|
||||||
@@ -568,6 +576,7 @@ function MovieModal({ tmdbId, onClose, mobile, onHasXrel }) {
|
|||||||
title: d.title || '',
|
title: d.title || '',
|
||||||
orig: d.original_title || '',
|
orig: d.original_title || '',
|
||||||
imdb_id: d.imdb_id || '',
|
imdb_id: d.imdb_id || '',
|
||||||
|
year: d.release_date ? d.release_date.slice(0,4) : '',
|
||||||
});
|
});
|
||||||
api(`/tools/media/movie/${tmdbId}/xrel?${params}`)
|
api(`/tools/media/movie/${tmdbId}/xrel?${params}`)
|
||||||
.then(r => {
|
.then(r => {
|
||||||
@@ -591,6 +600,7 @@ function MovieModal({ tmdbId, onClose, mobile, onHasXrel }) {
|
|||||||
title: d.title || '',
|
title: d.title || '',
|
||||||
orig: d.original_title || '',
|
orig: d.original_title || '',
|
||||||
imdb_id: d.imdb_id || '',
|
imdb_id: d.imdb_id || '',
|
||||||
|
year: d.release_date ? d.release_date.slice(0,4) : '',
|
||||||
});
|
});
|
||||||
if (forceRefresh) params.set('refresh', '1');
|
if (forceRefresh) params.set('refresh', '1');
|
||||||
const r = await api(`/tools/media/movie/${tmdbId}/xrel?${params}`);
|
const r = await api(`/tools/media/movie/${tmdbId}/xrel?${params}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user