feat(dom): currentAttribute scraper for gym page (manual verify)
This commit is contained in:
+81
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user