fix(interceptor): parse "gained" message for reliable per-train delta

This commit is contained in:
dev
2026-06-05 08:39:16 -05:00
parent 6dd5d2e3f2
commit 540416949b
+40 -20
View File
@@ -5,15 +5,16 @@
* compared against the previous one, and `onTrain({attr, delta, ts})`
* is invoked.
*
* `parseTrainResponse(text, url)` is intentionally permissive and
* returns `{ newValue, attr } | null`. The default implementation
* tries JSON first, then a regex fallback.
* `parseTrainResponse(text, url)` returns:
* { delta, attr } — when a "You gained X.XX <attr>" message is found
* { newValue, attr } — when a propertyValue span or JSON newValue is found
* null — when nothing usable is found
*/
export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onParseFail }) {
let lastValue = prevValue;
let warnedFor = null;
const handle = (text, url) => {
function handle(text, url) {
const parsed = parseTrainResponse(text, url, currentAttr);
if (!parsed) {
if (warnedFor !== url) {
@@ -22,11 +23,22 @@ export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onPar
}
return;
}
if (parsed.attr && currentAttr && parsed.attr !== currentAttr) return;
const delta = parsed.newValue - lastValue;
lastValue = parsed.newValue;
if (delta > 0) onTrain({ attr: parsed.attr, delta, ts: Date.now() });
};
let delta;
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;
} else {
return;
}
if (delta <= 0) return;
const attr = parsed.attr || currentAttr;
if (!attr) return;
onTrain({ attr, delta, ts: Date.now() });
}
wrapXhr(handle);
wrapFetch(handle);
@@ -37,23 +49,31 @@ export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onPar
}
function parseTrainResponse(text, url, fallbackAttr) {
// Try JSON
// 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, attr };
}
}
// Strategy 2: JSON with newValue + attr.
try {
const j = JSON.parse(text);
// Torn historically returns an HTML fragment; if it's JSON, look
// for a known shape. This is a placeholder — adjust after manual
// verification.
if (j && typeof j === 'object' && 'newValue' in j && 'attr' in j) {
return { newValue: Number(j.newValue), attr: String(j.attr) };
}
} catch { /* not JSON */ }
// Regex fallback: scan text for a number formatted like an attribute.
// If we find one and the caller passed a fallbackAttr, use it; otherwise
// the caller can choose to ignore the result.
const m = text.match(/(\d{1,3}(?:,\d{3})+|\d{4,})/);
if (m) {
const newValue = parseInt(m[1].replace(/,/g, ''), 10);
// Strategy 3: regex fallback (last resort). Don't use the first number
// blindly; look specifically for the propertyValue span content, which
// is the authoritative source.
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, attr: fallbackAttr || null };
}