// map-switch.js — 断言"章节 / 场景 / 细分场景"三个下拉框真的跳到选中的地图 // // 这里防的是一类会静默退化的 bug:下拉框写错查询参数(早期是 `?quadrant=` 而资源包路径读 // `?level=`),于是换图失败、悄悄回到该 act 的城镇。断言方式是拦下处理函数构造出来的目标 URL // (`location.search` 的赋值在同一个任务里),再检查参数名与取值。 // // 同时核对三级结构本身:章节是「第一章…第五章」、场景是中文场景名、细分场景是所选场景的变体 // (罗格营地是 北/东/南/西)。 // // CHROME=/root/.cache/ms-playwright/chromium-1223/chrome-linux64/chrome // node scripts/browser/inspect-page.mjs "$CHROME" \ // "https://www.laiseek.xyz/diablo2/?act=1" scripts/browser/checks/map-switch.js (async () => { const sleep = (ms) => new Promise((r) => setTimeout(r, ms)) const deadline = Date.now() + 120000 while (Date.now() < deadline) { const s = window.__d2webAct if (s && (s.ready || s.error)) break await sleep(200) } await sleep(800) // 处理函数用的是 location.search 赋值,拦 URLSearchParams.toString 就能拿到目标。 const seen = [] const paramsToString = URLSearchParams.prototype.toString URLSearchParams.prototype.toString = function () { const value = paramsToString.call(this); seen.push(`?${value}`); return value } const texts = (id) => Array.from(document.querySelectorAll(`#${id} option`)).map((o) => o.textContent) const result = { source: window.__d2webAct.source, headerLinks: Array.from(document.querySelectorAll('header a')).map((a) => a.textContent), actOptions: texts('act'), sceneOptions: texts('scene'), variantOptions: texts('variant'), } /** * 选中某个下拉框的某个值并捕获它构造出的目标 URL。 * * @param id - select id. * @param pick - chooses a value out of the options. * @param match - how to judge the captured `?level=`: a variant label *is* the value, * while a scene navigates to its **first variant**, whose label starts with the slug. */ const trySwitch = (id, pick, match = (value, level) => level === value) => { const select = document.querySelector(`#${id}`) if (select === null) return { error: 'no such select' } const values = Array.from(select.querySelectorAll('option')).map((o) => o.value) const value = pick(values) ?? values[0] seen.length = 0 select.value = value select.dispatchEvent(new Event('change', { bubbles: true })) const target = seen.at(-1) ?? null let params = null try { params = target === null ? null : new URL(target, location.origin).searchParams } catch { params = null } return { picked: value, target, levelParam: params === null ? null : params.get('level'), actParam: params === null ? null : params.get('act'), switchesMap: params !== null && match(value, params.get('level') ?? '') && params.get('act') !== null, } } // 章节切换跳到该 act 的第一个场景的第一个细分:`?act=` 变了,`?level=` 也该跟着变。 result.actSwitch = trySwitch('act', values => values[1] ?? values[0], (act, level) => act !== String(window.__d2webAct.act) && level !== '') result.variantSwitch = trySwitch('variant', values => values[1] ?? values[0]) result.sceneSwitch = trySwitch('scene', values => values[1] ?? values[0], (slug, level) => level === slug || level.startsWith(`${slug}-`)) URLSearchParams.prototype.toString = paramsToString const checks = [ ['resource pack path', result.source === 'pack'], ['header 上没有多余链接', result.headerLinks.length === 0], ['章节是五章中文名', result.actOptions.join('') === '第一章第二章第三章第四章第五章'], ['场景是中文名(不是 Act N - …)', result.sceneOptions.every(text => !/^Act \d/.test(text)) && result.sceneOptions.length > 1], ['细分场景是方位(罗格营地 北/东/南/西)', result.variantOptions.includes('北') && result.variantOptions.includes('南')], ['细分场景切换写 ?level=', result.variantSwitch.switchesMap === true], ['场景切换跳到该场景的首个细分(?level= 以 slug 开头)且带 ?act=', result.sceneSwitch.switchesMap === true], ['章节切换同时改 ?act= 与 ?level=', result.actSwitch.switchesMap === true], ] return { ...result, act: window.__d2webAct.act, level: window.__d2webAct.level, pass: checks.every(([, ok]) => ok), checks, } })()