v0.14.20 - Fix Purchase page scraper: const dynjs_eqstore + screen=purchase

- Bug 1: Purchase tab URL is 'screen=purchase', not 'screen=buy'
- Bug 2: dynjs_eqstore is declared with 'const', not 'var', so it doesn't
  create a window property. getHVEquipStore() now also checks bare names
  which works with @grant none and the IIFE scope chain
- Fixed price parsing: purchase table has 2 td cells (label + price),
  previously required >3 cells so prices were always empty
- Names with 🔺 prefix are cleaned before storage
This commit is contained in:
GaboGG 2026-07-27 20:48:27 -04:00
parent f2e8ce536a
commit 5286f66b3e
5 changed files with 51 additions and 21 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.19
// @version 0.14.20
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.19';
const VERSION = '0.14.20';
const CFG = {
// — Battle automation
@ -3281,8 +3281,18 @@ const GEAR_DB_KEY = SP + 'geardb';
// ── Access HV's in-memory equipment store ──
function getHVEquipStore() {
const w = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
// Check window properties (works for var-declared globals like dynjs_equip)
if (w.dynjs_eqstore && Object.keys(w.dynjs_eqstore).length) return w.dynjs_eqstore;
if (w.dynjs_equip && Object.keys(w.dynjs_equip).length) return w.dynjs_equip;
// Check bare names (works for const/let globals that don't create window props)
try {
if (typeof dynjs_eqstore !== 'undefined' && dynjs_eqstore && Object.keys(dynjs_eqstore).length)
return dynjs_eqstore;
} catch(e) {}
try {
if (typeof dynjs_equip !== 'undefined' && dynjs_equip && Object.keys(dynjs_equip).length)
return dynjs_equip;
} catch(e) {}
return null;
}
@ -3523,13 +3533,13 @@ function scrapeBuyPage() {
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
// Try to read price from buy pages (often in additional columns or cell text)
// Try to read price from buy pages (second td with credit cost)
let price = '';
if (!idM && !name && !itemId) return;
const cells = row.querySelectorAll('td');
if (cells.length > 3) {
const lastCell = cells[cells.length - 1];
if (lastCell) price = (lastCell.textContent || '').trim();
if (cells.length >= 2) {
const priceCell = cells[cells.length - 1]; // last td = price column
if (priceCell) price = (priceCell.textContent || '').trim();
}
const obj = { id: itemId, name: name || 'Unknown', category: cat, price, source: 'buy', scrapedAt: Date.now() };
@ -3559,7 +3569,7 @@ function autoScrapeGear() {
const url = window.location.href || '';
if (url.includes('ss=eq') || url.includes('ss=ch')) return scrapeCharacterEquip();
if (url.includes('ss=am') && (url.includes('screen=organize') || !url.includes('screen='))) return scrapeArmory();
if (url.includes('ss=am') && url.includes('screen=buy')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=purchase')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=modify')) return scrapeModifyDetail();
if (url.includes('ss=bi')) return scrapeBuyPage();
return [];

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.19
// @version 0.14.20
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*
@ -17,7 +17,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.19';
const VERSION = '0.14.20';
const CFG = {
// — Battle automation
@ -3281,8 +3281,18 @@ const GEAR_DB_KEY = SP + 'geardb';
// ── Access HV's in-memory equipment store ──
function getHVEquipStore() {
const w = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
// Check window properties (works for var-declared globals like dynjs_equip)
if (w.dynjs_eqstore && Object.keys(w.dynjs_eqstore).length) return w.dynjs_eqstore;
if (w.dynjs_equip && Object.keys(w.dynjs_equip).length) return w.dynjs_equip;
// Check bare names (works for const/let globals that don't create window props)
try {
if (typeof dynjs_eqstore !== 'undefined' && dynjs_eqstore && Object.keys(dynjs_eqstore).length)
return dynjs_eqstore;
} catch(e) {}
try {
if (typeof dynjs_equip !== 'undefined' && dynjs_equip && Object.keys(dynjs_equip).length)
return dynjs_equip;
} catch(e) {}
return null;
}
@ -3523,13 +3533,13 @@ function scrapeBuyPage() {
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
// Try to read price from buy pages (often in additional columns or cell text)
// Try to read price from buy pages (second td with credit cost)
let price = '';
if (!idM && !name && !itemId) return;
const cells = row.querySelectorAll('td');
if (cells.length > 3) {
const lastCell = cells[cells.length - 1];
if (lastCell) price = (lastCell.textContent || '').trim();
if (cells.length >= 2) {
const priceCell = cells[cells.length - 1]; // last td = price column
if (priceCell) price = (priceCell.textContent || '').trim();
}
const obj = { id: itemId, name: name || 'Unknown', category: cat, price, source: 'buy', scrapedAt: Date.now() };
@ -3559,7 +3569,7 @@ function autoScrapeGear() {
const url = window.location.href || '';
if (url.includes('ss=eq') || url.includes('ss=ch')) return scrapeCharacterEquip();
if (url.includes('ss=am') && (url.includes('screen=organize') || !url.includes('screen='))) return scrapeArmory();
if (url.includes('ss=am') && url.includes('screen=buy')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=purchase')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=modify')) return scrapeModifyDetail();
if (url.includes('ss=bi')) return scrapeBuyPage();
return [];

View file

@ -2,7 +2,7 @@
// CONFIG — default settings
// ═══════════════════════════════════════════════════════════════════════
const VERSION = '0.14.19';
const VERSION = '0.14.20';
const CFG = {
// — Battle automation

View file

@ -10,8 +10,18 @@ const GEAR_DB_KEY = SP + 'geardb';
// ── Access HV's in-memory equipment store ──
function getHVEquipStore() {
const w = (typeof unsafeWindow !== 'undefined') ? unsafeWindow : window;
// Check window properties (works for var-declared globals like dynjs_equip)
if (w.dynjs_eqstore && Object.keys(w.dynjs_eqstore).length) return w.dynjs_eqstore;
if (w.dynjs_equip && Object.keys(w.dynjs_equip).length) return w.dynjs_equip;
// Check bare names (works for const/let globals that don't create window props)
try {
if (typeof dynjs_eqstore !== 'undefined' && dynjs_eqstore && Object.keys(dynjs_eqstore).length)
return dynjs_eqstore;
} catch(e) {}
try {
if (typeof dynjs_equip !== 'undefined' && dynjs_equip && Object.keys(dynjs_equip).length)
return dynjs_equip;
} catch(e) {}
return null;
}
@ -252,13 +262,13 @@ function scrapeBuyPage() {
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
// Try to read price from buy pages (often in additional columns or cell text)
// Try to read price from buy pages (second td with credit cost)
let price = '';
if (!idM && !name && !itemId) return;
const cells = row.querySelectorAll('td');
if (cells.length > 3) {
const lastCell = cells[cells.length - 1];
if (lastCell) price = (lastCell.textContent || '').trim();
if (cells.length >= 2) {
const priceCell = cells[cells.length - 1]; // last td = price column
if (priceCell) price = (priceCell.textContent || '').trim();
}
const obj = { id: itemId, name: name || 'Unknown', category: cat, price, source: 'buy', scrapedAt: Date.now() };
@ -288,7 +298,7 @@ function autoScrapeGear() {
const url = window.location.href || '';
if (url.includes('ss=eq') || url.includes('ss=ch')) return scrapeCharacterEquip();
if (url.includes('ss=am') && (url.includes('screen=organize') || !url.includes('screen='))) return scrapeArmory();
if (url.includes('ss=am') && url.includes('screen=buy')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=purchase')) return scrapeBuyPage();
if (url.includes('ss=am') && url.includes('screen=modify')) return scrapeModifyDetail();
if (url.includes('ss=bi')) return scrapeBuyPage();
return [];

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.19
// @version 0.14.20
// @description HV Unified — battle automation, guidance, and UI enhancements for HentaiVerse
// @author GaboGG + Hermes
// @match *://*.hentaiverse.org/*