From 5286f66b3e26fbebe1440b7c9fd9daf7b6d2d8fd Mon Sep 17 00:00:00 2001 From: GaboGG Date: Mon, 27 Jul 2026 20:48:27 -0400 Subject: [PATCH] v0.14.20 - Fix Purchase page scraper: const dynjs_eqstore + screen=purchase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- scripts/hv-unified.user.js | 24 +++++++++++++++++------- scripts/latest.user.js | 24 +++++++++++++++++------- src/config.js | 2 +- src/gear-scraper.js | 20 +++++++++++++++----- src/header.user.js | 2 +- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/scripts/hv-unified.user.js b/scripts/hv-unified.user.js index af885c2..4a1f99f 100644 --- a/scripts/hv-unified.user.js +++ b/scripts/hv-unified.user.js @@ -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 []; diff --git a/scripts/latest.user.js b/scripts/latest.user.js index af885c2..4a1f99f 100644 --- a/scripts/latest.user.js +++ b/scripts/latest.user.js @@ -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 []; diff --git a/src/config.js b/src/config.js index c24144f..f9ecd48 100644 --- a/src/config.js +++ b/src/config.js @@ -2,7 +2,7 @@ // CONFIG — default settings // ═══════════════════════════════════════════════════════════════════════ -const VERSION = '0.14.19'; +const VERSION = '0.14.20'; const CFG = { // — Battle automation diff --git a/src/gear-scraper.js b/src/gear-scraper.js index 27f4fbb..62398f8 100644 --- a/src/gear-scraper.js +++ b/src/gear-scraper.js @@ -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 []; diff --git a/src/header.user.js b/src/header.user.js index 171aee1..f164bea 100644 --- a/src/header.user.js +++ b/src/header.user.js @@ -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/*