From 1796314db6a95a4348d187cc2d2f231452e96a88 Mon Sep 17 00:00:00 2001 From: Ryahn Date: Thu, 18 Sep 2025 03:31:04 +0000 Subject: [PATCH] Update F95_BRATR_Management_Ratings_Helper.js Minimize by default, remember it as well --- F95_BRATR_Management_Ratings_Helper.js | 122 +++++++++++++++++-------- 1 file changed, 83 insertions(+), 39 deletions(-) diff --git a/F95_BRATR_Management_Ratings_Helper.js b/F95_BRATR_Management_Ratings_Helper.js index e873f0d..c754e9e 100644 --- a/F95_BRATR_Management_Ratings_Helper.js +++ b/F95_BRATR_Management_Ratings_Helper.js @@ -1,7 +1,7 @@ // ==UserScript== // @name F95 BRATR Management Ratings Helper // @namespace Ryahn -// @version 1.4.2 +// @version 1.5.1 // @description Triage panel for /bratr-ratings/management: highlight low-effort reviews, filter, export CSV. // @match https://f95zone.to/bratr-ratings/management* // @match https://f95zone.to/bratr-ratings/*/management* @@ -23,7 +23,7 @@ }; const csvEscape = s => `"${(s||'').replace(/"/g,'""')}"`; const avg = arr => arr.length ? arr.reduce((a,b)=>a+b,0)/arr.length : 0; - const truncate = (s, n=80) => (s.length > n ? s.slice(0, n-1) + '…' : s); + const truncate = (s, n=80) => (s && s.length > n ? s.slice(0, n-1) + '…' : (s||'')); // Scrape reviews on page; stash stable index i for row->DOM mapping const reviews = $$('.message--review').map((msg, i) => { @@ -66,15 +66,15 @@ return { i, node: msg, author, rating, thread, threadUrl, timeIso, timeTxt, bodyTxt, rawLen, effLen, deleted, links }; }); - // Cliché patterns with labels + // Cliché patterns (includes fixed 200 detector) const cliché = [ - { label: 'good game', rx: /\bgood game\b/i }, - { label: 'very good', rx: /\bit was very good\b/i }, - { label: 'amazing', rx: /\bamazing!?(\b|$)/i }, - { label: 'top N', rx: /\btop\s+\d+\b/i }, - { label: 'pretty sex scenes', rx: /\bpretty sex scenes\b/i }, - { label: 'downloads flex', rx: /\bdownload(ed)? hundreds of games\b/i }, - { label: 'mentions 200 (char rule)', rx: /\b200(?:\s*[- ]?(?:char(?:s|acters?)?|word(?:s)?|limit|minimum|min))?\b/i } + { key:'good game', label:'“good game”', rx:/\bgood game\b/i }, + { key:'very good', label:'“it was very good”', rx:/\bit was very good\b/i }, + { key:'amazing', label:'“amazing”', rx:/\bamazing!?(\b|$)/i }, + { key:'top N', label:'“top ”', rx:/\btop\s+\d+\b/i }, + { key:'pretty sex scenes', label:'“pretty sex scenes”', rx:/\bpretty sex scenes\b/i }, + { key:'downloads flex', label:'“downloaded hundreds of games”', rx:/\bdownload(ed)? hundreds of games\b/i }, + { key:'mentions 200', label:'mentions “200” (char/limit/min)', rx:/\b200(?:\s*[- ]?(?:char(?:s|acters?)?|word(?:s)?|limit|minimum|min))?\b/i } ]; // Spam/bypass heuristics @@ -157,28 +157,24 @@ } // Diagnose low-effort - function diagnoseLowEffort(r, cutoff=200) { + function diagnoseLowEffort(r, cutoff=220) { const reasons = []; const body = r.bodyTxt || ''; if (!r.deleted && (r.effLen || 0) < cutoff) { - reasons.push({ - code: 'LEN', - text: `LEN ${r.effLen}<${cutoff}`, - tip: `Effective length ${r.effLen} is below cutoff ${cutoff}` - }); + reasons.push({ code:'LEN', text:`LEN ${r.effLen}<${cutoff}`, tip:`Effective length ${r.effLen} is below cutoff ${cutoff}` }); } if (!r.deleted) { cliché.forEach(c => { const m = body.match(c.rx); - if (m) reasons.push({ code: 'CLICHÉ', text: `CLICHÉ: "${truncate(m[0], 28)}"`, tip: `Matched phrase: ${m[0]}` }); + if (m) reasons.push({ code:'CLICHÉ', text:`CLICHÉ: "${truncate(m[0], 28)}"`, tip:`Matched: ${m[0]}`, key:c.key }); }); } analyzeSpam(body).forEach(sr => reasons.push(sr)); - if (r.deleted) reasons.push({ code: 'DELETED', text: 'DELETED', tip: 'Author deleted review' }); + if (r.deleted) reasons.push({ code:'DELETED', text:'DELETED', tip:'Author deleted review' }); const low = reasons.some(x => x.code === 'LEN' || x.code === 'CLICHÉ' || x.code === 'SPAM'); return { low, reasons }; @@ -196,11 +192,13 @@
- + + +
@@ -209,10 +207,10 @@ `; document.body.appendChild(panel); - // Styles (+ clickable rows + pulse highlight) + // Styles const css = document.createElement('style'); css.textContent = ` - #bratr-helper{position:fixed;right:12px;bottom:12px;width:780px;max-height:70vh;z-index:99999; + #bratr-helper{position:fixed;right:12px;bottom:12px;width:820px;max-height:70vh;z-index:99999; background:#111;border:1px solid #333;border-radius:12px;color:#ddd;font:12px/1.4 system-ui;box-shadow:0 8px 24px rgba(0,0,0,.4)} #bratr-helper .bh-head{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;border-bottom:1px solid #2a2a2a} #bratr-helper .bh-head strong{font-size:13px} @@ -221,7 +219,7 @@ #bratr-helper .bh-body{padding:8px} #bratr-helper .bh-controls{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px} #bratr-helper .bh-controls label{display:flex;gap:6px;align-items:center;background:#161616;border:1px solid #2a2a2a;border-radius:8px;padding:4px 8px} - #bratr-helper input{background:#0f0f0f;border:1px solid #333;color:#ddd;border-radius:6px;padding:3px 6px} + #bratr-helper input, #bratr-helper select{background:#0f0f0f;border:1px solid #333;color:#ddd;border-radius:6px;padding:3px 6px} #bratr-helper button.bh-copy,#bratr-helper button.bh-reset{background:#1e1e1e;border:1px solid #444;color:#ddd;border-radius:8px;padding:4px 10px;cursor:pointer} #bratr-helper .bh-table{overflow:auto;max-height:48vh;border:1px solid #2a2a2a;border-radius:8px} #bratr-helper table{width:100%;border-collapse:collapse} @@ -236,10 +234,7 @@ #bratr-helper .pill.delet{border-color:#8aa;color:#8aa} #bratr-helper .pill.spam{border-color:#ff9d00;color:#ff9d00} #bratr-helper .links a{margin-right:8px} - @keyframes bh-pulse { - 0% { box-shadow: 0 0 0 0 rgba(245,180,0,.6); } - 100% { box-shadow: 0 0 0 12px rgba(245,180,0,0); } - } + @keyframes bh-pulse {0%{box-shadow:0 0 0 0 rgba(245,180,0,.6);}100%{box-shadow:0 0 0 12px rgba(245,180,0,0);}} .bh-target-pulse { animation: bh-pulse 1s ease-out 0s 2; outline: 2px solid #f5b400 !important; } @media (max-width: 880px){#bratr-helper{left:8px;right:8px;width:auto}} `; @@ -252,21 +247,54 @@ lowonly: panel.querySelector('.bh-lowonly'), from: panel.querySelector('.bh-from'), to: panel.querySelector('.bh-to'), + clicheSel: panel.querySelector('.bh-cliche'), + clicheOnly: panel.querySelector('.bh-cliche-only'), table: panel.querySelector('.bh-table'), metrics: panel.querySelector('.bh-metrics') }; - panel.querySelector('.bh-collapse').addEventListener('click', () => { - const body = panel.querySelector('.bh-body'); - if (body.style.display === 'none') { body.style.display = ''; panel.querySelector('.bh-collapse').textContent = '–'; } - else { body.style.display = 'none'; panel.querySelector('.bh-collapse').textContent = '+'; } + // Populate cliché dropdown + const frag = document.createDocumentFragment(); + cliché.forEach(c => { + const opt = document.createElement('option'); + opt.value = c.key; + opt.textContent = c.label; + frag.appendChild(opt); }); + ui.clicheSel.appendChild(frag); + + const COLLAPSE_KEY = 'bh-collapsed'; + const bodyEl = panel.querySelector('.bh-body'); + const collapseBtn = panel.querySelector('.bh-collapse'); + + function setCollapsed(collapsed) { + bodyEl.style.display = collapsed ? 'none' : ''; + collapseBtn.textContent = collapsed ? '+' : '–'; + try { localStorage.setItem(COLLAPSE_KEY, collapsed ? '1' : '0'); } catch {} + } + + collapseBtn.addEventListener('click', () => { + setCollapsed(bodyEl.style.display !== 'none'); + }); + + // initialize from storage; default to collapsed if unset + const stored = (typeof localStorage !== 'undefined') ? localStorage.getItem(COLLAPSE_KEY) : null; + setCollapsed(stored === null ? true : stored === '1'); panel.querySelector('.bh-reset').addEventListener('click', () => { - ui.min.value = 0; ui.maxlen.value = 200; ui.user.value = ''; ui.lowonly.checked = true; ui.from.value = ''; ui.to.value = ''; + ui.min.value = 0; + ui.maxlen.value = 220; + ui.user.value = ''; + ui.lowonly.checked = true; + ui.from.value = ''; + ui.to.value = ''; + ui.clicheSel.value = '__ALL__'; + ui.clicheOnly.checked = false; render(); }); + ['input','change'].forEach(ev => { - [ui.min, ui.maxlen, ui.user, ui.lowonly, ui.from, ui.to].forEach(el => el.addEventListener(ev, render)); + [ui.min, ui.maxlen, ui.user, ui.lowonly, ui.from, ui.to, ui.clicheSel, ui.clicheOnly] + .forEach(el => el.addEventListener(ev, render)); }); function withinDates(iso, from, to) { @@ -277,6 +305,15 @@ return true; } + function reviewMatchesSelectedCliche(r) { + if (!ui.clicheOnly.checked) return true; + const selected = ui.clicheSel.value; + if (!selected || selected === '__ALL__') return true; + const def = cliché.find(c => c.key === selected); + if (!def) return true; + return def.rx.test(r.bodyTxt || ''); + } + function currentRows() { const min = parseFloat(ui.min.value || '0'); const maxlen = parseInt(ui.maxlen.value || '0', 10); @@ -289,6 +326,8 @@ if (Number.isFinite(min) && r.rating != null && r.rating < min) return false; if (user && !r.author.toLowerCase().includes(user)) return false; if (from || to) { if (!withinDates(r.timeIso, from, to)) return false; } + if (!reviewMatchesSelectedCliche(r)) return false; + const diag = diagnoseLowEffort(r, maxlen); if (lowOnly && !diag.low) return false; return true; @@ -296,11 +335,14 @@ } function render() { - const maxlen = parseInt(ui.maxlen.value || '200', 10); + const maxlen = parseInt(ui.maxlen.value || '220', 10); const live = reviews.filter(r => !r.deleted); const lowCount = live.filter(r => diagnoseLowEffort(r, maxlen).low).length; const avgRating = avg(live.map(r => r.rating || 0)).toFixed(2); - ui.metrics.textContent = `on page: ${reviews.length} • avg ★ ${avgRating} • low-effort flagged: ${lowCount}`; + const clicheInfo = ui.clicheOnly.checked + ? ` • cliché: ${ui.clicheSel.options[ui.clicheSel.selectedIndex]?.text || 'All'}` + : ''; + ui.metrics.textContent = `on page: ${reviews.length} • avg ★ ${avgRating} • low-effort flagged: ${lowCount}${clicheInfo}`; const rows = currentRows(); const html = [` @@ -311,6 +353,7 @@ const low = diag.low; const one = Math.round(r.rating||0) === 1; const cls = `${low ? 'low' : ''} ${r.deleted ? 'deleted': ''}`.trim(); + const flagHtml = diag.reasons.map(reason => { let pill = 'pill'; if (reason.code === 'LEN' || reason.code === 'CLICHÉ') pill += ' low'; @@ -319,6 +362,7 @@ const tip = reason.tip ? ` title="${reason.tip.replace(/"/g, '"')}"` : ''; return `${reason.text}`; }).join(''); + html.push(` @@ -338,11 +382,12 @@ html.push(`
${r.author || ''} ${r.rating != null ? r.rating.toFixed(2) : ''}
`); ui.table.innerHTML = html.join(''); - // Bind row click delegation exactly once and keep it + // Keep single delegated click handler alive if (!ui.table._bhBound) { ui.table.addEventListener('click', onRowClick); ui.table._bhBound = true; } + retint(); } @@ -354,17 +399,16 @@ const r = reviews[idx]; if (!r || !r.node) return; - // Smooth scroll and pulse r.node.scrollIntoView({ behavior: 'smooth', block: 'center' }); r.node.classList.remove('bh-target-pulse'); - void r.node.offsetWidth; // reflow to restart animation + void r.node.offsetWidth; r.node.classList.add('bh-target-pulse'); setTimeout(() => r.node.classList.remove('bh-target-pulse'), 1800); } // Tint live page with reasons as tooltip function retint() { - const maxlen = parseInt(ui.maxlen?.value || '200', 10); + const maxlen = parseInt(ui.maxlen?.value || '220', 10); reviews.forEach(r => { if (r.deleted) { r.node.style.outline = ''; @@ -389,7 +433,7 @@ // Copy CSV of current view, with reasons column panel.querySelector('.bh-copy').addEventListener('click', () => { const rows = currentRows(); - const maxlen = parseInt(ui.maxlen?.value || '200', 10); + const maxlen = parseInt(ui.maxlen?.value || '220', 10); const header = ['author','rating','thread','threadUrl','timeIso','effLen','rawLen','deleted','low','reasons','body']; const lines = [header.join(',')]; rows.forEach(r => {