v0.14.16 - Armory scraper now scavenges full store for all items

- Previously only scraped items visible in the current filter tab
  (had to visit each tab: New, Two-Handed, Light, Heavy, etc.)
- Now also scans the full dynjs_equip store for ALL items in your
  account (305 total) — every filter tab's worth of data at once
- Category guessed from tooltip type (Cloth, Light, Heavy, Weapon, etc.)
- One visit to the Armory page + HV.scrapeGear() = complete inventory
This commit is contained in:
GaboGG 2026-07-27 15:58:02 -04:00
parent d95409026f
commit 4aff813f01
5 changed files with 162 additions and 81 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.15
// @version 0.14.16
// @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.15';
const VERSION = '0.14.16';
const CFG = {
// — Battle automation
@ -3401,37 +3401,64 @@ function scrapeCharacterEquip() {
function scrapeArmory() {
const scraped = [];
const el = document.getElementById('equiplist');
if (!el) return scraped;
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
// First, scrape any items visible in the current filter tab
if (el) {
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
}
}
}
scraped.push(obj);
});
scraped.push(obj);
});
}
// Second, scan the full store for items NOT in the visible list
// This catches all inventory regardless of which filter tab you're on
const store = getHVEquipStore();
if (store) {
const visibleIds = new Set(scraped.map(s => s.id));
Object.entries(store).forEach(([id, data]) => {
if (visibleIds.has(id)) return; // already scraped from DOM
const stats = data.d ? parseEquipHTML(data.d) : null;
// Try to determine category from stats type
let category = 'All Items';
if (stats && stats.type) {
if (stats.type.includes('Cloth')) category = 'Cloth Armor';
else if (stats.type.includes('Light')) category = 'Light Armor';
else if (stats.type.includes('Heavy')) category = 'Heavy Armor';
else if (stats.type.includes('Shield')) category = 'Shield';
else if (stats.type.includes('Staff')) category = 'Staff';
else if (stats.type.includes('Axe') || stats.type.includes('Mace') || stats.type.includes('Sword') || stats.type.includes('Estoc') || stats.type.includes('Rapier') || stats.type.includes('Club') || stats.type.includes('Dagger')) category = 'Weapon';
}
if (!stats || (!data.t && !data.d)) return; // skip empty entries
scraped.push({
id, name: data.t || 'Unknown', category,
quality: data.q, stats, source: 'armory', scrapedAt: Date.now()
});
});
}
if (scraped.length > 0) {
const db = getGearDB();
saveGearDB([...db.filter(e => e.source !== 'armory'), ...scraped]);
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats)`, 'color:#0f0');
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats, ${scraped.length - (el ? el.querySelectorAll('table tr[onmouseover]').length : 0)} from store)`, 'color:#0f0');
}
return scraped;
}

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.15
// @version 0.14.16
// @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.15';
const VERSION = '0.14.16';
const CFG = {
// — Battle automation
@ -3401,37 +3401,64 @@ function scrapeCharacterEquip() {
function scrapeArmory() {
const scraped = [];
const el = document.getElementById('equiplist');
if (!el) return scraped;
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
// First, scrape any items visible in the current filter tab
if (el) {
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
}
}
}
scraped.push(obj);
});
scraped.push(obj);
});
}
// Second, scan the full store for items NOT in the visible list
// This catches all inventory regardless of which filter tab you're on
const store = getHVEquipStore();
if (store) {
const visibleIds = new Set(scraped.map(s => s.id));
Object.entries(store).forEach(([id, data]) => {
if (visibleIds.has(id)) return; // already scraped from DOM
const stats = data.d ? parseEquipHTML(data.d) : null;
// Try to determine category from stats type
let category = 'All Items';
if (stats && stats.type) {
if (stats.type.includes('Cloth')) category = 'Cloth Armor';
else if (stats.type.includes('Light')) category = 'Light Armor';
else if (stats.type.includes('Heavy')) category = 'Heavy Armor';
else if (stats.type.includes('Shield')) category = 'Shield';
else if (stats.type.includes('Staff')) category = 'Staff';
else if (stats.type.includes('Axe') || stats.type.includes('Mace') || stats.type.includes('Sword') || stats.type.includes('Estoc') || stats.type.includes('Rapier') || stats.type.includes('Club') || stats.type.includes('Dagger')) category = 'Weapon';
}
if (!stats || (!data.t && !data.d)) return; // skip empty entries
scraped.push({
id, name: data.t || 'Unknown', category,
quality: data.q, stats, source: 'armory', scrapedAt: Date.now()
});
});
}
if (scraped.length > 0) {
const db = getGearDB();
saveGearDB([...db.filter(e => e.source !== 'armory'), ...scraped]);
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats)`, 'color:#0f0');
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats, ${scraped.length - (el ? el.querySelectorAll('table tr[onmouseover]').length : 0)} from store)`, 'color:#0f0');
}
return scraped;
}

View file

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

View file

@ -158,37 +158,64 @@ function scrapeCharacterEquip() {
function scrapeArmory() {
const scraped = [];
const el = document.getElementById('equiplist');
if (!el) return scraped;
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
// First, scrape any items visible in the current filter tab
if (el) {
let cat = '';
el.querySelectorAll('table tr').forEach(row => {
if (row.classList.contains('eqtplabel')) { cat = (row.textContent || '').trim(); return; }
if (row.classList.contains('eqselall')) return;
const omo = row.getAttribute('onmouseover') || '';
const idM = omo.match(/hover_equip\((\d+)\)/);
const cb = row.querySelector('input[name="eqids[]"]');
const itemId = idM ? idM[1] : (cb ? cb.value : '');
const label = row.querySelector('label');
let name = label ? (label.textContent || '').trim() : '';
if (cb && label) name = (label.textContent || '').replace(cb.outerHTML, '').trim();
if (!itemId && !name) return;
const obj = { id: itemId, name: name || 'Unknown', category: cat, source: 'armory', scrapedAt: Date.now() };
if (itemId) {
const data = getHVEquipData(itemId);
if (data) {
if (data.t) obj.name = data.t;
if (data.q != null) obj.quality = data.q;
if (data.d) obj.stats = parseEquipHTML(data.d);
}
}
}
scraped.push(obj);
});
scraped.push(obj);
});
}
// Second, scan the full store for items NOT in the visible list
// This catches all inventory regardless of which filter tab you're on
const store = getHVEquipStore();
if (store) {
const visibleIds = new Set(scraped.map(s => s.id));
Object.entries(store).forEach(([id, data]) => {
if (visibleIds.has(id)) return; // already scraped from DOM
const stats = data.d ? parseEquipHTML(data.d) : null;
// Try to determine category from stats type
let category = 'All Items';
if (stats && stats.type) {
if (stats.type.includes('Cloth')) category = 'Cloth Armor';
else if (stats.type.includes('Light')) category = 'Light Armor';
else if (stats.type.includes('Heavy')) category = 'Heavy Armor';
else if (stats.type.includes('Shield')) category = 'Shield';
else if (stats.type.includes('Staff')) category = 'Staff';
else if (stats.type.includes('Axe') || stats.type.includes('Mace') || stats.type.includes('Sword') || stats.type.includes('Estoc') || stats.type.includes('Rapier') || stats.type.includes('Club') || stats.type.includes('Dagger')) category = 'Weapon';
}
if (!stats || (!data.t && !data.d)) return; // skip empty entries
scraped.push({
id, name: data.t || 'Unknown', category,
quality: data.q, stats, source: 'armory', scrapedAt: Date.now()
});
});
}
if (scraped.length > 0) {
const db = getGearDB();
saveGearDB([...db.filter(e => e.source !== 'armory'), ...scraped]);
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats)`, 'color:#0f0');
console.log(`%c[HV] 📦 Armory: ${scraped.length} items (${scraped.filter(s => s.stats).length} with stats, ${scraped.length - (el ? el.querySelectorAll('table tr[onmouseover]').length : 0)} from store)`, 'color:#0f0');
}
return scraped;
}

View file

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