diff --git a/CHANGELOG.md b/CHANGELOG.md index 703fe5f..67045f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,53 +4,32 @@ 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.6.4] - 2026-04-09 +## [0.7.0] - 2026-04-10 ### Fixed -- **Corrective message BBCode** — corrected Naming Guide link to proper `[url=…]…[/url]` BBCode syntax per bbcode.org spec. - -## [0.6.3] - 2026-04-09 - -### Fixed - -- **Corrective message BBCode** — incorrect fix (used `[url]…[/url]` without `=` attribute). - -## [0.6.2] - 2026-04-09 - -### Fixed - -- **Corrective message BBCode** — incorrect fix for the URL tag syntax (used `[url=…]` instead of `[url]…[/url]`). - -## [0.6.1] - 2026-04-09 +- **Banned groups** — replaced Luminarr list (87 groups) with DarkPeers authoritative list (60 groups + EVO/HDT conditional exceptions). False positives for Kira, 4K4U, d3g, iVy eliminated. +- **Encode resolution** — removed fake "720p minimum" rule. DarkPeers allows all resolutions (360p–4320p). +- **Opus/FLAC** — removed fake mono/stereo restriction. No channel limits on DarkPeers. +- **Dual-Audio** — corrected to DarkPeers definition: any 2 languages = Dual-Audio, 3+ = MULTi. +- **Release group** — missing group tag is now a warning, not an error. +- **Atmos detection** — checks only the default audio track, not all tracks. +- **HDR validation** — no longer gated behind 2160p/4320p. Catches "HDR10" at any resolution. +- **SRRDB search** — scene-name normalization and fallback keyword search when exact match fails. +- **Prowlarr rename detection** — ignores release group differences in self-consistency checks. AKA titles no longer cause false rename warnings. +- **Corrective messages** — replaced Luminarr §section references with "Naming Guide" link. Fixed BBCode URL syntax. Removed `img.luminarr.me`. +- **Prowlarr match summaries** — dynamically lists only fields that actually match instead of hardcoded claims. ### Added -- **Prowlarr timeout setting** — search timeout is now configurable via Tampermonkey settings (default: 15000ms). Accessible in the Integrations fieldset alongside Prowlarr URL and API Key. - -## [0.6.0] - 2026-04-09 - -### Fixed - -- **Banned groups** — replaced Luminarr list (87 groups) with DarkPeers authoritative list (60 groups + EVO/HDT conditional exceptions). False positives for Kira, 4K4U, d3g, iVy eliminated. EVO allowed for WEB-DL, HDT allowed for REMUX. -- **Encode resolution** — removed fake "720p minimum" rule. DarkPeers allows all resolutions including 360p, 480i, 480p, 576i, 576p. -- **Opus/FLAC** — removed fake mono/stereo restriction. DarkPeers has no channel limit on Opus or FLAC. -- **Dual-Audio** — corrected to DarkPeers definition: any 2 languages = Dual-Audio, 3+ = MULTi. Removed incorrect English-origin restriction. -- **Release group** — missing group tag is now a warning, not an error. DarkPeers allows uploads without a group tag. -- **Atmos detection** — now checks only the default audio track, not all tracks. Eliminates false positives from Atmos on commentary/secondary tracks. -- **HDR validation** — no longer gated behind 2160p/4320p. Validates HDR tags whenever they appear in the title, catching "HDR10" (should be "HDR") at any resolution. -- **SRRDB search** — added scene-name normalization (strip colons/apostrophes, & → and) and fallback keyword search when exact match fails. -- **Prowlarr rename detection** — self-consistency checks now ignore release group differences between torrent title and folder/file names, reducing false rename warnings. -- **Corrective messages** — replaced Luminarr §section references with plain "Naming Guide" link to DarkPeers wiki. Removed `img.luminarr.me` from image hosts. - -### Added - -- **Ignored indexers** — new setting to exclude specific indexers from Prowlarr results (e.g. TorrentLeech). Comma-separated, case-insensitive. -- **Manual re-search** — "Re-search" button in External Integrations section header. Prevents unwanted auto-searches after page edits. Click to re-query SRRDB and Prowlarr on demand. -- **Expanded indexer view** — all matching indexers shown (no longer capped at 3). First 3 inline, rest in collapsible "+N more" section. -- **Torrent age** — relative age shown next to each alternative indexer ("3d ago", "2mo ago") when Prowlarr provides publish dates. -- **Dynamic match summaries** — Prowlarr summary now lists only fields that actually match instead of hardcoded "title, year, resolution, codecs all consistent". Mismatched fields are excluded from the claim. -- **Alternative hyperlinks** — "Also found on" indexers are now clickable links to the source tracker page when URL is available. +- **Auto-search toggle** — "Auto-search on page load" setting. When off, SRRDB/Prowlarr only run when you click Search. No more automatic queries while browsing. +- **Ignored indexers** — exclude specific indexers from Prowlarr results via settings. +- **Search / Re-search button** — on-demand integration search in the External Integrations section. +- **Expanded indexer view** — all matching indexers shown (no cap). Collapsible "+N more" for overflow. +- **Torrent age** — relative age shown next to each alternative indexer. +- **Alternative hyperlinks** — "Also found on" indexers are clickable links. +- **Prowlarr timeout setting** — configurable search timeout (default: 15000ms). +- **MCP read-only test harness** — safety-enforced browser verification framework for live-site testing. ## [0.5.0] - 2026-04-06 diff --git a/modq-helper-darkpeers.user.js b/modq-helper-darkpeers.user.js index 80f7e4f..0234ef9 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.6.4 +// @version 0.7.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 @@ -1227,7 +1227,7 @@ let _resolvedModStatuses = DEFAULT_MOD_STATUSES; const Settings = { _KEY: "modq_settings", _defaults: { - prowlarr: { url: "", apiKey: "", enabled: false, preferredIndexers: [], ignoredIndexers: [], timeout: 15000 }, + prowlarr: { url: "", apiKey: "", enabled: false, preferredIndexers: [], ignoredIndexers: [], timeout: 15000, autoSearch: true }, srrdb: { enabled: true }, checks: { tmdbMatch: true, seasonEpisode: true, namingGuide: true, @@ -1368,6 +1368,11 @@ const Settings = { style="width:80px;background:#1e293b;border:1px solid #334155;color:#e2e8f0;border-radius:4px;padding:2px 6px;"> Default: 15000 + + When off, Prowlarr/SRRDB only search when you click the Search button.
@@ -3838,6 +3843,17 @@ const RenameDetector = { } catch { return null; } }, + /** + * _stripAka — Remove "AKA ..." portion from a title name. + * DP naming convention includes "Name AKA Original" but folders/filenames + * never include the AKA part, causing Jaccard mismatch. Strip it before + * comparing titleNames. + */ + _stripAka(name) { + if (!name) return name; + return name.replace(/\s+AKA\s+.*/i, "").trim(); + }, + /** Codec aliases for fuzzy matching */ _codecAliases: { "x264": "h.264", "h.264": "h.264", "avc": "h.264", @@ -3946,8 +3962,8 @@ const RenameDetector = { let weightedSum = 0; const fieldScores = {}; - // titleName — Jaccard similarity - const titleScore = this._jaccardWords(uploadTokens.titleName, resultTokens.titleName); + // titleName — Jaccard similarity (strip AKA for fair comparison) + const titleScore = this._jaccardWords(this._stripAka(uploadTokens.titleName), this._stripAka(resultTokens.titleName)); fieldScores.titleName = titleScore; weightedSum += titleScore * weights.titleName; totalWeight += weights.titleName; @@ -4088,8 +4104,10 @@ const RenameDetector = { * optionally ignores group differences. */ structurallyEquivalent(tokensA, tokensB, { ignoreGroup = false } = {}) { - // Compare title name words - const titleSim = this._jaccardWords(tokensA.titleName, tokensB.titleName); + // Compare title name words — strip AKA portion (folders never include it) + const titleA = this._stripAka(tokensA.titleName); + const titleB = this._stripAka(tokensB.titleName); + const titleSim = this._jaccardWords(titleA, titleB); if (titleSim < 0.5) return false; // Compare structural fields — must match if both present @@ -4298,7 +4316,7 @@ 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";const fs=conf.fieldScores||{};const issues=conf.issues||[];const alts=bestMatch.alternatives||[]; +
    ${T}${this._buildIntegrationPlaceholders(e, e._autoSearch)}
    `,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, autoSearch){if(!results.nogroup&&!results.dpTitle)return"";const _idle=autoSearch===false;const _srrdbContent=_idle?`
    SRRDBIdle — use the Search button above to check
    `:`
    SRRDBChecking scene database...
    `;const _prowlarrContent=_idle?`
    ProwlarrIdle — use the Search button above to check
    `:`
    ProwlarrSearching indexers...
    `;const _btnLabel=_idle?"Search":"Re-search";const _btnIcon=_idle?"fa-search":"fa-rotate";return`
    External Integrations
    ${_srrdbContent}
    ${_prowlarrContent}
    `},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}
    `; @@ -4404,6 +4422,8 @@ const Z=` color: var(--text-color, #e5e5e5); } .mh-btn--sm { font-size: 11px; padding: 2px 6px; margin-left: auto; } + .mh-btn--search { font-size: 12px; padding: 4px 12px; background: rgba(59,130,246,0.15); color: rgb(96,165,250); border: 1px solid rgba(59,130,246,0.3); border-radius: 4px; } + .mh-btn--search:hover { background: rgba(59,130,246,0.25); color: rgb(147,197,253); } .mh-body { padding: 0 !important; @@ -5034,6 +5054,9 @@ function main() { } // Create and inject panel + const _settings = Settings.load(); + const _autoSearch = _settings.prowlarr?.autoSearch !== false; + results._autoSearch = _autoSearch; const panel = U.createPanel(results); U.injectPanel(panel); @@ -5041,19 +5064,18 @@ function main() { // Async integration pipeline (DarkPeers) // Extracted into a function so the re-search button can call it again. - let _integrationSearchRun = false; + const _setIntegration = (id, html) => { + const c = document.querySelector(`#mh-${id}-container`); + if (c) c.innerHTML = html; + }; + const _loadingHtml = (label) => `
    ${label}Checking...
    `; const runIntegrationSearches = async () => { if (!dpFeatures.prowlarr && !dpFeatures.srrdb) return; const settings = Settings.load(); - // Reset placeholders to loading state on re-search - if (_integrationSearchRun) { - const sEl = document.querySelector('[data-integration="srrdb"]'); - if (sEl) sEl.outerHTML = '
    SRRDBChecking scene database...
    '; - const pEl = document.querySelector('[data-integration="prowlarr"]'); - if (pEl) pEl.outerHTML = '
    ProwlarrSearching indexers...
    '; - } - _integrationSearchRun = true; + // Show loading state + _setIntegration("srrdb", _loadingHtml("SRRDB")); + _setIntegration("prowlarr", _loadingHtml("Prowlarr")); // SRRDB: search + file comparison if (dpFeatures.srrdb && settings.srrdb?.enabled !== false) { @@ -5091,11 +5113,9 @@ function main() { } } - const el = document.querySelector('[data-integration="srrdb"]'); - if (el) el.outerHTML = U.renderIntegrationResult("SRRDB", searchResult); + _setIntegration("srrdb", U.renderIntegrationResult("SRRDB", searchResult)); } catch (err) { - const el = document.querySelector('[data-integration="srrdb"]'); - if (el) el.outerHTML = U.renderIntegrationResult("SRRDB", { error: err.message }); + _setIntegration("srrdb", U.renderIntegrationResult("SRRDB", { error: err.message })); } })(); } @@ -5129,20 +5149,17 @@ function main() { } } - const el = document.querySelector('[data-integration="prowlarr"]'); - if (el) el.outerHTML = U.renderIntegrationResult("Prowlarr", searchResult); + _setIntegration("prowlarr", U.renderIntegrationResult("Prowlarr", searchResult)); } catch (err) { - const el = document.querySelector('[data-integration="prowlarr"]'); - if (el) el.outerHTML = U.renderIntegrationResult("Prowlarr", { error: err.message }); + _setIntegration("prowlarr", U.renderIntegrationResult("Prowlarr", { error: err.message })); } })(); } else if (dpFeatures.prowlarr) { - const el = document.querySelector('[data-integration="prowlarr"]'); - if (el) el.outerHTML = U.renderIntegrationResult("Prowlarr", { notConfigured: true }); + _setIntegration("prowlarr", U.renderIntegrationResult("Prowlarr", { notConfigured: true })); } }; - // Wire up re-search button + // Wire up search/re-search button const refreshBtn = document.querySelector("#mh-integration-refresh"); if (refreshBtn) { refreshBtn.addEventListener("click", () => { @@ -5150,8 +5167,8 @@ function main() { }); } - // Run initial search - if (dpFeatures.prowlarr || dpFeatures.srrdb) { + // Run initial search (only if autoSearch is enabled) + if ((dpFeatures.prowlarr || dpFeatures.srrdb) && _autoSearch) { runIntegrationSearches(); }