fix(interceptor): parse "gained" message for reliable per-train delta
This commit is contained in:
+39
-19
@@ -5,15 +5,16 @@
|
|||||||
* compared against the previous one, and `onTrain({attr, delta, ts})`
|
* compared against the previous one, and `onTrain({attr, delta, ts})`
|
||||||
* is invoked.
|
* is invoked.
|
||||||
*
|
*
|
||||||
* `parseTrainResponse(text, url)` is intentionally permissive and
|
* `parseTrainResponse(text, url)` returns:
|
||||||
* returns `{ newValue, attr } | null`. The default implementation
|
* { delta, attr } — when a "You gained X.XX <attr>" message is found
|
||||||
* tries JSON first, then a regex fallback.
|
* { newValue, attr } — when a propertyValue span or JSON newValue is found
|
||||||
|
* null — when nothing usable is found
|
||||||
*/
|
*/
|
||||||
export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onParseFail }) {
|
export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onParseFail }) {
|
||||||
let lastValue = prevValue;
|
let lastValue = prevValue;
|
||||||
let warnedFor = null;
|
let warnedFor = null;
|
||||||
|
|
||||||
const handle = (text, url) => {
|
function handle(text, url) {
|
||||||
const parsed = parseTrainResponse(text, url, currentAttr);
|
const parsed = parseTrainResponse(text, url, currentAttr);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
if (warnedFor !== url) {
|
if (warnedFor !== url) {
|
||||||
@@ -22,11 +23,22 @@ export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onPar
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (parsed.attr && currentAttr && parsed.attr !== currentAttr) return;
|
|
||||||
const delta = parsed.newValue - lastValue;
|
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;
|
lastValue = parsed.newValue;
|
||||||
if (delta > 0) onTrain({ attr: parsed.attr, delta, ts: Date.now() });
|
} else {
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta <= 0) return;
|
||||||
|
const attr = parsed.attr || currentAttr;
|
||||||
|
if (!attr) return;
|
||||||
|
onTrain({ attr, delta, ts: Date.now() });
|
||||||
|
}
|
||||||
|
|
||||||
wrapXhr(handle);
|
wrapXhr(handle);
|
||||||
wrapFetch(handle);
|
wrapFetch(handle);
|
||||||
@@ -37,23 +49,31 @@ export function startRequestInterceptor({ prevValue, currentAttr, onTrain, onPar
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseTrainResponse(text, url, fallbackAttr) {
|
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 {
|
try {
|
||||||
const j = JSON.parse(text);
|
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) {
|
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 { /* not JSON */ }
|
} catch { /* not JSON */ }
|
||||||
|
// Strategy 3: regex fallback (last resort). Don't use the first number
|
||||||
// Regex fallback: scan text for a number formatted like an attribute.
|
// blindly; look specifically for the propertyValue span content, which
|
||||||
// If we find one and the caller passed a fallbackAttr, use it; otherwise
|
// is the authoritative source.
|
||||||
// the caller can choose to ignore the result.
|
const propertyValueMatch = text.match(/class="propertyValue[^"]*"[^>]*>([\d,]+(?:\.\d+)?)</);
|
||||||
const m = text.match(/(\d{1,3}(?:,\d{3})+|\d{4,})/);
|
if (propertyValueMatch) {
|
||||||
if (m) {
|
const newValue = parseInt(propertyValueMatch[1].replace(/,/g, ''), 10);
|
||||||
const newValue = parseInt(m[1].replace(/,/g, ''), 10);
|
|
||||||
if (Number.isFinite(newValue) && newValue > 0) {
|
if (Number.isFinite(newValue) && newValue > 0) {
|
||||||
return { newValue, attr: fallbackAttr || null };
|
return { newValue, attr: fallbackAttr || null };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user