184 lines
6.5 KiB
JavaScript
184 lines
6.5 KiB
JavaScript
import { spawn } from 'child_process'
|
|
|
|
async function sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms))
|
|
}
|
|
|
|
async function runBrowserAudit() {
|
|
console.log('1. Starting Google Chrome headless...')
|
|
const chromeProc = spawn('/usr/bin/google-chrome', [
|
|
'--headless=new',
|
|
'--remote-debugging-port=9222',
|
|
'--no-sandbox',
|
|
'--disable-gpu',
|
|
'--disable-extensions',
|
|
'http://localhost:8080/boss.html',
|
|
])
|
|
|
|
// Wait for Chrome to be ready
|
|
let wsUrl = null
|
|
for (let i = 0; i < 30; i++) {
|
|
await sleep(300)
|
|
try {
|
|
const res = await fetch('http://127.0.0.1:9222/json/list')
|
|
const pages = await res.json()
|
|
const page = pages.find(p => p.type === 'page' && p.url.includes('boss.html'))
|
|
if (page && page.webSocketDebuggerUrl) {
|
|
wsUrl = page.webSocketDebuggerUrl
|
|
break
|
|
}
|
|
} catch (e) {
|
|
// keep trying
|
|
}
|
|
}
|
|
|
|
if (!wsUrl) {
|
|
chromeProc.kill()
|
|
throw new Error('Failed to connect to Chrome headless CDP within 10s')
|
|
}
|
|
|
|
console.log('2. Connected to Chrome CDP on page:', wsUrl)
|
|
|
|
const ws = new WebSocket(wsUrl)
|
|
await new Promise(resolve => ws.onopen = resolve)
|
|
|
|
let idCounter = 1
|
|
function sendCommand(method, params = {}) {
|
|
return new Promise((resolve, reject) => {
|
|
const id = idCounter++
|
|
const handler = event => {
|
|
const msg = JSON.parse(event.data)
|
|
if (msg.id === id) {
|
|
ws.removeEventListener('message', handler)
|
|
if (msg.error) reject(msg.error)
|
|
else resolve(msg.result)
|
|
}
|
|
}
|
|
ws.addEventListener('message', handler)
|
|
ws.send(JSON.stringify({ id, method, params }))
|
|
})
|
|
}
|
|
|
|
// Enable Runtime
|
|
await sendCommand('Runtime.enable')
|
|
|
|
// Wait 1.5s for initial drop tables to load
|
|
await sleep(1500)
|
|
|
|
console.log('3. Triggering Chinese language switch and 10x Boss Kill in browser DOM...')
|
|
|
|
const evalResult = await sendCommand('Runtime.evaluate', {
|
|
expression: `(async () => {
|
|
// 1. Click Chinese language pill
|
|
const zhPill = document.querySelector('.lang-pill[data-lang="zh"]');
|
|
if (zhPill) zhPill.click();
|
|
|
|
// 2. Click 10x kill button
|
|
const kill10Btn = document.getElementById('btn-kill-10');
|
|
if (kill10Btn) kill10Btn.click();
|
|
|
|
// Wait a short moment for DOM rendering
|
|
await new Promise(r => setTimeout(r, 800));
|
|
|
|
// 3. Extract items
|
|
const cards = Array.from(document.querySelectorAll('#loot-items-grid .d2-item-card'));
|
|
return cards.map(c => {
|
|
const title = c.querySelector('.item-title')?.textContent?.trim() || '';
|
|
const subTitle = c.querySelector('.item-subtitle')?.textContent?.trim() || '';
|
|
const quality = c.querySelector('.item-quality-pill')?.textContent?.trim() || '';
|
|
const ilvl = c.querySelector('.item-ilvl-pill')?.textContent?.trim() || '';
|
|
const stats = Array.from(c.querySelectorAll('.item-base-stats .base-stat-item')).map(s => s.textContent.trim());
|
|
const affixes = Array.from(c.querySelectorAll('.item-affix-lines .tooltip-stat-line')).map(a => a.textContent.trim());
|
|
return {
|
|
title,
|
|
subTitle,
|
|
quality,
|
|
ilvl,
|
|
stats,
|
|
affixes
|
|
};
|
|
});
|
|
})()`,
|
|
awaitPromise: true,
|
|
returnByValue: true,
|
|
})
|
|
|
|
console.log('4. Evaluation completed. Closing Chrome...')
|
|
ws.close()
|
|
chromeProc.kill()
|
|
|
|
const items = evalResult.result.value
|
|
console.log(`\nSuccessfully captured ${items.length} items from http://localhost:8080/boss.html!\n`)
|
|
return items
|
|
}
|
|
|
|
runBrowserAudit()
|
|
.then(items => {
|
|
console.log('================================================================');
|
|
console.log(`=== AUDITING ALL ${items.length} ITEMS GENERATED FROM 10x BOSS KILL ===`);
|
|
console.log('================================================================\n');
|
|
|
|
let errorsFound = 0;
|
|
const traditionalChars = ['屬', '無', '紅', '綠', '變', '爛', '誕', '誠', '雲', '犧', '嚇', '層', '靜', '換', '亂', '節', '甦'];
|
|
|
|
items.forEach((item, index) => {
|
|
console.log(`[#${index + 1}] [${item.quality}] ${item.title} (底材: ${item.subTitle || '无'}, ${item.ilvl})`);
|
|
if (item.stats.length > 0) {
|
|
console.log(` 基础属性: ${item.stats.join(' | ')}`);
|
|
}
|
|
if (item.affixes.length > 0) {
|
|
console.log(` 词条属性:`);
|
|
item.affixes.forEach(aff => console.log(` * ${aff}`));
|
|
}
|
|
|
|
// Check 1: Negative numbers in charged spells
|
|
item.affixes.forEach(aff => {
|
|
if (aff.includes('次充能') && (aff.includes('等级 -') || aff.includes('(-'))) {
|
|
console.error(` ❌ ERROR: Negative value in charged spell: "${aff}"`);
|
|
errorsFound++;
|
|
}
|
|
});
|
|
|
|
// Check 2: Missing English affix translations (e.g. English letters followed by 之 or preceded by 之)
|
|
const englishAffixMatch = item.title.match(/[a-zA-Z]+之|之[a-zA-Z]+/);
|
|
if (englishAffixMatch) {
|
|
console.error(` ❌ ERROR: Untranslated English affix in title: "${item.title}"`);
|
|
errorsFound++;
|
|
}
|
|
|
|
// Check 3: Word order anomaly: base name followed by '之' at the end of title
|
|
if (item.subTitle && item.title.endsWith(item.subTitle + '之')) {
|
|
console.error(` ❌ ERROR: Word order inversion (base followed by suffix particle): "${item.title}"`);
|
|
errorsFound++;
|
|
}
|
|
|
|
// Check 4: Traditional Chinese characters
|
|
const allText = item.title + ' ' + item.subTitle + ' ' + item.affixes.join(' ');
|
|
traditionalChars.forEach(ch => {
|
|
if (allText.includes(ch)) {
|
|
console.error(` ❌ ERROR: Traditional Chinese character '${ch}' found in: "${allText}"`);
|
|
errorsFound++;
|
|
}
|
|
});
|
|
|
|
console.log('');
|
|
});
|
|
|
|
console.log('================================================================');
|
|
if (errorsFound === 0) {
|
|
console.log(`🎉 AUDIT PASSED! All ${items.length} items checked with ZERO errors!`);
|
|
console.log(`✅ All names in correct Chinese grammar word order`);
|
|
console.log(`✅ 100% Affix translations matched without fallback artifacts`);
|
|
console.log(`✅ Charged spell positive charges and positive skill levels verified`);
|
|
console.log(`✅ Normalized Simplified Chinese characters verified`);
|
|
} else {
|
|
console.error(`⚠️ AUDIT FOUND ${errorsFound} ERRORS!`);
|
|
process.exit(1);
|
|
}
|
|
console.log('================================================================\n');
|
|
})
|
|
.catch(err => {
|
|
console.error('Audit execution failed:', err);
|
|
process.exit(1);
|
|
});
|