v0.14.12 - Add captureAllTooltips() with async hover-to-popup delay

- captureAllTooltips() iterates each equipment slot, calls equips.set()
  to trigger the tooltip popup, waits 300ms for the DOM to update,
  then reads and saves the full stat block from #popup_box
- Catches 'Popup Box' default text vs real tooltip
- Saves stats directly to localStorage gear DB
- Exposed as HV.captureTooltips() for console use
- For best results: visit Character Equipment page, run HV.captureTooltips()
This commit is contained in:
GaboGG 2026-07-27 15:31:06 -04:00
parent c632c13818
commit ce09755fa8
6 changed files with 201 additions and 63 deletions

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.11
// @version 0.14.12
// @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.11';
const VERSION = '0.14.12';
const CFG = {
// — Battle automation
@ -3442,18 +3442,17 @@ function scrapeCharacterEquip() {
});
// Read the popup box for the currently-displayed tooltip
const tooltip = parseTooltipPopup();
// Merge tooltip stats with the matching item
if (tooltip && tooltip.name) {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
} else if (scraped.length > 0) {
// If no match, attach to first item as extra info
scraped[0].tooltipStats = tooltip.stats;
// Note: popup_box only updates on actual hover; triggerAllTooltips
// may not work if equips.set is async. We still get names/IDs from slots.
try {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
}
}
}
} catch (e) {}
if (scraped.length > 0) {
const db = getGearDB();
@ -3563,20 +3562,66 @@ function triggerAllTooltips() {
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : slot.getAttribute('onmouseover') || '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
// Trigger the tooltip
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Read the popup
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name) {
results.push(tooltip);
}
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
} catch (e) {}
}
});
return results;
}
// Manual: hover each item and capture tooltip stats with delays
// Run this from console on the character equipment page for full stats
function captureAllTooltips(delay = 400) {
const slots = document.querySelectorAll('#eqsb > .eqb');
const results = [];
let idx = 0;
function next() {
if (idx >= slots.length) {
console.log(`%c[HV] Captured ${results.length} tooltips`, 'color:#0f0');
// Re-scrape with the captured data
autoScrapeGear();
return;
}
const slot = slots[idx];
const nameDiv = slot.querySelector(':scope > div[onmouseover]');
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Wait for popup to populate
setTimeout(() => {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
// Store in localStorage directly
const db = getGearDB();
const match = db.find(e => e.id === idMatch[1]);
if (match) {
match.stats = tooltip.stats;
saveGearDB(db);
}
results.push(tooltip);
console.log(`%c[HV] [${idx}] ${tooltip.name}`, 'color:#888');
}
idx++;
next();
}, 300);
return;
} catch (e) {}
}
idx++;
next();
}
next();
return 'Capturing tooltips... check console';
}
// ── Auto-detect and scrape current page ──
function autoScrapeGear() {
const url = window.location.href || '';
@ -3954,6 +3999,7 @@ window.HV = {
gearSummary: () => logGearSummary(),
scrapeGear: () => autoScrapeGear(),
gearDebug: () => debugScrapeGear(),
captureTooltips: () => captureAllTooltips(),
};
// ═══════════════════════════════════════════════════════════════════════

View file

@ -1,7 +1,7 @@
// ==UserScript==
// @name HV Unified
// @namespace hvunified
// @version 0.14.11
// @version 0.14.12
// @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.11';
const VERSION = '0.14.12';
const CFG = {
// — Battle automation
@ -3442,18 +3442,17 @@ function scrapeCharacterEquip() {
});
// Read the popup box for the currently-displayed tooltip
const tooltip = parseTooltipPopup();
// Merge tooltip stats with the matching item
if (tooltip && tooltip.name) {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
} else if (scraped.length > 0) {
// If no match, attach to first item as extra info
scraped[0].tooltipStats = tooltip.stats;
// Note: popup_box only updates on actual hover; triggerAllTooltips
// may not work if equips.set is async. We still get names/IDs from slots.
try {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
}
}
}
} catch (e) {}
if (scraped.length > 0) {
const db = getGearDB();
@ -3563,20 +3562,66 @@ function triggerAllTooltips() {
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : slot.getAttribute('onmouseover') || '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
// Trigger the tooltip
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Read the popup
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name) {
results.push(tooltip);
}
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
} catch (e) {}
}
});
return results;
}
// Manual: hover each item and capture tooltip stats with delays
// Run this from console on the character equipment page for full stats
function captureAllTooltips(delay = 400) {
const slots = document.querySelectorAll('#eqsb > .eqb');
const results = [];
let idx = 0;
function next() {
if (idx >= slots.length) {
console.log(`%c[HV] Captured ${results.length} tooltips`, 'color:#0f0');
// Re-scrape with the captured data
autoScrapeGear();
return;
}
const slot = slots[idx];
const nameDiv = slot.querySelector(':scope > div[onmouseover]');
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Wait for popup to populate
setTimeout(() => {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
// Store in localStorage directly
const db = getGearDB();
const match = db.find(e => e.id === idMatch[1]);
if (match) {
match.stats = tooltip.stats;
saveGearDB(db);
}
results.push(tooltip);
console.log(`%c[HV] [${idx}] ${tooltip.name}`, 'color:#888');
}
idx++;
next();
}, 300);
return;
} catch (e) {}
}
idx++;
next();
}
next();
return 'Capturing tooltips... check console';
}
// ── Auto-detect and scrape current page ──
function autoScrapeGear() {
const url = window.location.href || '';
@ -3954,6 +3999,7 @@ window.HV = {
gearSummary: () => logGearSummary(),
scrapeGear: () => autoScrapeGear(),
gearDebug: () => debugScrapeGear(),
captureTooltips: () => captureAllTooltips(),
};
// ═══════════════════════════════════════════════════════════════════════

View file

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

View file

@ -199,18 +199,17 @@ function scrapeCharacterEquip() {
});
// Read the popup box for the currently-displayed tooltip
const tooltip = parseTooltipPopup();
// Merge tooltip stats with the matching item
if (tooltip && tooltip.name) {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
} else if (scraped.length > 0) {
// If no match, attach to first item as extra info
scraped[0].tooltipStats = tooltip.stats;
// Note: popup_box only updates on actual hover; triggerAllTooltips
// may not work if equips.set is async. We still get names/IDs from slots.
try {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
const matchIdx = scraped.findIndex(s => tooltip.name.includes(s.name) || s.name.includes(tooltip.name));
if (matchIdx >= 0) {
scraped[matchIdx].stats = tooltip.stats;
}
}
}
} catch (e) {}
if (scraped.length > 0) {
const db = getGearDB();
@ -320,20 +319,66 @@ function triggerAllTooltips() {
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : slot.getAttribute('onmouseover') || '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
// Trigger the tooltip
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Read the popup
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name) {
results.push(tooltip);
}
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
} catch (e) {}
}
});
return results;
}
// Manual: hover each item and capture tooltip stats with delays
// Run this from console on the character equipment page for full stats
function captureAllTooltips(delay = 400) {
const slots = document.querySelectorAll('#eqsb > .eqb');
const results = [];
let idx = 0;
function next() {
if (idx >= slots.length) {
console.log(`%c[HV] Captured ${results.length} tooltips`, 'color:#0f0');
// Re-scrape with the captured data
autoScrapeGear();
return;
}
const slot = slots[idx];
const nameDiv = slot.querySelector(':scope > div[onmouseover]');
const omo = nameDiv ? (nameDiv.getAttribute('onmouseover') || '') : '';
const idMatch = omo.match(/equips\.set\((\d+)/);
if (idMatch && typeof equips !== 'undefined' && equips.set) {
try {
equips.set(parseInt(idMatch[1]), 'slot_pane', 250, 130);
// Wait for popup to populate
setTimeout(() => {
const tooltip = parseTooltipPopup();
if (tooltip && tooltip.name && tooltip.name !== 'Popup Box') {
// Store in localStorage directly
const db = getGearDB();
const match = db.find(e => e.id === idMatch[1]);
if (match) {
match.stats = tooltip.stats;
saveGearDB(db);
}
results.push(tooltip);
console.log(`%c[HV] [${idx}] ${tooltip.name}`, 'color:#888');
}
idx++;
next();
}, 300);
return;
} catch (e) {}
}
idx++;
next();
}
next();
return 'Capturing tooltips... check console';
}
// ── Auto-detect and scrape current page ──
function autoScrapeGear() {
const url = window.location.href || '';

View file

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

View file

@ -25,4 +25,5 @@ window.HV = {
gearSummary: () => logGearSummary(),
scrapeGear: () => autoScrapeGear(),
gearDebug: () => debugScrapeGear(),
captureTooltips: () => captureAllTooltips(),
};