From aec9c4083536ee1196476a5b15727ee321afb80f Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 1 Jun 2026 16:38:22 -0500 Subject: [PATCH] fix(dom): scope value search near attribute, whole-word matching, panel-scoped gym name --- src/dom.js | 59 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/dom.js b/src/dom.js index a55142b..c64c12f 100644 --- a/src/dom.js +++ b/src/dom.js @@ -11,21 +11,23 @@ export function currentAttribute() { // Torn displays it as a capitalized word (e.g. "Strength") near the // top of the gym form. const KNOWN = ['strength', 'defense', 'speed', 'dexterity', 'endurance', 'intelligence']; + const ATTR_RE = new RegExp('\\b(' + KNOWN.join('|') + ')\\b'); const headers = document.querySelectorAll('h1, h2, h3, h4, .title, .gym-title, [class*="gym"]'); let attr = null; + let attrEl = null; for (const el of headers) { const t = (el.textContent || '').trim().toLowerCase(); - for (const k of KNOWN) { - if (t.includes(k)) { attr = k; break; } - } - if (attr) break; + const m = t.match(ATTR_RE); + if (m) { attr = m[1]; attrEl = el; break; } } if (!attr) return null; // Current value: look for the prominent number on the page that is - // formatted like a Torn attribute (e.g. "14,328,501"). - const valEl = findValueElement(); + // formatted like a Torn attribute (e.g. "14,328,501"). Search near + // the attribute element so we don't pick up unrelated global numbers. + let valEl = findValueNear(attrEl); + if (!valEl) valEl = findValueElement(); // fallback: whole-page scan if (!valEl) return null; const current = parseNumber(valEl.textContent); if (current == null) return null; @@ -37,10 +39,34 @@ export function currentAttribute() { return { attr, current, gym }; } +function findValueNear(el) { + // Look at the element itself, then up to a few ancestors, then their descendants. + // Prefer the largest formatted number within ~2 parent levels. + const scope = []; + let cur = el; + for (let depth = 0; depth < 3 && cur; depth++) { + scope.push(cur); + cur = cur.parentElement; + } + let best = null; + let bestN = -Infinity; + for (const root of scope) { + const candidates = root.querySelectorAll('*'); + for (const c of candidates) { + if (c.children.length > 0) continue; + const t = (c.textContent || '').trim(); + if (!/^[\d,]+(\.\d+)?$/.test(t)) continue; + const n = parseNumber(t); + if (n == null || n < 1) continue; + if (n > bestN) { best = c; bestN = n; } + } + } + return best; +} + function findValueElement() { + // Fallback only used when no element is found near the attribute. // Walk all elements; pick the largest formatted number on the page. - // Torn renders the current attribute as a big number near the - // "Property" label. const candidates = document.querySelectorAll('*'); let best = null; let bestN = -Infinity; @@ -57,16 +83,23 @@ function findValueElement() { function findGymName() { // Look for a known set of Torn gym name fragments. Adjust as needed. + // Prefer an element that looks like the gym panel so we don't match + // against global widgets (news, sidebar, ads). + const panel = document.querySelector('.gym, #gym, [class*="gym-"], [class*="Gym"]'); + const roots = panel ? [panel, document.body] : [document.body]; const known = [ 'Total Bastion', 'Frontline Fitness', 'Gym 300', 'Gym 500', 'Baldr\'s Gym', 'Sportscience Laboratory', 'Premier Fitness', 'Chrome Gym', 'Mr. Miyagi\'s', 'Power House', ]; - const all = document.querySelectorAll('p, span, div, h1, h2, h3, h4, li'); - for (const el of all) { - const t = (el.textContent || '').trim(); - for (const name of known) { - if (t.includes(name)) return name; + for (const root of roots) { + const all = root.querySelectorAll('h1, h2, h3, h4, p, span, div, li'); + for (const el of all) { + if (el.children.length > 0) continue; + const t = (el.textContent || '').trim(); + for (const name of known) { + if (t.includes(name)) return name; + } } } return null;