diablo2-web/scripts/browser/checks/map-switch.js

91 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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,
}
})()