From 2046e5080b1be655961b66b87cc1dacba9addf83 Mon Sep 17 00:00:00 2001 From: William Mantly Date: Wed, 1 Jul 2026 22:34:24 -0400 Subject: [PATCH] Stop Smart Search recommending the wrong movie / CAMs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TPB search is fuzzy: querying "Toy Story 5" returns Toy Story 4/3/… plus the only real 2026 copies, which are CAM/Telesync. The LLM rejected them all, then fallbackReleases picked the most-seeded (Toy Story 4), ignoring title/year. Two deterministic guards: - prefilter now drops low-quality source tags (CAM/TS/TELESYNC/SCR/…) via a token match, so neither the LLM nor the fallback can surface them. - findReleases requires the confirmed TMDB year in a movie's release name, so a fuzzy match can't cross to a different film. If nothing survives, return no options and tell the user it may be unreleased / only CAMs exist. Verified: "Toy Story 5" now returns 0 options (was Toy Story 4); Night of the Living Dead (1990) still recommends its 1080p x265 pack. Co-Authored-By: Claude Opus 4.8 --- controller/search.js | 17 ++++++++++++++++- public/partial/header.html | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/controller/search.js b/controller/search.js index 9d009fe..edd8b0d 100644 --- a/controller/search.js +++ b/controller/search.js @@ -100,9 +100,16 @@ async function tpbSearch(title){ return parseTPBHtml(await res.text()); } +// Low-quality source tags we never want to recommend( token match to avoid false hits +// like "GHOSTS" matching "TS"). +const JUNK = new Set(['cam', 'hdcam', 'camrip', 'ts', 'hdts', 'telesync', 'tc', 'telecine', 'scr', 'screener', 'dvdscr', 'workprint']); +function isJunk(name){ + return String(name).toLowerCase().split(/[.\s_\-\[\]()]+/).some(tok => JUNK.has(tok)); +} + // Deterministic pre-filter before the torrents ever reach the LLM. function prefilter(torrents, imdbId){ - let out = torrents.filter(t => videoKind(t.category) && Number(t.seeders) > 0); + let out = torrents.filter(t => videoKind(t.category) && Number(t.seeders) > 0 && !isJunk(t.name)); // If we can positively identify the title by IMDB id, trust it and drop the rest. if(imdbId){ @@ -244,6 +251,13 @@ async function findReleases(tmdbId, mediaType){ let torrents = await tpbSearch(meta.title); let candidates = prefilter(torrents, meta.imdbId); + // For movies, require the confirmed release year in the name. TPB search is fuzzy + // (a "Toy Story 5" query returns Toy Story 4/3/…), and without this the fallback can + // cross to the wrong movie. If nothing matches, we genuinely have no good copy yet. + if(mediaType !== 'tv' && meta.year){ + candidates = candidates.filter(t => t.name.includes(meta.year)); + } + if(!candidates.length){ return { title: meta.title, year: meta.year, options: [] }; } @@ -345,4 +359,5 @@ module.exports = { tpbSearch, parseTPBHtml, sizeToBytes, + isJunk, }; diff --git a/public/partial/header.html b/public/partial/header.html index 9cad9ae..c63df04 100644 --- a/public/partial/header.html +++ b/public/partial/header.html @@ -396,7 +396,7 @@ // Step 3: render the release cards; a tap feeds the existing add dialog. function tbpRenderReleases(data){ - if(!data.options.length){ tbpSearchBody('

No good torrents found for '+ tbpEsc(data.title) +'.

'); return; } + if(!data.options.length){ tbpSearchBody('

No good-quality copy of '+ tbpEsc(data.title) +' found — it may not be released yet, or only low-quality (CAM) copies exist.

'); return; } var header = '

'+ tbpEsc(data.title +' ('+ (data.year || '?') +')') +'

'; if(data.library && data.library.owned) header += '

✓ Already in your library ('+ tbpEsc(data.library.quality) +') — only upgrades are offered.

'; tbpSearchBody(header);