From 6ff9f86bd8d6612485e505ff25cd76c4e6f4441d Mon Sep 17 00:00:00 2001 From: Jonathan Sykes Date: Wed, 30 Sep 2026 00:17:44 +0800 Subject: [PATCH] cache /api/search results for 3 minutes to skip repeat yt-dlp spawns --- server/server.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index 0a63bbc..5e5ae6c 100644 --- a/server/server.js +++ b/server/server.js @@ -353,11 +353,25 @@ app.get('/api/version', (c) => ) ); +// Same query repeats a lot (users re-searching, several devices, the +// landing-page chips) and yt-dlp's own search takes several seconds — cache +// the combined (library + YouTube) result for a short TTL. Same shape as +// resolveStreams()' streamCache below, just keyed by normalized query text +// instead of videoId. Only a full success is cached, so a transient yt-dlp +// failure still gets retried on the next request. +const SEARCH_CACHE_MAX = 100; +const SEARCH_CACHE_TTL_MS = 3 * 60_000; +const searchCache = new Map(); // lowercased q -> { results, expiresAt } + // GET /api/search?q= app.get('/api/search', async (c) => { const q = (c.req.query('q') || '').trim(); if (!q) return c.json({ ok: false, error: 'empty query' }, 400); + const cacheKey = q.toLowerCase(); + const cached = searchCache.get(cacheKey); + if (cached && Date.now() < cached.expiresAt) return c.json({ ok: true, results: cached.results }); + // This server's own library first — and it still answers when YouTube // (yt-dlp) is unreachable or rate-limited. let mine = []; @@ -368,7 +382,10 @@ app.get('/api/search', async (c) => { '--dump-json', '--flat-playlist', '--no-warnings', '--ignore-errors', ]); - return c.json({ ok: true, results: [...mine, ...parseCards(out)] }); + const results = [...mine, ...parseCards(out)]; + if (searchCache.size >= SEARCH_CACHE_MAX) searchCache.delete(searchCache.keys().next().value); + searchCache.set(cacheKey, { results, expiresAt: Date.now() + SEARCH_CACHE_TTL_MS }); + return c.json({ ok: true, results }); } catch (err) { if (mine.length) return c.json({ ok: true, results: mine, youtubeError: err.message }); return c.json({ ok: false, error: err.message }, 500);