diff --git a/CHANGELOG.md b/CHANGELOG.md index 01f7578..9616f4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to the DarkPeers Mod Queue Helper will be documented in this The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/). +## [0.5.0] - 2026-04-06 + +### Added + +- **Preferred indexer selection** — new `preferredIndexers` setting (comma-separated list in Settings panel) allows moderators to specify trusted indexers. When multiple indexers return the same release (tied relevance scores), preferred indexers are selected first. Preference is strictly a tiebreaker — never overrides a clearly better match from a non-preferred indexer. +- **Best match link** — best match title now links to the source tracker page via Prowlarr's `infoUrl` field (opens in new tab). Link omitted gracefully when URL unavailable. +- **Field comparison detail** — collapsible "Comparison details" section shows per-field match status (title, year, resolution, source, video codec, audio codec), self-consistency check results (folder ✓/✗, filename ✓/✗), and alternative indexers. +- **Alternative indexers** — up to 3 alternative indexers shown in detail section ("Also found on: BLU, TL") + +### Changed + +- **Explainability rewrite** — replaced vague "Title consistent with indexed release" with specific moderation summaries: + - `match`: "Name matches release on {indexer} — title, year, resolution, codecs all consistent" + - `likely_match`: "Name likely matches release on {indexer} — minor field differences" + - `uncertain`: "No strong match found — cannot verify release name automatically" + - `likely_renamed`: "Possible rename — {specific issue}" + - `renamed`: "Likely renamed — folder and filename both differ from torrent name" +- **Rename issue labels** — replaced misleading "expected/found" framing with "Torrent name: / Folder name:" to avoid implying the torrent name is the authoritative source +- **Header wording** — "Found on N indexer(s)" replaced with "Found on {indexer}" (single) or "Best match from {indexer} (N indexers total)" (multiple) +- **Match selection algorithm** — replaced single-pass highest-score selection with candidate-based ranking: relevance score (descending, 0.01 epsilon for ties) → preferred indexer rank → seeders (descending) + ## [0.4.1] - 2026-04-06 ### Fixed diff --git a/README.md b/README.md index f4dc8bc..995e7cb 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,8 @@ Tampermonkey checks for updates automatically. To force a check: Tampermonkey ic ## Optional: Prowlarr Integration -Open the settings via Tampermonkey menu > **ModQ Helper Settings** and enter your Prowlarr URL and API key to enable cross-seed detection. +Open the settings via Tampermonkey menu > **ModQ Helper Settings** and enter your Prowlarr URL and API key to enable indexer-backed rename detection. + +### Preferred Indexers + +In the settings panel you can optionally set a comma-separated list of preferred indexers (e.g. `Aither, BLU`). When multiple indexers return the same release, the preferred one is shown as the best match. This is a tiebreaker only — a clearly better match from another indexer still wins. diff --git a/modq-helper-darkpeers.user.js b/modq-helper-darkpeers.user.js index c852a6d..c6b489f 100644 --- a/modq-helper-darkpeers.user.js +++ b/modq-helper-darkpeers.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name UNIT3D Mod Queue Helper — DarkPeers // @namespace https://gitea.computerliebe.org/Procuria/dp-modq-helper -// @version 0.4.1 +// @version 0.5.0 // @description Quality-gate checks for DarkPeers — extended moderation rules, title validation, SRRDB & Prowlarr integrations // @author TQG Contributors // @updateURL https://gitea.computerliebe.org/Procuria/dp-modq-helper/raw/branch/main/modq-helper-darkpeers.user.js @@ -1226,7 +1226,7 @@ let _resolvedModStatuses = DEFAULT_MOD_STATUSES; const Settings = { _KEY: "modq_settings", _defaults: { - prowlarr: { url: "", apiKey: "", enabled: false }, + prowlarr: { url: "", apiKey: "", enabled: false, preferredIndexers: [] }, srrdb: { enabled: true }, checks: { tmdbMatch: true, seasonEpisode: true, namingGuide: true, @@ -1349,6 +1349,12 @@ const Settings = { +
@@ -1401,9 +1407,13 @@ const Settings = { const s = self.load(); modal.querySelectorAll("[data-setting]").forEach(el => { const key = el.getAttribute("data-setting"); - const val = el.type === "checkbox" ? el.checked - : el.type === "number" ? parseInt(el.value, 10) - : el.value; + let val = el.type === "checkbox" ? el.checked + : el.type === "number" ? parseInt(el.value, 10) + : el.value; + // Parse comma-separated list settings into arrays + if (key === "prowlarr.preferredIndexers") { + val = typeof val === "string" ? val.split(",").map(s => s.trim()).filter(Boolean) : []; + } const parts = key.split("."); let target = s; for (let i = 0; i < parts.length - 1; i++) { @@ -3915,19 +3925,70 @@ const RenameDetector = { }, /** - * findBestMatch — Select the best Prowlarr result by relevance score. + * _indexerRank — Return preference rank for an indexer name. + * Lower = more preferred. Infinity = not in preference list. */ - findBestMatch(uploadTokens, results) { - let best = null; - let bestScore = -1; + _indexerRank(indexerName, preferredList) { + if (!preferredList || preferredList.length === 0) return Infinity; + const idx = preferredList.findIndex( + p => p.toLowerCase() === (indexerName || "").toLowerCase() + ); + return idx === -1 ? Infinity : idx; + }, + + /** + * findBestMatch — Select the best Prowlarr result using candidate ranking. + * + * Sort order: + * 1. relevanceScore (descending) — scores within 0.01 epsilon are ties + * 2. preferredIndexer rank (ascending — lower = more preferred) + * 3. seeders (descending — liveness tiebreaker) + * + * Also tracks up to 3 alternative candidates (score > 0.50) for display. + */ + findBestMatch(uploadTokens, results, preferredIndexers) { + const EPSILON = 0.01; + const candidates = []; + for (const r of results) { const tokens = this.tokenize(r.title || ""); const score = this.scoreMatch(uploadTokens, tokens); - if (score.relevanceScore > bestScore) { - bestScore = score.relevanceScore; - best = { title: r.title, indexer: r.indexer, size: r.size, tokens, score, relevanceScore: score.relevanceScore }; + candidates.push({ + title: r.title, indexer: r.indexer, size: r.size, + infoUrl: r.infoUrl || null, guid: r.guid || null, seeders: r.seeders || 0, + tokens, score, relevanceScore: score.relevanceScore, + }); + } + + if (candidates.length === 0) return null; + + const prefs = Array.isArray(preferredIndexers) ? preferredIndexers : []; + + candidates.sort((a, b) => { + // Primary: relevanceScore descending (ties within epsilon) + const scoreDiff = b.relevanceScore - a.relevanceScore; + if (Math.abs(scoreDiff) > EPSILON) return scoreDiff; + // Secondary: preferred indexer rank ascending + const rankA = this._indexerRank(a.indexer, prefs); + const rankB = this._indexerRank(b.indexer, prefs); + if (rankA !== rankB) return rankA - rankB; + // Tertiary: seeders descending + return (b.seeders || 0) - (a.seeders || 0); + }); + + const best = candidates[0]; + // Collect alternatives: distinct indexers, score > 0.50, not the best match + const seen = new Set([best.indexer]); + const alternatives = []; + for (let i = 1; i < candidates.length && alternatives.length < 3; i++) { + const c = candidates[i]; + if (c.relevanceScore >= 0.50 && !seen.has(c.indexer)) { + seen.add(c.indexer); + alternatives.push({ indexer: c.indexer, title: c.title, relevanceScore: c.relevanceScore }); } } + best.alternatives = alternatives; + return best; }, @@ -4164,7 +4225,30 @@ const U={getStatusIcon(e){switch(e){case"pass":return' -
${T}${this._buildIntegrationPlaceholders(e)}
`,n},injectPanel(e){const n=E.getModerationPanel();if(n)n.parentNode.insertBefore(e,n);else{const t=document.querySelector(_resolvedSelectors.torrentTags);t&&t.parentNode.insertBefore(e,t.nextSibling)}this.attachEvents(e)},attachEvents(e){const n=e.querySelector("#mh-toggle-all");if(n){let D=!1;n.addEventListener("click",()=>{D=!D,e.querySelectorAll(".mh-accordion, .mh-section").forEach(x=>x.open=D),n.querySelector("i").className=D?"fas fa-angles-up":"fas fa-angles-down",n.title=D?"Collapse all sections":"Expand all sections"})}const t=e.querySelector("#mh-corrective-text"),s=e.querySelector("#mh-corrective-editor");t&&s&&(t.addEventListener("click",()=>{t.style.display="none",s.style.display="block",s.focus()}),s.addEventListener("blur",()=>{t.textContent=s.value,t.style.display="",s.style.display=""}));const a=()=>s?s.value:"",o=e.querySelector("#mh-copy-message");o&&o.addEventListener("click",()=>{const D=a();navigator.clipboard.writeText(D).then(()=>{const x=o.querySelector("i");x.className="fas fa-check",o.classList.add("mh-btn--success"),setTimeout(()=>{x.className="fas fa-copy",o.classList.remove("mh-btn--success")},2e3)})});const c=D=>{const x=document.querySelectorAll(_resolvedSelectors.moderationForms);for(const p of x){const y=p.querySelector(_resolvedSelectors.moderationStatus);if(y&&y.value===D)return p.querySelector(_resolvedSelectors.moderationMessage)}return null},r=(D,x)=>{const p=e.querySelector(D);p&&p.addEventListener("click",()=>{const y=a(),C=c(x);if(C){C.value=y,C.dispatchEvent(new Event("input",{bubbles:!0}));const h=p.querySelector("i");h.className="fas fa-check",p.classList.add("mh-btn--success"),setTimeout(()=>{h.className="fas fa-paste",p.classList.remove("mh-btn--success")},2e3)}else{const h=p.querySelector("i");h.className="fas fa-xmark",p.classList.add("mh-btn--fail"),setTimeout(()=>{h.className="fas fa-paste",p.classList.remove("mh-btn--fail")},2e3)}})};r("#mh-fill-postpone",_resolvedModStatuses.postpone),r("#mh-fill-reject",_resolvedModStatuses.reject);const d=e.querySelector("#mh-filename-compare-btn"),f=e.querySelector("#mh-filename-block"),A=e.querySelector("#mh-filename-uploaded"),T=e.querySelector("#mh-filename-input"),b=e.querySelector("#mh-filename-run"),u=e.querySelector("#mh-filename-result");if(d&&f&&d.addEventListener("click",()=>{const D=f.style.display!=="none";if(f.style.display=D?"none":"",d.classList.toggle("mh-btn--active",!D),!D&&A){const x=E.getMediaInfoFilename()||"(not found)";A.textContent=x}}),b&&T&&u){const D=()=>{const x=T.value.trim();if(!x){u.innerHTML='Please enter a reference filename.';return}const p=E.getMediaInfoFilename();if(!p||p==="(not found)"){u.innerHTML='Could not read MediaInfo filename from page.';return}const y=z.diff(x,p);u.innerHTML=z.renderDiff(y)};b.addEventListener("click",D),T.addEventListener("keydown",x=>{x.key==="Enter"&&D()})}},_buildIntegrationPlaceholders(results){if(!results.nogroup&&!results.dpTitle)return"";return`
External Integrations
SRRDBChecking scene database...
ProwlarrSearching indexers...
`},renderIntegrationResult(name,result){const esc=(s)=>String(s||"").replace(/&/g,"&").replace(//g,">");if(result.error){return`
${esc(name)}${esc(result.error)}
`}if(result.notConfigured){return`
${esc(name)}Not configured — open ModQ Helper Settings
`}if(name==="SRRDB"){if(!result.found){return`
SRRDBNot found — may not be a scene release
`}const relName=esc(result.release?.release||"Unknown");let fileHtml="";if(result.fileCheck){if(result.fileCheck.match){fileHtml=`
File/folder names match SRRDB record
`}else if(result.fileCheck.error){fileHtml=`
Could not verify files: ${esc(result.fileCheck.error)}
`}else{const diffs=(result.fileCheck.discrepancies||[]).map(d=>`
  • ${esc(d)}
  • `).join("");fileHtml=`
    File names differ from SRRDB record — possible rename (REJECT per A1.1)${diffs?`
      ${diffs}
    `:""}
    `}}return`
    SRRDBScene release found: ${relName}${fileHtml}
    `}if(name==="Prowlarr"){if(!result.found){return`
    ProwlarrRelease not indexed — may be new or not tracked
    `}const count=result.results?.length||0;const bestMatch=result.bestMatch;let matchHtml="";if(bestMatch){const conf=bestMatch.confidence||{};const confLevel=conf.level||"uncertain";matchHtml+=`
    Best match: ${esc(bestMatch.title)} [${esc(bestMatch.indexer)}]
    `;if(confLevel==="match"||confLevel==="likely_match"){matchHtml+=`
    Title consistent with indexed release
    `}else if(confLevel==="uncertain"){matchHtml+=`
    Cannot confidently assess rename status — review manually
    `}else if(confLevel==="likely_renamed"||confLevel==="renamed"){const issueItems=(conf.issues||[]).map(i=>{if(i.type==="folder")return`
  • Folder differs: expected ${esc(i.expected)}, found ${esc(i.found)}
  • `;if(i.type==="filename")return`
  • Filename differs: expected ${esc(i.expected)}, found ${esc(i.found)}
  • `;return`
  • Mismatch: expected ${esc(i.expected)}, found ${esc(i.found)}
  • `}).join("");const renameMsg=confLevel==="renamed"?"Strong evidence of renaming — review filenames":"Possible rename detected — review filenames";matchHtml+=`
    ${renameMsg}${issueItems?`
      ${issueItems}
    `:""}
    `}if(conf.note){matchHtml+=`
    ${esc(conf.note)}
    `}}const prowlStatusMap={"match":"pass","likely_match":"pass","uncertain":"advisory","likely_renamed":"warn","renamed":"warn"};const prowlIconMap={"match":"check-circle mh-icon--pass","likely_match":"check-circle mh-icon--pass","uncertain":"info-circle mh-icon--advisory","likely_renamed":"exclamation-triangle mh-icon--warn","renamed":"exclamation-triangle mh-icon--warn"};const confLevel=bestMatch?.confidence?.level||"uncertain";const prowlStatus=prowlStatusMap[confLevel]||"advisory";const prowlIcon=prowlIconMap[confLevel]||"info-circle mh-icon--advisory";return`
    ProwlarrFound on ${count} indexer(s)${matchHtml}
    `}return`
    ${esc(name)}${result.found?"Found":"Not found"}
    `}}; +
    ${T}${this._buildIntegrationPlaceholders(e)}
    `,n},injectPanel(e){const n=E.getModerationPanel();if(n)n.parentNode.insertBefore(e,n);else{const t=document.querySelector(_resolvedSelectors.torrentTags);t&&t.parentNode.insertBefore(e,t.nextSibling)}this.attachEvents(e)},attachEvents(e){const n=e.querySelector("#mh-toggle-all");if(n){let D=!1;n.addEventListener("click",()=>{D=!D,e.querySelectorAll(".mh-accordion, .mh-section").forEach(x=>x.open=D),n.querySelector("i").className=D?"fas fa-angles-up":"fas fa-angles-down",n.title=D?"Collapse all sections":"Expand all sections"})}const t=e.querySelector("#mh-corrective-text"),s=e.querySelector("#mh-corrective-editor");t&&s&&(t.addEventListener("click",()=>{t.style.display="none",s.style.display="block",s.focus()}),s.addEventListener("blur",()=>{t.textContent=s.value,t.style.display="",s.style.display=""}));const a=()=>s?s.value:"",o=e.querySelector("#mh-copy-message");o&&o.addEventListener("click",()=>{const D=a();navigator.clipboard.writeText(D).then(()=>{const x=o.querySelector("i");x.className="fas fa-check",o.classList.add("mh-btn--success"),setTimeout(()=>{x.className="fas fa-copy",o.classList.remove("mh-btn--success")},2e3)})});const c=D=>{const x=document.querySelectorAll(_resolvedSelectors.moderationForms);for(const p of x){const y=p.querySelector(_resolvedSelectors.moderationStatus);if(y&&y.value===D)return p.querySelector(_resolvedSelectors.moderationMessage)}return null},r=(D,x)=>{const p=e.querySelector(D);p&&p.addEventListener("click",()=>{const y=a(),C=c(x);if(C){C.value=y,C.dispatchEvent(new Event("input",{bubbles:!0}));const h=p.querySelector("i");h.className="fas fa-check",p.classList.add("mh-btn--success"),setTimeout(()=>{h.className="fas fa-paste",p.classList.remove("mh-btn--success")},2e3)}else{const h=p.querySelector("i");h.className="fas fa-xmark",p.classList.add("mh-btn--fail"),setTimeout(()=>{h.className="fas fa-paste",p.classList.remove("mh-btn--fail")},2e3)}})};r("#mh-fill-postpone",_resolvedModStatuses.postpone),r("#mh-fill-reject",_resolvedModStatuses.reject);const d=e.querySelector("#mh-filename-compare-btn"),f=e.querySelector("#mh-filename-block"),A=e.querySelector("#mh-filename-uploaded"),T=e.querySelector("#mh-filename-input"),b=e.querySelector("#mh-filename-run"),u=e.querySelector("#mh-filename-result");if(d&&f&&d.addEventListener("click",()=>{const D=f.style.display!=="none";if(f.style.display=D?"none":"",d.classList.toggle("mh-btn--active",!D),!D&&A){const x=E.getMediaInfoFilename()||"(not found)";A.textContent=x}}),b&&T&&u){const D=()=>{const x=T.value.trim();if(!x){u.innerHTML='Please enter a reference filename.';return}const p=E.getMediaInfoFilename();if(!p||p==="(not found)"){u.innerHTML='Could not read MediaInfo filename from page.';return}const y=z.diff(x,p);u.innerHTML=z.renderDiff(y)};b.addEventListener("click",D),T.addEventListener("keydown",x=>{x.key==="Enter"&&D()})}},_buildIntegrationPlaceholders(results){if(!results.nogroup&&!results.dpTitle)return"";return`
    External Integrations
    SRRDBChecking scene database...
    ProwlarrSearching indexers...
    `},renderIntegrationResult(name,result){const esc=(s)=>String(s||"").replace(/&/g,"&").replace(//g,">");if(result.error){return`
    ${esc(name)}${esc(result.error)}
    `}if(result.notConfigured){return`
    ${esc(name)}Not configured — open ModQ Helper Settings
    `}if(name==="SRRDB"){if(!result.found){return`
    SRRDBNot found — may not be a scene release
    `}const relName=esc(result.release?.release||"Unknown");let fileHtml="";if(result.fileCheck){if(result.fileCheck.match){fileHtml=`
    File/folder names match SRRDB record
    `}else if(result.fileCheck.error){fileHtml=`
    Could not verify files: ${esc(result.fileCheck.error)}
    `}else{const diffs=(result.fileCheck.discrepancies||[]).map(d=>`
  • ${esc(d)}
  • `).join("");fileHtml=`
    File names differ from SRRDB record — possible rename (REJECT per A1.1)${diffs?`
      ${diffs}
    `:""}
    `}}return`
    SRRDBScene release found: ${relName}${fileHtml}
    `}if(name==="Prowlarr"){if(!result.found){return`
    ProwlarrRelease not indexed — may be new or not tracked
    `}const count=result.results?.length||0;const bestMatch=result.bestMatch;let matchHtml="";if(bestMatch){const conf=bestMatch.confidence||{};const confLevel=conf.level||"uncertain";const fs=conf.fieldScores||{};const issues=conf.issues||[];const alts=bestMatch.alternatives||[]; +// Best match title with optional link +const titleText=esc(bestMatch.title);const indexerText=esc(bestMatch.indexer);const linkHtml=bestMatch.infoUrl?` `:""; +matchHtml+=`
    Best match: ${titleText} [${indexerText}]${linkHtml}
    `; +// Summary line — specific to confidence level +const summaryMessages={"match":`Name matches release on ${indexerText} — title, year, resolution, codecs all consistent`,"likely_match":`Name likely matches release on ${indexerText} — minor field differences`,"uncertain":"No strong match found — cannot verify release name automatically","likely_renamed":issues.length>0?`Possible rename — ${issues[0].type==="folder"?"folder name differs from torrent name":"filename differs from torrent name"}`:"Possible rename detected — review filenames","renamed":"Likely renamed — folder and filename both differ from torrent name"}; +const summaryIcons={"match":"check-circle mh-icon--pass","likely_match":"check-circle mh-icon--pass","uncertain":"info-circle mh-icon--advisory","likely_renamed":"exclamation-triangle mh-icon--warn","renamed":"exclamation-triangle mh-icon--warn"}; +const summaryClasses={"match":"pass","likely_match":"pass","uncertain":"advisory","likely_renamed":"warn","renamed":"warn"}; +matchHtml+=`
    ${summaryMessages[confLevel]||"Review manually"}
    `; +// Rename issues — reframed labels +if(confLevel==="likely_renamed"||confLevel==="renamed"){const issueItems=issues.map(i=>{if(i.type==="folder")return`
  • Torrent name: ${esc(i.expected)}
    Folder name: ${esc(i.found)}
  • `;if(i.type==="filename")return`
  • Torrent name: ${esc(i.expected)}
    Filename: ${esc(i.found)}
  • `;return`
  • ${esc(i.expected)} vs ${esc(i.found)}
  • `}).join("");if(issueItems)matchHtml+=``} +// Group note +if(conf.note&&confLevel!=="likely_renamed"&&confLevel!=="renamed"){matchHtml+=`
    ${esc(conf.note)}
    `} +// Collapsible field comparison detail +const fieldLabels={titleName:"Title",year:"Year",resolution:"Resolution",source:"Source",vcodec:"Video codec",acodec:"Audio codec"};const fieldEntries=Object.entries(fs).filter(([k])=>fieldLabels[k]);if(fieldEntries.length>0){let detailRows=fieldEntries.map(([k,v])=>{const label=fieldLabels[k];const icon=v>=1.0?'':v>0?'':'';return`
    ${icon} ${label}
    `}).join(""); +// Self-consistency summary +const selfIssues=issues.filter(i=>i.severity==="high");const folderOk=!selfIssues.some(i=>i.type==="folder");const fileOk=!selfIssues.some(i=>i.type==="filename");detailRows+=`
    Self-check: folder ${folderOk?"✓":"✗"} · filename ${fileOk?"✓":"✗"}
    `; +// Alternatives +if(alts.length>0){const altNames=alts.map(a=>esc(a.indexer)).join(", ");detailRows+=`
    Also found on: ${altNames}
    `} +matchHtml+=`
    Comparison details
    ${detailRows}
    `}} +const prowlStatusMap={"match":"pass","likely_match":"pass","uncertain":"advisory","likely_renamed":"warn","renamed":"warn"};const prowlIconMap={"match":"check-circle mh-icon--pass","likely_match":"check-circle mh-icon--pass","uncertain":"info-circle mh-icon--advisory","likely_renamed":"exclamation-triangle mh-icon--warn","renamed":"exclamation-triangle mh-icon--warn"};const confLevel=bestMatch?.confidence?.level||"uncertain";const prowlStatus=prowlStatusMap[confLevel]||"advisory";const prowlIcon=prowlIconMap[confLevel]||"info-circle mh-icon--advisory"; +// Header: precise indexer wording +const headerMsg=count===1?`Found on ${esc(bestMatch?.indexer||"1 indexer")}`:`Best match from ${esc(bestMatch?.indexer||"unknown")} (${count} indexers total)`; +return`
    Prowlarr${headerMsg}${matchHtml}
    `}return`
    ${esc(name)}${result.found?"Found":"Not found"}
    `}}; /* ======================================================================== * CSS — Panel styles (injected via GM_addStyle) @@ -4740,6 +4824,22 @@ const Z=` .mh-integration__diffs { margin: 4px 0 0 16px; padding: 0; list-style: disc; font-size: 11px; } .mh-integration__compare { margin-top: 4px; font-size: 11px; opacity: 0.85; } .mh-integration__compare div { padding: 2px 0; } + .mh-integration__detail--advisory { color: rgba(96, 165, 250, 0.80); } + .mh-integration__detail--note { color: var(--text-color, #8c8c8c); } + .mh-integration__link { color: rgba(96, 165, 250, 0.70); text-decoration: none; margin-left: 4px; font-size: 11px; } + .mh-integration__link:hover { color: rgba(96, 165, 250, 1); } + .mh-integration__expand { margin-top: 6px; } + .mh-integration__expand-trigger { cursor: pointer; list-style: none; color: rgba(96, 165, 250, 0.70); user-select: none; } + .mh-integration__expand-trigger::-webkit-details-marker, + .mh-integration__expand-trigger::marker { display: none; } + .mh-integration__expand-trigger::before { content: "▸ "; } + .mh-integration__expand[open] > .mh-integration__expand-trigger::before { content: "▾ "; } + .mh-integration__fields { padding: 4px 0 0 8px; } + .mh-field-row { font-size: 11px; padding: 1px 0; color: var(--text-color, #cbd5e1); display: flex; align-items: center; gap: 4px; } + .mh-field-row i { font-size: 10px; width: 12px; text-align: center; } + .mh-field-label { min-width: 80px; } + .mh-field-row--self { margin-top: 4px; padding-top: 4px; border-top: 1px solid rgba(59, 61, 62, 0.40); color: var(--text-color, #8c8c8c); } + .mh-field-row--alts { color: var(--text-color, #8c8c8c); } `;; /* ======================================================================== @@ -4911,7 +5011,8 @@ function main() { const searchResult = await Integrations.prowlarr.search(settings.prowlarr, tvScope.searchQuery); if (searchResult.found && searchResult.results.length > 0) { - const bestMatch = RenameDetector.findBestMatch(uploadTokens, searchResult.results); + const preferredIndexers = settings.prowlarr?.preferredIndexers || []; + const bestMatch = RenameDetector.findBestMatch(uploadTokens, searchResult.results, preferredIndexers); if (bestMatch) { const assessment = RenameDetector.assessRename(data, bestMatch, tvScope); @@ -4919,9 +5020,11 @@ function main() { title: bestMatch.title, indexer: bestMatch.indexer, size: bestMatch.size, + infoUrl: bestMatch.infoUrl, uploadedTitle: data.torrentName, relevanceScore: bestMatch.relevanceScore, confidence: assessment, + alternatives: bestMatch.alternatives || [], }; } }