91 lines
4.5 KiB
JavaScript
91 lines
4.5 KiB
JavaScript
// 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,
|
||
}
|
||
})()
|