From e647fbf545a115320fd30b7ca179b0af3b562589 Mon Sep 17 00:00:00 2001 From: dev Date: Mon, 1 Jun 2026 16:32:44 -0500 Subject: [PATCH] feat(dom): currentAttribute scraper for gym page (manual verify) --- src/dom.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/dom.js diff --git a/src/dom.js b/src/dom.js new file mode 100644 index 0000000..a55142b --- /dev/null +++ b/src/dom.js @@ -0,0 +1,81 @@ +/** + * Reads the gym page DOM and returns: + * { attr: 'strength'|'speed'|..., current: number, gym: string } + * or `null` if the page doesn't look like a Torn gym page. + * + * The selectors below are best-effort matches for torn.com/gym.php + * and will need adjustment if Torn changes the markup. + */ +export function currentAttribute() { + // The attribute name is shown in the gym page header. + // 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 headers = document.querySelectorAll('h1, h2, h3, h4, .title, .gym-title, [class*="gym"]'); + let attr = 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; + } + 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(); + if (!valEl) return null; + const current = parseNumber(valEl.textContent); + if (current == null) return null; + + // Gym name: any element on the page containing the word "Gym" or + // "Bastion" / "Frontline" / etc. Torn's gym names vary. + const gym = findGymName() || 'Unknown gym'; + + return { attr, current, gym }; +} + +function findValueElement() { + // 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; + for (const el of candidates) { + if (el.children.length > 0) continue; + const t = (el.textContent || '').trim(); + if (!/^[\d,]+(\.\d+)?$/.test(t)) continue; + const n = parseNumber(t); + if (n == null || n < 1) continue; + if (n > bestN) { best = el; bestN = n; } + } + return best; +} + +function findGymName() { + // Look for a known set of Torn gym name fragments. Adjust as needed. + 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; + } + } + return null; +} + +function parseNumber(text) { + if (!text) return null; + const cleaned = text.replace(/,/g, '').trim(); + if (!/^\d+(\.\d+)?$/.test(cleaned)) return null; + const n = parseFloat(cleaned); + return Number.isFinite(n) ? Math.floor(n) : null; +}