From 8b058aaa97029e41cf85255855088b98fbcc74b1 Mon Sep 17 00:00:00 2001 From: Dicken Date: Fri, 19 Jun 2026 23:01:47 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Neuerscheinungen-Endpoint=20pro=20Anbie?= =?UTF-8?q?ter=20(zuverl=C3=A4ssig,=20nur=20Release-Datum),=20Network-ID-D?= =?UTF-8?q?ebug=20f=C3=BCr=20Originals-Recherche?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/tools/media/routes.js | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/backend/src/tools/media/routes.js b/backend/src/tools/media/routes.js index 9240f78..120273d 100644 --- a/backend/src/tools/media/routes.js +++ b/backend/src/tools/media/routes.js @@ -536,6 +536,73 @@ router.get('/streaming/providers', authenticate, (req, res) => { }); // GET /streaming/:providerId – Top 10 Filme + Serien für einen Anbieter (DE) +// GET /streaming/:providerId/new – Neuerscheinungen (letzte 60 Tage, neueste zuerst) +// Zuverlässig für ALLE Anbieter, da nur release_date + watch_provider kombiniert wird – +// keine Sprach-/Popularitäts-Heuristik nötig, die Reihenfolge ergibt sich einfach +// aus dem tatsächlichen Veröffentlichungsdatum. +router.get('/streaming/:providerId/new', authenticate, async (req, res) => { + try { + const provider = STREAMING_PROVIDERS.find(p => p.id === req.params.providerId); + if (!provider) return res.status(404).json({ error: 'Unbekannter Anbieter' }); + + const cacheKey = `streaming-new:${provider.id}`; + const cached = cacheGet(cacheKey); + if (cached !== undefined) return res.json(cached); + + const since = (() => { + const d = new Date(); + d.setDate(d.getDate() - 60); + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + })(); + const today = (() => { + const d = new Date(); + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; + })(); + + function baseParams(mediaKind) { + return { + watch_region: 'DE', + with_watch_providers: provider.tmdbId, + with_watch_monetization_types: 'flatrate', + sort_by: mediaKind === 'movie' ? 'primary_release_date.desc' : 'first_air_date.desc', + ...(mediaKind === 'movie' + ? { 'primary_release_date.gte': since, 'primary_release_date.lte': today } + : { 'first_air_date.gte': since, 'first_air_date.lte': today }), + }; + } + + const [movieData, tvData, genreMap] = await Promise.all([ + tmdb('/discover/movie', { ...baseParams('movie'), page: 1 }), + tmdb('/discover/tv', { ...baseParams('tv'), page: 1 }), + getGenreMap(), + ]); + + const movies = (movieData.results || []) + .filter(m => !BLOCKED_LANGS.has(m.original_language)) + .slice(0, 10) + .map((m, i) => ({ ...enrichMovie(m, genreMap, null, i + 1), media_type: 'movie' })); + + const tvShows = (tvData.results || []) + .filter(m => !BLOCKED_LANGS.has(m.original_language)) + .slice(0, 10) + .map((m, i) => ({ + id: m.id, media_type: 'tv', rank: i + 1, + title: m.name || m.original_name, + original_title: m.original_name, + release_date: m.first_air_date || '', + poster_path: m.poster_path || '', + genres: (m.genre_ids||[]).map(gid => genreMap[gid]).filter(Boolean), + vote_average: m.vote_average, + original_language: m.original_language, + origin_country: m.origin_country || [], + })); + + const result = { provider: { id: provider.id, label: provider.label }, movies, tvShows, since }; + cacheSet(cacheKey, result, 6 * 60 * 60 * 1000); + res.json(result); + } catch(e) { res.status(500).json({ error: e.message }); } +}); + router.get('/streaming/:providerId', authenticate, async (req, res) => { try { const provider = STREAMING_PROVIDERS.find(p => p.id === req.params.providerId); @@ -837,6 +904,14 @@ router.get('/streaming/debug/:providerId', authenticate, requireAdmin, async (re } catch(e) { res.status(500).json({ error: e.message }); } }); +// Debug: TV-Network-ID per Namenssuche finden (für Originals-Filter) +router.get('/streaming/debug-network/:name', authenticate, requireAdmin, async (req, res) => { + try { + const data = await tmdb('/search/network', { query: req.params.name }); + res.json(data.results || []); + } catch(e) { res.status(500).json({ error: e.message }); } +}); + router.post('/streaming/clear-cache', authenticate, requireAdmin, (req, res) => { const r = db.prepare("DELETE FROM xrel_cache WHERE cache_key LIKE 'streaming-top10:%'").run(); res.json({ ok: true, cleared: r.changes });