fix(bundle): mirror source fixes in embedded userscript

This commit is contained in:
dev
2026-06-05 08:43:22 -05:00
parent 540416949b
commit 8d89e40b91
+93 -62
View File
@@ -122,76 +122,80 @@
} }
// ===== dom.js (embedded) ===== // ===== dom.js (embedded) =====
const TAT_KNOWN_ATTRS = ['strength', 'defense', 'speed', 'dexterity', 'endurance', 'intelligence'];
const TAT_KNOWN_GYMS = [
'Total Bastion', 'Frontline Fitness', 'Premier Fitness', 'Average Joes',
"Woody's Workout Club", "Baldr's Gym", 'Sportscience Laboratory',
'Chrome Gym', "Mr. Miyagi's", 'Power House', 'Gym 300', 'Gym 400', 'Gym 500', 'Gym 600',
'Elite Gym', "David's Gym",
];
function currentAttribute() { function currentAttribute() {
const KNOWN = ['strength', 'defense', 'speed', 'dexterity', 'endurance', 'intelligence']; const li = tatFindActiveAttributeLi();
const ATTR_RE = new RegExp('\\b(' + KNOWN.join('|') + ')\\b'); if (!li) return null;
const headers = document.querySelectorAll('h1, h2, h3, h4, .title, .gym-title, [class*="gym"]'); const attr = tatExtractAttrFromLi(li);
let attr = null, attrEl = null;
for (const el of headers) {
const t = (el.textContent || '').trim().toLowerCase();
const m = t.match(ATTR_RE);
if (m) { attr = m[1]; attrEl = el; break; }
}
if (!attr) return null; if (!attr) return null;
let valEl = findValueNear(attrEl); const current = tatExtractValueFromLi(li);
if (!valEl) valEl = findValueElement();
if (!valEl) return null;
const current = parseNumber(valEl.textContent);
if (current == null) return null; if (current == null) return null;
const gym = findGymName() || 'Unknown gym'; const gym = tatFindGymName() || 'Unknown gym';
return { attr: attr, current: current, gym: gym }; return { attr: attr, current: current, gym: tatEsc(gym) };
} }
function findValueNear(el) { function tatFindActiveAttributeLi() {
const scope = []; // Priority 1: the <li> with the "success" class (just trained).
let cur = el; const lis = document.querySelectorAll('ul[class*="properties"] > li[class*="success"]');
for (let depth = 0; depth < 3 && cur; depth++) { for (const li of lis) {
scope.push(cur); if (tatExtractAttrFromLi(li)) return li;
cur = cur.parentElement;
} }
let best = null, bestN = -Infinity; // Priority 2: the <li> corresponding to the .gained message's attribute.
for (const root of scope) { const gained = document.querySelector('[class*="gained"]');
const candidates = root.querySelectorAll('*'); if (gained) {
for (const c of candidates) { const text = (gained.textContent || '').toLowerCase();
if (c.children.length > 0) continue; for (const attr of TAT_KNOWN_ATTRS) {
const t = (c.textContent || '').trim(); if (text.indexOf(attr) !== -1) {
if (!/^[\d,]+(\.\d+)?$/.test(t)) continue; const li = document.querySelector('ul[class*="properties"] > li[class^="' + attr + '___"]');
const n = parseNumber(t); if (li) return li;
if (n == null || n < 1) continue;
if (n > bestN) { best = c; bestN = n; }
} }
} }
return best;
} }
function findValueElement() { // Priority 3: the first <li> in the properties list.
const candidates = document.querySelectorAll('*'); const all = document.querySelectorAll('ul[class*="properties"] > li');
let best = null, bestN = -Infinity; for (const li of all) {
for (const el of candidates) { if (tatExtractAttrFromLi(li)) return li;
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; return null;
} }
function findGymName() { function tatExtractAttrFromLi(li) {
const panel = document.querySelector('.gym, #gym, [class*="gym-"], [class*="Gym"]'); const cls = li.className || '';
const roots = panel ? [panel, document.body] : [document.body]; const parts = cls.split(/\s+/);
const known = ['Total Bastion', 'Frontline Fitness', 'Gym 300', 'Gym 500', "Baldr's Gym", 'Sportscience Laboratory', 'Premier Fitness', 'Chrome Gym', "Mr. Miyagi's", 'Power House']; for (const attr of TAT_KNOWN_ATTRS) {
for (const root of roots) { const prefix = attr + '___';
const all = root.querySelectorAll('h1, h2, h3, h4, p, span, div, li'); for (const c of parts) {
for (const el of all) { if (c.indexOf(prefix) === 0) return attr;
if (el.children.length > 0) continue;
const t = (el.textContent || '').trim();
for (const name of known) { if (t.includes(name)) return name; }
} }
} }
return null; return null;
} }
function parseNumber(text) { function tatExtractValueFromLi(li) {
const valueSpan = li.querySelector('[class^="propertyValue"]');
if (!valueSpan) return null;
return tatParseNumber(valueSpan.textContent);
}
function tatFindGymName() {
// Gym names live in aria-labels of <button class="gymButton___HASH">.
const buttons = document.querySelectorAll('button[class*="gymButton"]');
for (const btn of buttons) {
const label = btn.getAttribute('aria-label') || '';
for (const name of TAT_KNOWN_GYMS) {
// aria-label format: "Gym Name. Membership cost - $X. ..."
if (label === name || label.indexOf(name + '.') === 0 || label.indexOf(name + ' ') === 0) {
return name;
}
}
}
return null;
}
function tatParseNumber(text) {
if (!text) return null; if (!text) return null;
const cleaned = text.replace(/,/g, '').trim(); const cleaned = String(text).replace(/,/g, '').trim();
if (!/^\d+(\.\d+)?$/.test(cleaned)) return null; if (!/^\d+(\.\d+)?$/.test(cleaned)) return null;
const n = parseFloat(cleaned); const n = parseFloat(cleaned);
return Number.isFinite(n) ? Math.floor(n) : null; return Number.isFinite(n) ? Math.floor(n) : null;
@@ -204,25 +208,52 @@
function handle(text, url) { function handle(text, url) {
const parsed = parseTrainResponse(text, url, opts.currentAttr); const parsed = parseTrainResponse(text, url, opts.currentAttr);
if (!parsed) { if (warnedFor !== url) { warnedFor = url; opts.onParseFail && opts.onParseFail(url); } return; } if (!parsed) { if (warnedFor !== url) { warnedFor = url; opts.onParseFail && opts.onParseFail(url); } return; }
if (parsed.attr && opts.currentAttr && parsed.attr !== opts.currentAttr) return; let delta;
const delta = parsed.newValue - lastValue; if (typeof parsed.delta === 'number' && parsed.delta > 0) {
delta = parsed.delta;
} else if (typeof parsed.newValue === 'number' && parsed.newValue > 0) {
delta = parsed.newValue - lastValue;
lastValue = parsed.newValue; lastValue = parsed.newValue;
if (delta > 0) opts.onTrain({ attr: parsed.attr || opts.currentAttr, delta: delta, ts: Date.now() }); } else {
return;
}
if (delta <= 0) return;
const attr = parsed.attr || opts.currentAttr;
if (!attr) return;
opts.onTrain({ attr: attr, delta: delta, ts: Date.now() });
} }
wrapXhr(handle); wrapFetch(handle); wrapXhr(handle); wrapFetch(handle);
return { updatePrevValue: function (v) { lastValue = v; } }; return { updatePrevValue: function (v) { lastValue = v; } };
} }
function parseTrainResponse(text, url, fallbackAttr) { function parseTrainResponse(text, url, fallbackAttr) {
// Strategy 1: look for the "gained" message in the response.
// Format: "You gained <number> <attr>" (e.g. "You gained 10,885.76 dexterity").
// Torn sometimes prefixes with other text (e.g. "You gained 10,885.76 dexterity"),
// so we match the number-and-attribute-name pattern directly.
const gainedMatch = text.match(/[Yy]ou\s+gained\s+([\d,]+(?:\.\d+)?)\s+(strength|defense|speed|dexterity|endurance|intelligence)\b/i);
if (gainedMatch) {
const delta = parseFloat(gainedMatch[1].replace(/,/g, ''));
const attr = gainedMatch[2].toLowerCase();
if (Number.isFinite(delta) && delta >= 0) {
return { delta: delta, attr: attr };
}
}
// Strategy 2: JSON with newValue + attr.
try { try {
const j = JSON.parse(text); const j = JSON.parse(text);
if (j && typeof j === 'object' && 'newValue' in j && 'attr' in j) { if (j && typeof j === 'object' && 'newValue' in j && 'attr' in j) {
return { newValue: Number(j.newValue), attr: String(j.attr) }; return { newValue: Number(j.newValue), attr: String(j.attr) };
} }
} catch {} } catch {}
const m = text.match(/(\d{1,3}(?:,\d{3})+|\d{4,})/); // Strategy 3: regex fallback (last resort). Don't use the first number
if (m) { // blindly; look specifically for the propertyValue span content, which
const newValue = parseInt(m[1].replace(/,/g, ''), 10); // is the authoritative source.
if (Number.isFinite(newValue) && newValue > 0) return { newValue: newValue, attr: fallbackAttr || null }; const propertyValueMatch = text.match(/class="propertyValue[^"]*"[^>]*>([\d,]+(?:\.\d+)?)</);
if (propertyValueMatch) {
const newValue = parseInt(propertyValueMatch[1].replace(/,/g, ''), 10);
if (Number.isFinite(newValue) && newValue > 0) {
return { newValue: newValue, attr: fallbackAttr || null };
}
} }
return null; return null;
} }