From 06f54f8e1ca78b49f0cf8e6afd0e5fcaf0f484e9 Mon Sep 17 00:00:00 2001 From: fugui Date: Tue, 8 Sep 2026 00:45:03 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=B2=B9=E7=8C=B4=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- code-v0.6.1.user.js | 1244 +++++++++++++++++ code-v1.6.1.user.js | 1166 +++++++++++++++ code-v1.6.2jr.user.js | 1181 ++++++++++++++++ fixed-site-replacer-main/bookmarklet.txt | 1 + fixed-site-replacer-main/code-v0.6.1.user.js | 1244 +++++++++++++++++ fixed-site-replacer-main/code-v1.6.1.user.js | 1166 +++++++++++++++ .../code-v1.6.2jr.user.js | 1181 ++++++++++++++++ fixed-site-replacer-main/code.js | 656 +++++++++ fixed-site-replacer-main/code.user.js | 656 +++++++++ fixed-site-replacer-main/index.html | 349 +++++ .../koshigaya-accounts.js | 1012 ++++++++++++++ fixed-site-replacer-main/nagoya-accounts.js | 302 ++++ .../namco-parks-rename.user.js | 1155 +++++++++++++++ fixed-site-replacer-main/namco2.js | 1151 +++++++++++++++ fixed-site-replacer-main/namco2.user.js | 1151 +++++++++++++++ fixed-site-replacer-main/shibuya-accounts.js | 772 ++++++++++ index.html | 349 +++++ 17 files changed, 14736 insertions(+) create mode 100644 code-v0.6.1.user.js create mode 100644 code-v1.6.1.user.js create mode 100644 code-v1.6.2jr.user.js create mode 100644 fixed-site-replacer-main/bookmarklet.txt create mode 100644 fixed-site-replacer-main/code-v0.6.1.user.js create mode 100644 fixed-site-replacer-main/code-v1.6.1.user.js create mode 100644 fixed-site-replacer-main/code-v1.6.2jr.user.js create mode 100644 fixed-site-replacer-main/code.js create mode 100644 fixed-site-replacer-main/code.user.js create mode 100644 fixed-site-replacer-main/index.html create mode 100644 fixed-site-replacer-main/koshigaya-accounts.js create mode 100644 fixed-site-replacer-main/nagoya-accounts.js create mode 100644 fixed-site-replacer-main/namco-parks-rename.user.js create mode 100644 fixed-site-replacer-main/namco2.js create mode 100644 fixed-site-replacer-main/namco2.user.js create mode 100644 fixed-site-replacer-main/shibuya-accounts.js create mode 100644 index.html diff --git a/code-v0.6.1.user.js b/code-v0.6.1.user.js new file mode 100644 index 0000000..d0474f6 --- /dev/null +++ b/code-v0.6.1.user.js @@ -0,0 +1,1244 @@ +// ==UserScript== +// @name Fixed Site Name Replacer2 +// @namespace local.codex.fixed-site-replacer +// @version 0.6.1 +// @description 本地替换 Microsoft 登录页、Outlook 和 Bandai Parks 页面上的显示文字。(iOS Safari Userscripts / 油猴双兼容;触发:长按 2 秒或三击顶部区域) +// @match https://login.microsoftonline.com/* +// @match https://outlook.live.com/* +// @match https://parks2.bandainamco-am.co.jp/* +// @grant none +// @run-at document-start +// ==/UserScript== + +(() => { + "use strict"; + + const STORAGE_KEY = "codex.fixedSite.nameReplacer.config.v1"; + const SUPPORTED_HOSTS = [ + "login.microsoftonline.com", + "outlook.live.com", + "parks2.bandainamco-am.co.jp", + ]; + const PANEL_ID = "codex-msmail-name-panel"; + const STYLE_ID = "codex-msmail-name-style"; + const SESSION_DURATION_MS = 4 * 60 * 60 * 1000; + const PANEL_HOLD_MS = 2000; + const PANEL_HOLD_ZONE_PX = 100; + const TRIPLE_CLICK_WINDOW_MS = 500; + const TRIPLE_CLICK_MAX_SPREAD = 40; + const FAST_SCAN_WINDOW_MS = 4000; + const FAST_SCAN_INTERVAL_MS = 120; + const DEFAULT_RULES = [ + { enabled: true, original: "", replacement: "", mode: "normal" }, + { enabled: true, original: "", replacement: "", mode: "normal" }, + ]; + + let originalTextMap = new WeakMap(); + let originalValueMap = new WeakMap(); + const touchedTextNodes = new Set(); + const touchedElements = new Set(); + let observer = null; + let applying = false; + let statusUpdater = null; + let holdTimer = null; + let topHoldStartY = 0; + let threeFingerHold = false; + let tripleClickTimes = []; + let fastScanInterval = null; + let fastScanStopTimer = null; + + const defaultConfig = { + enabled: false, + fontAdjust: false, + panelVisible: false, + bodyCollapsed: false, + sessionStartedAt: null, + rules: DEFAULT_RULES, + }; + + const cloneRules = (rules) => + (Array.isArray(rules) ? rules : DEFAULT_RULES).map((rule) => ({ + enabled: rule.enabled !== false, + original: String(rule.original || ""), + replacement: String(rule.replacement || ""), + mode: rule.mode === "regex" ? "regex" : "normal", + })); + + const readStoredConfig = () => { + if (typeof GM_getValue === "function") { + return GM_getValue(STORAGE_KEY, "{}"); + } + return localStorage.getItem(STORAGE_KEY) || "{}"; + }; + + const writeStoredConfig = (value) => { + if (typeof GM_setValue === "function") { + GM_setValue(STORAGE_KEY, value); + return; + } + localStorage.setItem(STORAGE_KEY, value); + }; + + const loadConfig = () => { + try { + const saved = JSON.parse(readStoredConfig()); + return { + ...defaultConfig, + ...saved, + panelVisible: false, + sessionStartedAt: + typeof saved.sessionStartedAt === "number" && saved.sessionStartedAt > 0 + ? saved.sessionStartedAt + : null, + rules: cloneRules(saved.rules), + }; + } catch { + return { + ...defaultConfig, + rules: cloneRules(defaultConfig.rules), + }; + } + }; + + let config = loadConfig(); + config.panelVisible = false; + + const saveConfig = () => { + writeStoredConfig( + JSON.stringify({ + ...config, + rules: cloneRules(config.rules), + }) + ); + }; + + const normalize = (value) => value.replace(/\s+/g, " ").trim(); + + const siteAuthorized = () => { + return SUPPORTED_HOSTS.includes(location.hostname); + }; + + const isOutlookPage = () => location.hostname === "outlook.live.com"; + + const shouldSkipNode = (node) => { + const element = + node instanceof Element ? node : node?.parentElement instanceof Element ? node.parentElement : null; + + if (!element) return false; + if (element.closest(`#${PANEL_ID}`)) return true; + if (["SCRIPT", "STYLE", "NOSCRIPT"].includes(element.tagName)) return true; + return Boolean(element.closest('iframe, [contenteditable="true"]')); + }; + + const getReplacementRoots = () => { + if (!document.body) return []; + return [document.body]; + }; + + const hasValidSession = () => typeof config.sessionStartedAt === "number" && config.sessionStartedAt > 0; + + const getSessionExpiresAt = () => + hasValidSession() ? config.sessionStartedAt + SESSION_DURATION_MS : 0; + + const getRemainingMs = () => + hasValidSession() ? Math.max(0, getSessionExpiresAt() - Date.now()) : 0; + + const formatRemaining = (ms) => { + const totalSeconds = Math.ceil(ms / 1000); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + }; + + const hasExpired = () => hasValidSession() && getRemainingMs() <= 0; + + const isReplacementActive = () => + config.enabled && siteAuthorized() && hasValidSession() && !hasExpired(); + + const replaceByRule = (input, rule) => { + if (!rule.enabled || !rule.original || !rule.replacement) { + return input; + } + + if (rule.mode === "regex") { + try { + return input.replace(new RegExp(rule.original, "g"), rule.replacement); + } catch { + return input; + } + } + + return input.split(rule.original).join(rule.replacement); + }; + + const replaceText = (text) => { + if (!isReplacementActive()) return text; + return config.rules.reduce((next, rule) => replaceByRule(next, rule), text); + }; + + const rememberOriginalText = (node, value) => { + touchedTextNodes.add(node); + if (!originalTextMap.has(node)) { + originalTextMap.set(node, value); + } + }; + + const rememberOriginalValue = (element, key, value) => { + touchedElements.add(element); + let item = originalValueMap.get(element); + if (!item) { + item = {}; + originalValueMap.set(element, item); + } + if (!(key in item)) { + item[key] = value; + } + }; + + const restoreTouchedContent = () => { + touchedTextNodes.forEach((node) => { + if (node.isConnected) { + restoreNode(node); + } + }); + + touchedElements.forEach((element) => { + if (element.isConnected) { + restoreElement(element); + } + }); + + touchedTextNodes.clear(); + touchedElements.clear(); + originalTextMap = new WeakMap(); + originalValueMap = new WeakMap(); + }; + + const restoreNode = (node) => { + if (!originalTextMap.has(node)) return; + + const original = originalTextMap.get(node); + if (node.nodeValue !== original) { + node.nodeValue = original; + } + }; + + const restoreElement = (element) => { + const item = originalValueMap.get(element); + if (!item) return; + + if ("value" in item && typeof element.value === "string" && element.value !== item.value) { + element.value = item.value; + } + + if ( + "placeholder" in item && + typeof element.placeholder === "string" && + element.placeholder !== item.placeholder + ) { + element.placeholder = item.placeholder; + } + }; + + const updateTextNode = (node) => { + const current = node.nodeValue; + if (!current || !normalize(current)) return; + + if (!isReplacementActive()) return; + + rememberOriginalText(node, current); + const base = originalTextMap.get(node) || current; + const next = replaceText(base); + if (next !== current) { + node.nodeValue = next; + } + }; + + const updateElementValue = (element) => { + if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) { + return; + } + + if (shouldSkipNode(element)) return; + + if (!isReplacementActive()) return; + + if (typeof element.placeholder === "string") { + rememberOriginalValue(element, "placeholder", element.placeholder); + const nextPlaceholder = replaceText( + originalValueMap.get(element)?.placeholder || element.placeholder + ); + if (nextPlaceholder !== element.placeholder) { + element.placeholder = nextPlaceholder; + } + } + }; + + const walkAndReplace = (root) => { + if (!root) return; + + const walker = document.createTreeWalker( + root, + NodeFilter.SHOW_TEXT, + { + acceptNode(node) { + if (!node.parentElement) return NodeFilter.FILTER_REJECT; + if (shouldSkipNode(node)) return NodeFilter.FILTER_REJECT; + return NodeFilter.FILTER_ACCEPT; + }, + } + ); + + let textNode = walker.nextNode(); + while (textNode) { + updateTextNode(textNode); + textNode = walker.nextNode(); + } + + if (root instanceof Element) { + updateElementValue(root); + root.querySelectorAll("input, textarea").forEach(updateElementValue); + } + }; + + const applyFontAdjust = () => { + const active = Boolean(config.fontAdjust && isReplacementActive()); + document.documentElement.classList.toggle("codex-msmail-font-adjust", active); + }; + + const updateStatusText = (message) => { + const statusNode = document.querySelector(`#${PANEL_ID} [data-role="status"]`); + if (!statusNode) return; + + if (message) { + statusNode.textContent = message; + return; + } + + if (!config.enabled) { + statusNode.textContent = "替换功能已关闭,页面正常显示。"; + return; + } + + if (!hasValidSession()) { + statusNode.textContent = "尚未开始计时,点击保存并应用后开始 4 小时倒计时。"; + return; + } + + if (hasExpired()) { + statusNode.textContent = "已超过 4 小时,替换功能自动失效。"; + return; + } + + statusNode.textContent = `替换功能开启中,剩余时间: ${formatRemaining(getRemainingMs())}`; + }; + + const syncStatusLoop = () => { + if (statusUpdater) { + clearInterval(statusUpdater); + } + + statusUpdater = window.setInterval(() => { + if (config.enabled && hasExpired()) { + disableReplacement("已超过 4 小时,替换功能自动失效。"); + return; + } + updateStatusText(); + }, 1000); + }; + + const applyReplacements = (statusMessage = "") => { + if (applying) return; + applying = true; + + try { + applyFontAdjust(); + if (isReplacementActive()) { + getReplacementRoots().forEach(walkAndReplace); + } + updateStatusText(statusMessage); + } finally { + applying = false; + } + }; + + const stopFastScanLoop = () => { + if (fastScanInterval) { + clearInterval(fastScanInterval); + fastScanInterval = null; + } + if (fastScanStopTimer) { + clearTimeout(fastScanStopTimer); + fastScanStopTimer = null; + } + }; + + const startFastScanLoop = () => { + stopFastScanLoop(); + + const tick = () => { + if (document.body) { + applyReplacements(); + } + }; + + tick(); + fastScanInterval = window.setInterval(tick, FAST_SCAN_INTERVAL_MS); + fastScanStopTimer = window.setTimeout(() => { + stopFastScanLoop(); + }, FAST_SCAN_WINDOW_MS); + }; + + const startObserver = () => { + if (observer) observer.disconnect(); + + observer = new MutationObserver((mutations) => { + if (applying) return; + + for (const mutation of mutations) { + if (mutation.type === "characterData") { + updateTextNode(mutation.target); + continue; + } + + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.TEXT_NODE) { + updateTextNode(node); + } else if (node.nodeType === Node.ELEMENT_NODE) { + walkAndReplace(node); + } + }); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true, + characterData: true, + }); + }; + + const ensureStyles = () => { + if (document.getElementById(STYLE_ID)) return; + + const style = document.createElement("style"); + style.id = STYLE_ID; + style.textContent = ` + .codex-msmail-font-adjust body, + .codex-msmail-font-adjust input, + .codex-msmail-font-adjust button, + .codex-msmail-font-adjust textarea, + .codex-msmail-font-adjust select { + letter-spacing: 0.02em !important; + } + + #${PANEL_ID} { + position: fixed; + top: max(12px, env(safe-area-inset-top)); + left: 12px; + right: 12px; + z-index: 2147483647; + background: rgba(247, 244, 237, 0.98); + color: #2e2a26; + border: 1px solid rgba(60, 49, 38, 0.16); + border-radius: 10px; + box-shadow: 0 18px 40px rgba(27, 22, 18, 0.18); + padding: 12px; + font: 13px/1.35 -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + max-height: calc(100vh - max(24px, env(safe-area-inset-top)) - max(24px, env(safe-area-inset-bottom))); + overflow: hidden; + } + + #${PANEL_ID}[hidden] { + display: none !important; + } + + #${PANEL_ID}.is-collapsed .codex-body { + display: none; + } + + #${PANEL_ID} .codex-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + } + + #${PANEL_ID} .codex-header-main { + min-width: 0; + } + + #${PANEL_ID} .codex-title { + font-size: 18px; + font-weight: 700; + border: 0; + background: transparent; + color: #2e2a26; + padding: 0; + text-align: left; + } + + #${PANEL_ID} .codex-close { + border: 0; + background: transparent; + color: #4b433b; + font-size: 14px; + padding: 2px 4px; + } + + #${PANEL_ID} .codex-meta { + margin-top: 4px; + color: #6d6256; + display: grid; + gap: 2px; + word-break: break-all; + } + + #${PANEL_ID} .codex-body { + margin-top: 12px; + display: grid; + gap: 10px; + max-height: calc(100vh - 180px); + overflow-y: auto; + overflow-x: hidden; + padding-right: 2px; + -webkit-overflow-scrolling: touch; + } + + #${PANEL_ID} button, + #${PANEL_ID} input, + #${PANEL_ID} textarea, + #${PANEL_ID} select { + font: inherit; + } + + #${PANEL_ID} .codex-action-row { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + #${PANEL_ID} .codex-btn { + border: 0; + border-radius: 999px; + padding: 8px 12px; + background: #ddd4c7; + color: #352f29; + } + + #${PANEL_ID} .codex-btn.primary { + background: #c6b091; + color: #201915; + } + + #${PANEL_ID} .codex-input { + width: 100%; + box-sizing: border-box; + border: 1px solid #d5c8b8; + border-radius: 8px; + padding: 9px 10px; + background: rgba(255, 255, 255, 0.9); + color: #2f2924; + } + + #${PANEL_ID} .codex-check-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + color: #433b34; + } + + #${PANEL_ID} .codex-check { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.68); + } + + #${PANEL_ID} .codex-table { + display: grid; + gap: 8px; + } + + #${PANEL_ID} .codex-rules-scroll { + overflow-x: auto; + overflow-y: visible; + -webkit-overflow-scrolling: touch; + padding-bottom: 4px; + } + + #${PANEL_ID} .codex-rule-guide { + display: grid; + gap: 6px; + padding: 10px 12px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.62); + color: #5c5248; + } + + #${PANEL_ID} .codex-guide-strong { + font-weight: 700; + color: #2f2924; + } + + #${PANEL_ID} .codex-table-head, + #${PANEL_ID} .codex-rule-row { + display: grid; + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 58px; + gap: 8px; + align-items: center; + } + + #${PANEL_ID}.is-advanced .codex-table-head, + #${PANEL_ID}.is-advanced .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 112px 58px; + } + + #${PANEL_ID} .codex-col-mode, + #${PANEL_ID} .codex-mode-block { + display: none; + } + + #${PANEL_ID}.is-advanced .codex-col-mode, + #${PANEL_ID}.is-advanced .codex-mode-block { + display: block; + } + + #${PANEL_ID} .codex-table-head { + color: #5d5348; + font-weight: 600; + } + + #${PANEL_ID} .codex-rule-row { + background: rgba(255, 255, 255, 0.48); + border-radius: 14px; + padding: 12px; + border: 1px solid rgba(190, 176, 157, 0.5); + } + + #${PANEL_ID} .codex-field-block { + display: grid; + gap: 4px; + min-width: 0; + } + + #${PANEL_ID} .codex-field-label { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.01em; + } + + #${PANEL_ID} .codex-field-label.original { + color: #8a5a26; + } + + #${PANEL_ID} .codex-field-label.replacement { + color: #1f6b45; + } + + #${PANEL_ID} .codex-rule-row .codex-input.original { + border-color: #d9bf9a; + background: rgba(255, 248, 240, 0.95); + } + + #${PANEL_ID} .codex-rule-row .codex-input.replacement { + border-color: #9fc7ae; + background: rgba(244, 255, 248, 0.95); + } + + #${PANEL_ID} .codex-small-btn { + border: 0; + border-radius: 8px; + background: #e4d9cb; + color: #443b32; + padding: 11px 8px; + } + + #${PANEL_ID} .codex-status { + color: #75685a; + font-size: 12px; + } + + #${PANEL_ID} .codex-rule-toggle { + display: flex; + align-items: center; + justify-content: center; + } + + #${PANEL_ID} .codex-rule-toggle input { + width: 22px; + height: 22px; + } + + #${PANEL_ID} .codex-mode-block { + display: grid; + gap: 4px; + } + + #${PANEL_ID} .codex-mode-label { + font-size: 11px; + font-weight: 700; + color: #555048; + } + + @media (max-width: 520px) { + #${PANEL_ID} .codex-table-head, + #${PANEL_ID} .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 58px; + } + + #${PANEL_ID}.is-advanced .codex-table-head, + #${PANEL_ID}.is-advanced .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 104px 52px; + } + + #${PANEL_ID} .codex-rule-row { + gap: 10px; + } + } + `; + + document.head.appendChild(style); + }; + + const escapeHtml = (value) => + value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + + const buildMetaText = () => [ + `当前网站: ${location.hostname}`, + ]; + + // 从会员页面 HTML 中提取用户信息字段(通用解析器) + const extractMemberInfo = (html) => { + const doc = new DOMParser().parseFromString(html, "text/html"); + const fields = []; + const push = (label, value) => { + const v = String(value || "").replace(/\s+/g, " ").trim(); + if (v && v.length <= 80 && !fields.some((f) => f.value === v)) { + fields.push({ label: String(label || "").replace(/\s+/g, " ").trim(), value: v }); + } + }; + + // 1) dl > dt + dd 结构 + doc.querySelectorAll("dl").forEach((dl) => { + const dt = dl.querySelector("dt"); + const dd = dl.querySelector("dd"); + if (dt && dd) push(dt.textContent, dd.textContent); + }); + + // 2) table 中 th + td 结构 + doc.querySelectorAll("tr").forEach((tr) => { + const th = tr.querySelector("th"); + const td = tr.querySelector("td"); + if (th && td) push(th.textContent, td.textContent); + }); + + // 3) 输入框 value(文本类) + doc.querySelectorAll("input").forEach((inp) => { + const t = (inp.type || "text").toLowerCase(); + if (["text", "email", "tel", "search"].includes(t) && inp.value) { + const label = inp.getAttribute("aria-label") || inp.getAttribute("title") || inp.name || "入力値"; + push(label, inp.value); + } + }); + + // 4) 常见日文会员字段名:找到标签元素,取其相邻值 + const knownLabels = ["会員番号", "会員No", "会員ID", "氏名", "お名前", "フリガナ", "ニックネーム", "メールアドレス", "電話番号", "生年月日", "性別", "住所"]; + const labelCandidates = doc.querySelectorAll("div, span, p, label, th, dt"); + knownLabels.forEach((lb) => { + const el = Array.from(labelCandidates).find((e) => e.textContent.replace(/\s+/g, "").trim() === lb); + if (!el) return; + const sibling = el.nextElementSibling; + if (sibling) push(lb, sibling.textContent); + }); + + // 5) 页面内嵌脚本变量 member_data(会員番号 / 生年月日 / 性別) + const memberDataMatch = html.match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})(?:\s*;)?\s*(?:<\/script>|$)/i); + if (memberDataMatch) { + try { + const data = JSON.parse(memberDataMatch[1]); + if (data.member_id) push("会員番号", data.member_id); + if (data.birth) push("生年月日", data.birth); + if (data.sex) push("性別", data.sex); + } catch (e) { + // 忽略解析失败 + } + } + + // 6) ポイント(現在のポイント:Nポイント,值前后可能夹着标签) + const pointMatch = html.match(/現在のポイント[::][^0-9]*([\d,]+)[^0-9]*ポイント/); + if (pointMatch) push("現在のポイント", pointMatch[1]); + + // 7) 页面标题 h1「XXX さんのマイページ」→ 昵称 + const titleMatch = html.match(/]*>([^<]*?)\s*さんのマイページ<\/h1>/); + if (titleMatch && titleMatch[1]) push("ニックネーム(页面标题)", titleMatch[1]); + + return fields; + }; + + const createRuleRowHtml = (rule, index) => ` +
+
+ +
+
+
网页当前显示的原文字
+ +
+
+
你想显示的新文字
+ +
+
+
模式
+ +
+ +
+ `; + + const getPanel = () => document.getElementById(PANEL_ID); + + const syncPanelVisibility = () => { + const panel = getPanel(); + if (!panel) return; + panel.hidden = !config.panelVisible; + panel.classList.toggle("is-collapsed", Boolean(config.bodyCollapsed)); + }; + + const showPanel = () => { + config.panelVisible = true; + saveConfig(); + syncPanelVisibility(); + updateStatusText("设置面板已打开。"); + }; + + const hidePanel = () => { + config.panelVisible = false; + saveConfig(); + syncPanelVisibility(); + }; + + const togglePanelBody = () => { + config.bodyCollapsed = !config.bodyCollapsed; + saveConfig(); + syncPanelVisibility(); + }; + + const startSessionNow = () => { + config.sessionStartedAt = Date.now(); + }; + + const disableReplacement = (message = "替换功能已关闭,页面正常显示。") => { + config.enabled = false; + config.sessionStartedAt = null; + saveConfig(); + hidePanel(); + }; + + const clearHoldTimer = () => { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; + } + threeFingerHold = false; + }; + + const clearSelection = () => { + const sel = window.getSelection && window.getSelection(); + if (sel && typeof sel.removeAllRanges === "function") { + sel.removeAllRanges(); + } + }; + + const recordTripleClick = (clientY) => { + if (config.panelVisible) return; + if (clientY > PANEL_HOLD_ZONE_PX) return; + + const now = Date.now(); + tripleClickTimes = tripleClickTimes.filter( + (t) => now - t.time <= TRIPLE_CLICK_WINDOW_MS + ); + tripleClickTimes.push({ time: now, y: clientY }); + + if (tripleClickTimes.length < 3) return; + + const ys = tripleClickTimes.map((t) => t.y); + const spread = Math.max(...ys) - Math.min(...ys); + tripleClickTimes = []; + + if (spread <= TRIPLE_CLICK_MAX_SPREAD) { + showPanel(); + setTimeout(clearSelection, 0); + } + }; + + const startTopHoldDetector = () => { + const beginHold = (clientY, isThreeFinger = false) => { + if (config.panelVisible) return; + if (!isThreeFinger && clientY > PANEL_HOLD_ZONE_PX) return; + + clearHoldTimer(); + topHoldStartY = clientY; + threeFingerHold = isThreeFinger; + holdTimer = window.setTimeout(() => { + holdTimer = null; + threeFingerHold = false; + showPanel(); + }, PANEL_HOLD_MS); + }; + + const moveHold = (clientY, touchesLength = 1) => { + if (!holdTimer) return; + if (threeFingerHold && touchesLength !== 3) { + clearHoldTimer(); + return; + } + if ( + Math.abs(clientY - topHoldStartY) > 14 || + (!threeFingerHold && clientY > PANEL_HOLD_ZONE_PX + 20) + ) { + clearHoldTimer(); + } + }; + + document.addEventListener( + "touchstart", + (event) => { + if (event.touches.length === 1) { + beginHold(event.touches[0].clientY, false); + return; + } + + if (event.touches.length === 3) { + beginHold(event.touches[0].clientY, true); + return; + } + + clearHoldTimer(); + }, + { passive: true } + ); + + document.addEventListener( + "touchmove", + (event) => { + if (event.touches.length < 1) { + clearHoldTimer(); + return; + } + moveHold(event.touches[0].clientY, event.touches.length); + }, + { passive: true } + ); + + document.addEventListener("touchend", clearHoldTimer, { passive: true }); + document.addEventListener("touchcancel", clearHoldTimer, { passive: true }); + + document.addEventListener("mousedown", (event) => { + beginHold(event.clientY); + // 顶部区域内、且已有近期点击(正在形成三击的后续点击)→ 阻止浏览器选中文本 + const isInZone = event.clientY <= PANEL_HOLD_ZONE_PX; + const lastTap = + tripleClickTimes.length > 0 + ? tripleClickTimes[tripleClickTimes.length - 1] + : null; + const isChainTap = + lastTap !== null && + Date.now() - lastTap.time <= TRIPLE_CLICK_WINDOW_MS; + if (isInZone && isChainTap && event.defaultPrevented === false) { + event.preventDefault(); + } + }); + + document.addEventListener("mousemove", (event) => { + moveHold(event.clientY); + }); + + document.addEventListener("mouseup", (event) => { + clearHoldTimer(); + recordTripleClick(event.clientY); + }); + document.addEventListener("mouseleave", clearHoldTimer); + }; + + const buildPanel = () => { + if (document.getElementById(PANEL_ID)) return; + + ensureStyles(); + + const panel = document.createElement("section"); + panel.id = PANEL_ID; + + panel.innerHTML = ` +
+
+ +
${buildMetaText() + .map((line) => `
${escapeHtml(line)}
`) + .join("")}
+
+ +
+
+
+ + +
+
+ + + + + +
+
+
+
+
左边填网页当前显示的内容,右边填你想显示的新内容。
+
规则会替换页面里匹配到的可见文字;登录框里手动输入的内容不会被修改。
+
示例: 原文字 user@example.com -> 新文字 alias@example.com
+
+ +
+
+
+
原文字
+
新文字
+
模式
+
+
+
+
+
+
+ `; + + const rulesContainer = panel.querySelector(".codex-rules"); + const advancedToggleBtn = panel.querySelector('[data-action="toggle-advanced"]'); + + const syncAdvancedMode = () => { + const hasRegex = (config.rules || []).some((r) => r.mode === "regex"); + panel.classList.toggle("is-advanced", hasRegex); + if (advancedToggleBtn) { + advancedToggleBtn.textContent = hasRegex ? "↩ 返回简单替换" : "⚙ 高级替换(正则)"; + } + }; + + const renderRules = () => { + if (!rulesContainer) return; + rulesContainer.innerHTML = config.rules.map((rule, index) => createRuleRowHtml(rule, index)).join(""); + syncAdvancedMode(); + }; + + const syncFields = () => { + panel.querySelectorAll("[data-field]").forEach((input) => { + const field = input.getAttribute("data-field"); + if (!field) return; + + if (input instanceof HTMLInputElement && input.type === "checkbox") { + input.checked = Boolean(config[field]); + } else if (input instanceof HTMLInputElement) { + input.value = String(config[field] || ""); + } + }); + }; + + const readFields = () => { + panel.querySelectorAll("[data-field]").forEach((input) => { + const field = input.getAttribute("data-field"); + if (!field) return; + + if (input instanceof HTMLInputElement && input.type === "checkbox") { + config[field] = input.checked; + } else if (input instanceof HTMLInputElement) { + config[field] = input.value.trim(); + } + }); + }; + + const readRules = () => { + const nextRules = []; + panel.querySelectorAll(".codex-rule-row").forEach((row) => { + const enabled = row.querySelector('[data-rule-field="enabled"]'); + const original = row.querySelector('[data-rule-field="original"]'); + const replacement = row.querySelector('[data-rule-field="replacement"]'); + const mode = row.querySelector('[data-rule-field="mode"]'); + + nextRules.push({ + enabled: enabled instanceof HTMLInputElement ? enabled.checked : true, + original: original instanceof HTMLInputElement ? original.value.trim() : "", + replacement: replacement instanceof HTMLInputElement ? replacement.value.trim() : "", + mode: mode instanceof HTMLSelectElement && mode.value === "regex" ? "regex" : "normal", + }); + }); + + config.rules = nextRules.length ? nextRules : cloneRules(DEFAULT_RULES); + }; + + const handleFetchMember = async () => { + if (location.hostname !== "parks2.bandainamco-am.co.jp") { + updateStatusText("⚠️ 请先在 Bandai Parks 网站(parks2.bandainamco-am.co.jp)上使用此功能。"); + return; + } + updateStatusText("⏳ 正在获取会员信息…"); + try { + const resp = await fetch("https://parks2.bandainamco-am.co.jp/member_mypage.html", { + credentials: "include", + }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const html = await resp.text(); + const fields = extractMemberInfo(html); + if (!fields.length) { + updateStatusText("⚠️ 未获取到用户信息(可能未登录或页面结构变化)。"); + return; + } + readRules(); + // 清掉空行,避免和抓取到的信息混在一起 + config.rules = config.rules.filter((r) => r.original.trim() !== ""); + // 每个字段值作为一条规则的「原文字」,新文字留空由用户填写 + fields.forEach((f) => { + config.rules.push({ enabled: true, original: f.value, replacement: "", mode: "normal" }); + }); + if (!config.rules.length) config.rules = cloneRules(DEFAULT_RULES); + renderRules(); + updateStatusText(`✅ 已获取 ${fields.length} 项用户信息,请在「新文字」中填写要显示的内容。`); + } catch (e) { + updateStatusText("⚠️ 获取失败: " + (e && e.message ? e.message : "未知错误")); + } + }; + + panel.addEventListener("click", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + + const action = target.getAttribute("data-action"); + if (!action) return; + + if (action === "toggle-advanced") { + const isAdvanced = panel.classList.toggle("is-advanced"); + target.textContent = isAdvanced ? "↩ 返回简单替换" : "⚙ 高级替换(正则)"; + updateStatusText(isAdvanced ? "已开启高级替换,支持正则匹配。" : "已关闭高级替换,仅普通匹配。"); + return; + } + + if (action === "toggle-body") { + togglePanelBody(); + return; + } + + if (action === "hide-panel") { + hidePanel(); + return; + } + + if (action === "fetch-member") { + handleFetchMember(); + return; + } + + if (action === "add-rule") { + readRules(); + config.rules.push({ enabled: true, original: "", replacement: "", mode: "normal" }); + renderRules(); + updateStatusText("已添加一条规则。"); + return; + } + + if (action === "remove-rule") { + const row = target.closest(".codex-rule-row"); + if (!row) return; + const index = Number(row.getAttribute("data-index")); + readRules(); + config.rules.splice(index, 1); + if (!config.rules.length) { + config.rules = cloneRules(DEFAULT_RULES); + } + renderRules(); + updateStatusText("规则已删除。"); + return; + } + + if (action === "save") { + readFields(); + readRules(); + config.enabled = true; + startSessionNow(); + saveConfig(); + applyReplacements( + `已应用 ${config.rules.filter((rule) => rule.enabled && rule.original && rule.replacement).length} 条规则,4 小时后自动失效。` + ); + return; + } + + if (action === "disable") { + disableReplacement(); + return; + } + + if (action === "restart-hour") { + readFields(); + readRules(); + config.enabled = true; + startSessionNow(); + saveConfig(); + applyReplacements("已重新开始计时,4 小时后自动失效。"); + } + }); + + syncFields(); + renderRules(); + document.body.appendChild(panel); + syncPanelVisibility(); + updateStatusText("长按顶部 2 秒,或快速三击顶部区域可再次打开设置面板。"); + }; + + const boot = () => { + if (config.enabled && hasExpired()) { + config.enabled = false; + config.sessionStartedAt = null; + saveConfig(); + } + + config.panelVisible = false; + + startObserver(); + startTopHoldDetector(); + syncStatusLoop(); + + const mountUi = () => { + if (!document.body) { + requestAnimationFrame(mountUi); + return; + } + buildPanel(); + applyReplacements(); + startFastScanLoop(); + }; + + mountUi(); + + document.addEventListener("readystatechange", () => { + applyReplacements(); + }); + + window.addEventListener("load", () => { + applyReplacements(); + startFastScanLoop(); + }); + }; + + boot(); +})(); + diff --git a/code-v1.6.1.user.js b/code-v1.6.1.user.js new file mode 100644 index 0000000..3c9057b --- /dev/null +++ b/code-v1.6.1.user.js @@ -0,0 +1,1166 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息 *(通用版本) +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.0 +// @description 改会员资料姓名/生日/性别;可隐藏按钮与券面强制显示;支持 Excel 复制快速填充 +// @grant unsafeWindow +// @author park-tools +// @match https://parks2.bandainamco-am.co.jp/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_deleteValue +// ==/UserScript== + +(function () { + 'use strict'; + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + const LS_HIDE_UI = 'namco_hide_plugin_ui_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function getHideUi() { + const saved = store.get(LS_HIDE_UI, null); + if (saved != null) return saved; + return { hidden: false }; + } + + function setHideUi(cfg) { + store.set(LS_HIDE_UI, cfg); + } + + function shouldHidePluginUi() { + const cfg = getHideUi(); + return !!(cfg && cfg.hidden); + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})[-/\.](\d{1,2})[-/\.](\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = changes.gender || profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + SEX: sex, + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + SEX: sex, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input, .np-row select, .np-row textarea{width:100%;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;background:#fff;} +.np-row input, .np-row select{height:42px;} +.np-row textarea{padding:8px 12px;font-size:14px;resize:vertical;} +.np-row input:focus, .np-row select:focus, .np-row textarea:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名、生日与性别,无 SMS(手机号不变)。

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日/性别)。官网编辑页生日/性别虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    + 隐藏插件按钮 + +
    +

    隐藏后连点屏幕右下角两次可再打开设置

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + if (shouldHidePluginUi()) { + overlayBadge.style.display = 'none'; + return; + } + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = shouldHidePluginUi() ? 'none' : ''; + refreshOverlayBadge(); + } + + function loadHideUiToUI() { + $('#npHideUi', root).checked = shouldHidePluginUi(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + loadHideUiToUI(); + refreshPluginUiVisibility(); + } + + function clearAllInputs() { + $('#npPaste', root).value = ''; + $('#npL', root).value = ''; + $('#npF', root).value = ''; + $('#npLk', root).value = ''; + $('#npFk', root).value = ''; + $('#npBirthday', root).value = ''; + $('#npGender', root).value = 'M'; + $('#npPwd', root).value = ''; + $('#npOverlayName', root).value = ''; + } + + function openPanel() { + mask.style.display = 'block'; + panel.classList.add('open'); + + clearAllInputs(); + + loadOverlayToUI(); + loadHideUiToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + $('#npHideUi', root).addEventListener('change', () => { + setHideUi({ hidden: $('#npHideUi', root).checked }); + refreshPluginUiVisibility(); + }); + + (function setupSecretOpen() { + let lastTap = 0; + function hitCorner(x, y) { + const margin = 72; + return x >= window.innerWidth - margin && y >= window.innerHeight - margin; + } + function onCornerTap(clientX, clientY) { + if (!shouldHidePluginUi()) return; + if (panel.classList.contains('open')) return; + if (!hitCorner(clientX, clientY)) return; + const now = Date.now(); + if (now - lastTap < 450) { + lastTap = 0; + openPanel(); + } else { + lastTap = now; + } + } + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + if (t) onCornerTap(t.clientX, t.clientY); + }, + { passive: true } + ); + document.addEventListener('click', (e) => { + if (e.target.closest('#npRenameRoot')) return; + onCornerTap(e.clientX, e.clientY); + }); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在下方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // 快速填充逻辑:解析从 Excel 复制的整行内容(已补全平假名/片假名支持) + $('#npQuickFill', root).addEventListener('click', () => { + const rawText = $('#npPaste', root).value.trim(); + if (!rawText) { + log('请先粘贴 Excel 行数据到快速录入框'); + return; + } + + // 1. 优先按 Tab 制表符(Excel 复制的默认分隔符)或 2 个以上空格拆分 + const cols = rawText.split(/\t+|\s{2,}/).map(c => c.trim()).filter(Boolean); + const tokens = cols.length > 1 ? cols : rawText.split(/\s+/).map(c => c.trim()).filter(Boolean); + + let nameStr = ''; + let kanaStr = ''; + let genderStr = ''; + let bdayStr = ''; + let pwdStr = ''; + + // 匹配平假名与片假名的正则表达式(包含长音符号 ー) + const kanaRegex = /^[\u3040-\u309F\u30A0-\u30FF\u30FC\s]+$/; + + // 2. 智能提取字段 + tokens.forEach(token => { + // 匹配生日: YYYY-MM-DD / YYYY/MM/DD / 8位数字 + if (!bdayStr && (/^\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2}$/.test(token) || /^\d{8}$/.test(token))) { + bdayStr = normalizeBirthday(token); + } + // 匹配性别: 男 / 女 / M / F / Male / Female + else if (!genderStr && /^(男|女|M|F|Male|Female)$/i.test(token)) { + genderStr = token; + } + // 匹配密码: 包含字母和数字组合且长度 >= 6 + else if (!pwdStr && /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/.test(token)) { + pwdStr = token; + } + // 匹配平假名/片假名 + else if (!kanaStr && kanaRegex.test(token)) { + kanaStr = token; + } + // 剩余非纯数字文本作为汉字/英文姓名候选 + else if (!nameStr && !/^\d+$/.test(token)) { + nameStr = token; + } + }); + + if (!nameStr && tokens.length > 0) nameStr = tokens[0]; + + // 3. 拆分汉字/英文 姓与名 + const { l, f } = splitFullName(nameStr); + $('#npL', root).value = l; + $('#npF', root).value = f; + + // 4. 拆分假名 姓与名 并填充 + if (kanaStr) { + const { l: lk, f: fk } = splitFullName(kanaStr); + $('#npLk', root).value = lk; + $('#npFk', root).value = fk; + } + + // 5. 填充性别 + if (genderStr) { + if (/^(女|F|Female)$/i.test(genderStr)) { + $('#npGender', root).value = 'F'; + } else if (/^(男|M|Male)$/i.test(genderStr)) { + $('#npGender', root).value = 'M'; + } + } + + // 6. 填充生日 + if (bdayStr) { + $('#npBirthday', root).value = bdayStr; + } + + // 7. 填充密码 + if (pwdStr) { + $('#npPwd', root).value = pwdStr; + } + + // 8. 同步填充券面显示姓名 + const fullName = `${l} ${f}`.trim(); + if (fullName) { + $('#npOverlayName', root).value = fullName; + } + + const lkVal = $('#npLk', root).value; + const fkVal = $('#npFk', root).value; + log(`✅ 快速填充完成:\n• 姓名:${l} ${f}\n• 假名:${lkVal || fkVal ? `${lkVal} ${fkVal}` : '未匹配'}\n• 性别:${$('#npGender', root).value === 'F' ? '女 (F)' : '男 (M)'}\n• 生日:${bdayStr || '未匹配'}\n• 密码:${pwdStr ? '已自动填充' : '未匹配'}`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + const sexLabel = p.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n性别:${sexLabel}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + $('#npGender', root).value = p.gender || 'M'; + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + const gender = $('#npGender', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + gender, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + const sexLabel = after.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `性别:${sexLabel}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + const sexLabel = prof.gender === 'F' ? '女 (F)' : '男 (M)'; + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n性别:${sexLabel}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); \ No newline at end of file diff --git a/code-v1.6.2jr.user.js b/code-v1.6.2jr.user.js new file mode 100644 index 0000000..dd6e8d8 --- /dev/null +++ b/code-v1.6.2jr.user.js @@ -0,0 +1,1181 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息 *(通用版本) +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.0 +// @description 改会员资料姓名/生日/性别;可隐藏按钮与券面强制显示;支持 Excel 复制快速填充 +// @grant unsafeWindow +// @author park-tools +// @match https://parks2.bandainamco-am.co.jp/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_deleteValue +// ==/UserScript== + +(function () { + 'use strict'; + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + const LS_HIDE_UI = 'namco_hide_plugin_ui_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?:^|\D)(070\d{8}|080\d{8}|090\d{8})(?!\d)/); + if (tm) tel = tm[1]; + } + const y = parseInput(html, 'BIRTH_YEAR'); + const mo = parseInput(html, 'BIRTH_MONTH'); + const d = parseInput(html, 'BIRTH_DAY'); + let birthday = (md.birth || '').replace(/\//g, '-'); + if (y && mo && d) { + birthday = `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`; + } + const sex = parseCheckedRadio(html, 'SEX') || (md.sex || 'M').toString().charAt(0).toUpperCase(); + return { + email: parseInput(html, 'PC_MAIL'), + last_name: parseInput(html, 'L_NAME'), + first_name: parseInput(html, 'F_NAME'), + last_name_kana: parseInput(html, 'L_KANA') || parseInput(html, 'L_NAME_KANA'), + first_name_kana: parseInput(html, 'F_KANA') || parseInput(html, 'F_NAME_KANA'), + nickname: parseInput(html, 'NICKNAME'), + addr1: parseSelected(html, 'ADDR1') || parseInput(html, 'ADDR1') || '東京都', + zip: (parseInput(html, 'ZIP') || '').replace(/-/g, ''), + addr2: parseInput(html, 'ADDR2'), + addr_street: parseInput(html, 'MEMBER.FREE_ITEM16'), + addr3: parseInput(html, 'ADDR3'), + tel, + gender: sex === 'F' || sex === '2' ? 'F' : (sex === 'M' || sex === '1' ? 'M' : 'M'), + birthday: birthday || '1990-01-01', + formFields: parseHiddenFields(parseFormChunk(html, 'memberFrm').chunk || html), + }; + } + + function splitFullName(full) { + const s = String(full || '').trim().replace(/[\t\r\n]+/g, ' ').replace(/\s+/g, ' '); + if (!s) return { l: '', f: '' }; + if (/^[A-Za-z]/.test(s)) { + const parts = s.split(' '); + if (parts.length >= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function getHideUi() { + const saved = store.get(LS_HIDE_UI, null); + if (saved != null) return saved; + return { hidden: false }; + } + + function setHideUi(cfg) { + store.set(LS_HIDE_UI, cfg); + } + + function shouldHidePluginUi() { + const cfg = getHideUi(); + return !!(cfg && cfg.hidden); + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})[-/\.](\d{1,2})[-/\.](\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = changes.gender || profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + SEX: sex, + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + SEX: sex, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + // const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + // ✅ 修改为(兼容 iOS 15): + const orders = []; + const orderRe = /admission_use_ticket\.html\?order_no=(\d+)/g; + let om; + while ((om = orderRe.exec(r.text)) !== null) { + orders.push(om[1]); + } + + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + // const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + // ✅ 修改为(兼容 iOS 15): + const clients = []; + const clientRe = /ご依頼主<\/dt>\s*]*>\s*([^<]+)/g; + let cm; + while ((cm = clientRe.exec(hist.text)) !== null) { + clients.push(cm[1].trim()); + } + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input, .np-row select, .np-row textarea{width:100%;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;background:#fff;} +.np-row input, .np-row select{height:42px;} +.np-row textarea{padding:8px 12px;font-size:14px;resize:vertical;} +.np-row input:focus, .np-row select:focus, .np-row textarea:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名、生日与性别,无 SMS(手机号不变)。

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日/性别)。官网编辑页生日/性别虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    + 隐藏插件按钮 + +
    +

    隐藏后连点屏幕右下角两次可再打开设置

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + if (shouldHidePluginUi()) { + overlayBadge.style.display = 'none'; + return; + } + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = shouldHidePluginUi() ? 'none' : ''; + refreshOverlayBadge(); + } + + function loadHideUiToUI() { + $('#npHideUi', root).checked = shouldHidePluginUi(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + loadHideUiToUI(); + refreshPluginUiVisibility(); + } + + function clearAllInputs() { + $('#npPaste', root).value = ''; + $('#npL', root).value = ''; + $('#npF', root).value = ''; + $('#npLk', root).value = ''; + $('#npFk', root).value = ''; + $('#npBirthday', root).value = ''; + $('#npGender', root).value = 'M'; + $('#npPwd', root).value = ''; + $('#npOverlayName', root).value = ''; + } + + function openPanel() { + mask.style.display = 'block'; + panel.classList.add('open'); + + clearAllInputs(); + + loadOverlayToUI(); + loadHideUiToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + $('#npHideUi', root).addEventListener('change', () => { + setHideUi({ hidden: $('#npHideUi', root).checked }); + refreshPluginUiVisibility(); + }); + + (function setupSecretOpen() { + let lastTap = 0; + function hitCorner(x, y) { + const margin = 72; + return x >= window.innerWidth - margin && y >= window.innerHeight - margin; + } + function onCornerTap(clientX, clientY) { + if (!shouldHidePluginUi()) return; + if (panel.classList.contains('open')) return; + if (!hitCorner(clientX, clientY)) return; + const now = Date.now(); + if (now - lastTap < 450) { + lastTap = 0; + openPanel(); + } else { + lastTap = now; + } + } + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + if (t) onCornerTap(t.clientX, t.clientY); + }, + { passive: true } + ); + document.addEventListener('click', (e) => { + if (e.target.closest('#npRenameRoot')) return; + onCornerTap(e.clientX, e.clientY); + }); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在下方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // 快速填充逻辑:解析从 Excel 复制的整行内容(已补全平假名/片假名支持) + $('#npQuickFill', root).addEventListener('click', () => { + const rawText = $('#npPaste', root).value.trim(); + if (!rawText) { + log('请先粘贴 Excel 行数据到快速录入框'); + return; + } + + // 1. 优先按 Tab 制表符(Excel 复制的默认分隔符)或 2 个以上空格拆分 + const cols = rawText.split(/\t+|\s{2,}/).map(c => c.trim()).filter(Boolean); + const tokens = cols.length > 1 ? cols : rawText.split(/\s+/).map(c => c.trim()).filter(Boolean); + + let nameStr = ''; + let kanaStr = ''; + let genderStr = ''; + let bdayStr = ''; + let pwdStr = ''; + + // 匹配平假名与片假名的正则表达式(包含长音符号 ー) + const kanaRegex = /^[\u3040-\u309F\u30A0-\u30FF\u30FC\s]+$/; + + // 2. 智能提取字段 + tokens.forEach(token => { + // 匹配生日: YYYY-MM-DD / YYYY/MM/DD / 8位数字 + if (!bdayStr && (/^\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2}$/.test(token) || /^\d{8}$/.test(token))) { + bdayStr = normalizeBirthday(token); + } + // 匹配性别: 男 / 女 / M / F / Male / Female + else if (!genderStr && /^(男|女|M|F|Male|Female)$/i.test(token)) { + genderStr = token; + } + // 匹配密码: 包含字母和数字组合且长度 >= 6 + else if (!pwdStr && /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/.test(token)) { + pwdStr = token; + } + // 匹配平假名/片假名 + else if (!kanaStr && kanaRegex.test(token)) { + kanaStr = token; + } + // 剩余非纯数字文本作为汉字/英文姓名候选 + else if (!nameStr && !/^\d+$/.test(token)) { + nameStr = token; + } + }); + + if (!nameStr && tokens.length > 0) nameStr = tokens[0]; + + // 3. 拆分汉字/英文 姓与名 + const { l, f } = splitFullName(nameStr); + $('#npL', root).value = l; + $('#npF', root).value = f; + + // 4. 拆分假名 姓与名 并填充 + if (kanaStr) { + const { l: lk, f: fk } = splitFullName(kanaStr); + $('#npLk', root).value = lk; + $('#npFk', root).value = fk; + } + + // 5. 填充性别 + if (genderStr) { + if (/^(女|F|Female)$/i.test(genderStr)) { + $('#npGender', root).value = 'F'; + } else if (/^(男|M|Male)$/i.test(genderStr)) { + $('#npGender', root).value = 'M'; + } + } + + // 6. 填充生日 + if (bdayStr) { + $('#npBirthday', root).value = bdayStr; + } + + // 7. 填充密码 + if (pwdStr) { + $('#npPwd', root).value = pwdStr; + } + + // 8. 同步填充券面显示姓名 + const fullName = `${l} ${f}`.trim(); + if (fullName) { + $('#npOverlayName', root).value = fullName; + } + + const lkVal = $('#npLk', root).value; + const fkVal = $('#npFk', root).value; + log(`✅ 快速填充完成:\n• 姓名:${l} ${f}\n• 假名:${lkVal || fkVal ? `${lkVal} ${fkVal}` : '未匹配'}\n• 性别:${$('#npGender', root).value === 'F' ? '女 (F)' : '男 (M)'}\n• 生日:${bdayStr || '未匹配'}\n• 密码:${pwdStr ? '已自动填充' : '未匹配'}`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + const sexLabel = p.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n性别:${sexLabel}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + $('#npGender', root).value = p.gender || 'M'; + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + const gender = $('#npGender', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + gender, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + const sexLabel = after.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `性别:${sexLabel}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + const sexLabel = prof.gender === 'F' ? '女 (F)' : '男 (M)'; + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n性别:${sexLabel}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); \ No newline at end of file diff --git a/fixed-site-replacer-main/bookmarklet.txt b/fixed-site-replacer-main/bookmarklet.txt new file mode 100644 index 0000000..6d9a851 --- /dev/null +++ b/fixed-site-replacer-main/bookmarklet.txt @@ -0,0 +1 @@ +javascript:(()%20%3D%3E%20%7B%0A%20%20%22use%20strict%22%3B%0A%20%20%2F%2F%20%3D%3D%3D%3D%3D%20bookmarklet%20%E6%A8%A1%E5%BC%8F%EF%BC%88%E7%94%B1%20build-bookmarklet.js%20%E6%B3%A8%E5%85%A5%EF%BC%89%20%3D%3D%3D%3D%3D%0A%20%20const%20BOOKMARKLET_HOSTS%20%3D%20%5B%0A%20%20%20%20%22login.microsoftonline.com%22%2C%0A%20%20%20%20%22outlook.live.com%22%2C%0A%20%20%20%20%22parks2.bandainamco-am.co.jp%22%2C%0A%20%20%5D%3B%0A%0A%20%20const%20showToast%20%3D%20(msg)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!document.body)%20return%3B%0A%20%20%20%20let%20el%20%3D%20document.getElementById(%22codex-msmail-toast%22)%3B%0A%20%20%20%20if%20(!el)%20%7B%0A%20%20%20%20%20%20el%20%3D%20document.createElement(%22div%22)%3B%0A%20%20%20%20%20%20el.id%20%3D%20%22codex-msmail-toast%22%3B%0A%20%20%20%20%20%20el.style.cssText%20%3D%0A%20%20%20%20%20%20%20%20%22position%3Afixed%3Btop%3Amax(12px%2C%20env(safe-area-inset-top))%3Bleft%3A50%25%3B%22%20%2B%0A%20%20%20%20%20%20%20%20%22transform%3AtranslateX(-50%25)%3Bz-index%3A2147483646%3Bbackground%3Argba(32%2C25%2C21%2C.92)%3B%22%20%2B%0A%20%20%20%20%20%20%20%20%22color%3A%23fff%3Bpadding%3A10px%2016px%3Bborder-radius%3A999px%3B%22%20%2B%0A%20%20%20%20%20%20%20%20%22font%3A13px%20-apple-system%2CBlinkMacSystemFont%2C'PingFang%20SC'%2Csans-serif%3B%22%20%2B%0A%20%20%20%20%20%20%20%20%22max-width%3A90vw%3Btext-align%3Acenter%3Bpointer-events%3Anone%3Bopacity%3A0%3B%22%20%2B%0A%20%20%20%20%20%20%20%20%22transition%3Aopacity%20.3s%3B%22%3B%0A%20%20%20%20%20%20document.body.appendChild(el)%3B%0A%20%20%20%20%7D%0A%20%20%20%20el.textContent%20%3D%20msg%3B%0A%20%20%20%20el.style.opacity%20%3D%20%221%22%3B%0A%20%20%20%20clearTimeout(window.__codexToastTimer)%3B%0A%20%20%20%20window.__codexToastTimer%20%3D%20setTimeout(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20el.style.opacity%20%3D%20%220%22%3B%0A%20%20%20%20%7D%2C%202500)%3B%0A%20%20%7D%3B%0A%0A%20%20if%20(!BOOKMARKLET_HOSTS.includes(location.hostname))%20%7B%0A%20%20%20%20showToast(%22%E2%9A%A0%EF%B8%8F%20%E8%AF%B7%E5%85%88%E5%9C%A8%E6%94%AF%E6%8C%81%E7%9A%84%E7%BD%91%E7%AB%99%E4%BD%BF%E7%94%A8%EF%BC%9AMicrosoft%20%E7%99%BB%E5%BD%95%20%2F%20Outlook%20%2F%20Bandai%20Parks%22)%3B%0A%20%20%20%20return%3B%0A%20%20%7D%0A%0A%20%20%2F%2F%20%E5%B7%B2%E5%8A%A0%E8%BD%BD%E8%BF%87%EF%BC%88%E5%86%8D%E6%AC%A1%E7%82%B9%E5%87%BB%E4%B9%A6%E7%AD%BE%EF%BC%89%EF%BC%9A%E5%8F%AA%E9%87%8D%E6%96%B0%E5%BA%94%E7%94%A8%EF%BC%8C%E4%B8%8D%E9%87%8D%E5%A4%8D%E5%88%9D%E5%A7%8B%E5%8C%96%0A%20%20if%20(window.__codexNameReplacerLoaded)%20%7B%0A%20%20%20%20if%20(typeof%20window.__codexNameReplacerReapply%20%3D%3D%3D%20%22function%22)%20%7B%0A%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20window.__codexNameReplacerReapply()%3B%0A%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%7D%0A%20%20%20%20%7D%0A%20%20%20%20return%3B%0A%20%20%7D%0A%20%20window.__codexNameReplacerLoaded%20%3D%20true%3B%0A%20%20window.__codexNameReplacerReapply%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(isReplacementActive())%20%7B%0A%20%20%20%20%20%20applyReplacements()%3B%0A%20%20%20%20%20%20showToast(%22%E2%9C%85%20%E5%B7%B2%E5%BA%94%E7%94%A8%E6%9B%BF%E6%8D%A2%22)%3B%0A%20%20%20%20%7D%20else%20%7B%0A%20%20%20%20%20%20showPanel()%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%0A%20%20const%20STORAGE_KEY%20%3D%20%22codex.fixedSite.nameReplacer.config.v1%22%3B%0A%20%20const%20SUPPORTED_HOSTS%20%3D%20%5B%0A%20%20%20%20%22login.microsoftonline.com%22%2C%0A%20%20%20%20%22outlook.live.com%22%2C%0A%20%20%20%20%22parks2.bandainamco-am.co.jp%22%2C%0A%20%20%5D%3B%0A%20%20const%20PANEL_ID%20%3D%20%22codex-msmail-name-panel%22%3B%0A%20%20const%20STYLE_ID%20%3D%20%22codex-msmail-name-style%22%3B%0A%20%20const%20SESSION_DURATION_MS%20%3D%204%20*%2060%20*%2060%20*%201000%3B%0A%20%20const%20PANEL_HOLD_MS%20%3D%202000%3B%0A%20%20const%20PANEL_HOLD_ZONE_PX%20%3D%20100%3B%0A%20%20const%20TRIPLE_CLICK_WINDOW_MS%20%3D%20500%3B%0A%20%20const%20TRIPLE_CLICK_MAX_SPREAD%20%3D%2040%3B%0A%20%20const%20FAST_SCAN_WINDOW_MS%20%3D%204000%3B%0A%20%20const%20FAST_SCAN_INTERVAL_MS%20%3D%20120%3B%0A%20%20const%20DEFAULT_RULES%20%3D%20%5B%0A%20%20%20%20%7B%20enabled%3A%20true%2C%20original%3A%20%22%22%2C%20replacement%3A%20%22%22%2C%20mode%3A%20%22normal%22%20%7D%2C%0A%20%20%20%20%7B%20enabled%3A%20true%2C%20original%3A%20%22%22%2C%20replacement%3A%20%22%22%2C%20mode%3A%20%22normal%22%20%7D%2C%0A%20%20%5D%3B%0A%0A%20%20let%20originalTextMap%20%3D%20new%20WeakMap()%3B%0A%20%20let%20originalValueMap%20%3D%20new%20WeakMap()%3B%0A%20%20const%20touchedTextNodes%20%3D%20new%20Set()%3B%0A%20%20const%20touchedElements%20%3D%20new%20Set()%3B%0A%20%20let%20observer%20%3D%20null%3B%0A%20%20let%20applying%20%3D%20false%3B%0A%20%20let%20statusUpdater%20%3D%20null%3B%0A%20%20let%20holdTimer%20%3D%20null%3B%0A%20%20let%20topHoldStartY%20%3D%200%3B%0A%20%20let%20threeFingerHold%20%3D%20false%3B%0A%20%20let%20tripleClickTimes%20%3D%20%5B%5D%3B%0A%20%20let%20fastScanInterval%20%3D%20null%3B%0A%20%20let%20fastScanStopTimer%20%3D%20null%3B%0A%0A%20%20const%20defaultConfig%20%3D%20%7B%0A%20%20%20%20enabled%3A%20false%2C%0A%20%20%20%20fontAdjust%3A%20false%2C%0A%20%20%20%20replaceValues%3A%20false%2C%0A%20%20%20%20panelVisible%3A%20false%2C%0A%20%20%20%20bodyCollapsed%3A%20false%2C%0A%20%20%20%20sessionStartedAt%3A%20null%2C%0A%20%20%20%20rules%3A%20DEFAULT_RULES%2C%0A%20%20%7D%3B%0A%0A%20%20const%20cloneRules%20%3D%20(rules)%20%3D%3E%0A%20%20%20%20(Array.isArray(rules)%20%3F%20rules%20%3A%20DEFAULT_RULES).map((rule)%20%3D%3E%20(%7B%0A%20%20%20%20%20%20enabled%3A%20rule.enabled%20!%3D%3D%20false%2C%0A%20%20%20%20%20%20original%3A%20String(rule.original%20%7C%7C%20%22%22)%2C%0A%20%20%20%20%20%20replacement%3A%20String(rule.replacement%20%7C%7C%20%22%22)%2C%0A%20%20%20%20%20%20mode%3A%20rule.mode%20%3D%3D%3D%20%22regex%22%20%3F%20%22regex%22%20%3A%20%22normal%22%2C%0A%20%20%20%20%7D))%3B%0A%0A%20%20const%20readStoredConfig%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(typeof%20GM_getValue%20%3D%3D%3D%20%22function%22)%20%7B%0A%20%20%20%20%20%20return%20GM_getValue(STORAGE_KEY%2C%20%22%7B%7D%22)%3B%0A%20%20%20%20%7D%0A%20%20%20%20return%20localStorage.getItem(STORAGE_KEY)%20%7C%7C%20%22%7B%7D%22%3B%0A%20%20%7D%3B%0A%0A%20%20const%20writeStoredConfig%20%3D%20(value)%20%3D%3E%20%7B%0A%20%20%20%20if%20(typeof%20GM_setValue%20%3D%3D%3D%20%22function%22)%20%7B%0A%20%20%20%20%20%20GM_setValue(STORAGE_KEY%2C%20value)%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%20%20%20%20localStorage.setItem(STORAGE_KEY%2C%20value)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20loadConfig%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20const%20saved%20%3D%20JSON.parse(readStoredConfig())%3B%0A%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20...defaultConfig%2C%0A%20%20%20%20%20%20%20%20...saved%2C%0A%20%20%20%20%20%20%20%20panelVisible%3A%20false%2C%0A%20%20%20%20%20%20%20%20sessionStartedAt%3A%0A%20%20%20%20%20%20%20%20%20%20typeof%20saved.sessionStartedAt%20%3D%3D%3D%20%22number%22%20%26%26%20saved.sessionStartedAt%20%3E%200%0A%20%20%20%20%20%20%20%20%20%20%20%20%3F%20saved.sessionStartedAt%0A%20%20%20%20%20%20%20%20%20%20%20%20%3A%20null%2C%0A%20%20%20%20%20%20%20%20rules%3A%20cloneRules(saved.rules)%2C%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20return%20%7B%0A%20%20%20%20%20%20%20%20...defaultConfig%2C%0A%20%20%20%20%20%20%20%20rules%3A%20cloneRules(defaultConfig.rules)%2C%0A%20%20%20%20%20%20%7D%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20let%20config%20%3D%20loadConfig()%3B%0A%20%20config.panelVisible%20%3D%20false%3B%0A%0A%20%20const%20saveConfig%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20writeStoredConfig(%0A%20%20%20%20%20%20JSON.stringify(%7B%0A%20%20%20%20%20%20%20%20...config%2C%0A%20%20%20%20%20%20%20%20rules%3A%20cloneRules(config.rules)%2C%0A%20%20%20%20%20%20%7D)%0A%20%20%20%20)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20normalize%20%3D%20(value)%20%3D%3E%20value.replace(%2F%5Cs%2B%2Fg%2C%20%22%20%22).trim()%3B%0A%0A%20%20const%20siteAuthorized%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20return%20SUPPORTED_HOSTS.includes(location.hostname)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20isOutlookPage%20%3D%20()%20%3D%3E%20location.hostname%20%3D%3D%3D%20%22outlook.live.com%22%3B%0A%0A%20%20const%20shouldSkipNode%20%3D%20(node)%20%3D%3E%20%7B%0A%20%20%20%20const%20element%20%3D%0A%20%20%20%20%20%20node%20instanceof%20Element%20%3F%20node%20%3A%20node%3F.parentElement%20instanceof%20Element%20%3F%20node.parentElement%20%3A%20null%3B%0A%0A%20%20%20%20if%20(!element)%20return%20false%3B%0A%20%20%20%20if%20(element.closest(%60%23%24%7BPANEL_ID%7D%60))%20return%20true%3B%0A%20%20%20%20if%20(%5B%22SCRIPT%22%2C%20%22STYLE%22%2C%20%22NOSCRIPT%22%5D.includes(element.tagName))%20return%20true%3B%0A%20%20%20%20return%20Boolean(element.closest('iframe%2C%20%5Bcontenteditable%3D%22true%22%5D'))%3B%0A%20%20%7D%3B%0A%0A%20%20const%20getReplacementRoots%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(!document.body)%20return%20%5B%5D%3B%0A%20%20%20%20return%20%5Bdocument.body%5D%3B%0A%20%20%7D%3B%0A%0A%20%20const%20hasValidSession%20%3D%20()%20%3D%3E%20typeof%20config.sessionStartedAt%20%3D%3D%3D%20%22number%22%20%26%26%20config.sessionStartedAt%20%3E%200%3B%0A%0A%20%20const%20getSessionExpiresAt%20%3D%20()%20%3D%3E%0A%20%20%20%20hasValidSession()%20%3F%20config.sessionStartedAt%20%2B%20SESSION_DURATION_MS%20%3A%200%3B%0A%0A%20%20const%20getRemainingMs%20%3D%20()%20%3D%3E%0A%20%20%20%20hasValidSession()%20%3F%20Math.max(0%2C%20getSessionExpiresAt()%20-%20Date.now())%20%3A%200%3B%0A%0A%20%20const%20formatRemaining%20%3D%20(ms)%20%3D%3E%20%7B%0A%20%20%20%20const%20totalSeconds%20%3D%20Math.ceil(ms%20%2F%201000)%3B%0A%20%20%20%20const%20minutes%20%3D%20Math.floor(totalSeconds%20%2F%2060)%3B%0A%20%20%20%20const%20seconds%20%3D%20totalSeconds%20%25%2060%3B%0A%20%20%20%20return%20%60%24%7BString(minutes).padStart(2%2C%20%220%22)%7D%3A%24%7BString(seconds).padStart(2%2C%20%220%22)%7D%60%3B%0A%20%20%7D%3B%0A%0A%20%20const%20hasExpired%20%3D%20()%20%3D%3E%20hasValidSession()%20%26%26%20getRemainingMs()%20%3C%3D%200%3B%0A%0A%20%20const%20isReplacementActive%20%3D%20()%20%3D%3E%0A%20%20%20%20config.enabled%20%26%26%20siteAuthorized()%20%26%26%20hasValidSession()%20%26%26%20!hasExpired()%3B%0A%0A%20%20const%20replaceByRule%20%3D%20(input%2C%20rule)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!rule.enabled%20%7C%7C%20!rule.original%20%7C%7C%20!rule.replacement)%20%7B%0A%20%20%20%20%20%20return%20input%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(rule.mode%20%3D%3D%3D%20%22regex%22)%20%7B%0A%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20return%20input.replace(new%20RegExp(rule.original%2C%20%22g%22)%2C%20rule.replacement)%3B%0A%20%20%20%20%20%20%7D%20catch%20%7B%0A%20%20%20%20%20%20%20%20return%20input%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20return%20input.split(rule.original).join(rule.replacement)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20replaceText%20%3D%20(text)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!isReplacementActive())%20return%20text%3B%0A%20%20%20%20return%20config.rules.reduce((next%2C%20rule)%20%3D%3E%20replaceByRule(next%2C%20rule)%2C%20text)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20rememberOriginalText%20%3D%20(node%2C%20value)%20%3D%3E%20%7B%0A%20%20%20%20touchedTextNodes.add(node)%3B%0A%20%20%20%20if%20(!originalTextMap.has(node))%20%7B%0A%20%20%20%20%20%20originalTextMap.set(node%2C%20value)%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20rememberOriginalValue%20%3D%20(element%2C%20key%2C%20value)%20%3D%3E%20%7B%0A%20%20%20%20touchedElements.add(element)%3B%0A%20%20%20%20let%20item%20%3D%20originalValueMap.get(element)%3B%0A%20%20%20%20if%20(!item)%20%7B%0A%20%20%20%20%20%20item%20%3D%20%7B%7D%3B%0A%20%20%20%20%20%20originalValueMap.set(element%2C%20item)%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(!(key%20in%20item))%20%7B%0A%20%20%20%20%20%20item%5Bkey%5D%20%3D%20value%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20restoreTouchedContent%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20touchedTextNodes.forEach((node)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(node.isConnected)%20%7B%0A%20%20%20%20%20%20%20%20restoreNode(node)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20touchedElements.forEach((element)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(element.isConnected)%20%7B%0A%20%20%20%20%20%20%20%20restoreElement(element)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20touchedTextNodes.clear()%3B%0A%20%20%20%20touchedElements.clear()%3B%0A%20%20%20%20originalTextMap%20%3D%20new%20WeakMap()%3B%0A%20%20%20%20originalValueMap%20%3D%20new%20WeakMap()%3B%0A%20%20%7D%3B%0A%0A%20%20const%20restoreNode%20%3D%20(node)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!originalTextMap.has(node))%20return%3B%0A%0A%20%20%20%20const%20original%20%3D%20originalTextMap.get(node)%3B%0A%20%20%20%20if%20(node.nodeValue%20!%3D%3D%20original)%20%7B%0A%20%20%20%20%20%20node.nodeValue%20%3D%20original%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20restoreElement%20%3D%20(element)%20%3D%3E%20%7B%0A%20%20%20%20const%20item%20%3D%20originalValueMap.get(element)%3B%0A%20%20%20%20if%20(!item)%20return%3B%0A%0A%20%20%20%20if%20(%22value%22%20in%20item%20%26%26%20typeof%20element.value%20%3D%3D%3D%20%22string%22%20%26%26%20element.value%20!%3D%3D%20item.value)%20%7B%0A%20%20%20%20%20%20element.value%20%3D%20item.value%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(%0A%20%20%20%20%20%20%22placeholder%22%20in%20item%20%26%26%0A%20%20%20%20%20%20typeof%20element.placeholder%20%3D%3D%3D%20%22string%22%20%26%26%0A%20%20%20%20%20%20element.placeholder%20!%3D%3D%20item.placeholder%0A%20%20%20%20)%20%7B%0A%20%20%20%20%20%20element.placeholder%20%3D%20item.placeholder%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20updateTextNode%20%3D%20(node)%20%3D%3E%20%7B%0A%20%20%20%20const%20current%20%3D%20node.nodeValue%3B%0A%20%20%20%20if%20(!current%20%7C%7C%20!normalize(current))%20return%3B%0A%0A%20%20%20%20if%20(!isReplacementActive())%20return%3B%0A%0A%20%20%20%20rememberOriginalText(node%2C%20current)%3B%0A%20%20%20%20const%20base%20%3D%20originalTextMap.get(node)%20%7C%7C%20current%3B%0A%20%20%20%20const%20next%20%3D%20replaceText(base)%3B%0A%20%20%20%20if%20(next%20!%3D%3D%20current)%20%7B%0A%20%20%20%20%20%20node.nodeValue%20%3D%20next%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20updateElementValue%20%3D%20(element)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!(element%20instanceof%20HTMLInputElement%20%7C%7C%20element%20instanceof%20HTMLTextAreaElement))%20%7B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(shouldSkipNode(element))%20return%3B%0A%0A%20%20%20%20if%20(!isReplacementActive())%20return%3B%0A%0A%20%20%20%20if%20(typeof%20element.placeholder%20%3D%3D%3D%20%22string%22)%20%7B%0A%20%20%20%20%20%20rememberOriginalValue(element%2C%20%22placeholder%22%2C%20element.placeholder)%3B%0A%20%20%20%20%20%20const%20nextPlaceholder%20%3D%20replaceText(%0A%20%20%20%20%20%20%20%20originalValueMap.get(element)%3F.placeholder%20%7C%7C%20element.placeholder%0A%20%20%20%20%20%20)%3B%0A%20%20%20%20%20%20if%20(nextPlaceholder%20!%3D%3D%20element.placeholder)%20%7B%0A%20%20%20%20%20%20%20%20element.placeholder%20%3D%20nextPlaceholder%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%20%E5%8F%AF%E9%80%89%EF%BC%9A%E5%90%8C%E6%97%B6%E6%9B%BF%E6%8D%A2%E8%BE%93%E5%85%A5%E6%A1%86%20value%EF%BC%88%E7%94%9F%E6%97%A5%2F%E9%82%AE%E7%AE%B1%E7%AD%89%E8%A1%A8%E5%8D%95%E5%80%BC%EF%BC%9Bpassword%20%E6%B0%B8%E4%B8%8D%E5%A4%84%E7%90%86%EF%BC%89%0A%20%20%20%20if%20(config.replaceValues%20%26%26%20typeof%20element.value%20%3D%3D%3D%20%22string%22%20%26%26%20element.type%20!%3D%3D%20%22password%22)%20%7B%0A%20%20%20%20%20%20rememberOriginalValue(element%2C%20%22value%22%2C%20element.value)%3B%0A%20%20%20%20%20%20const%20baseValue%20%3D%20originalValueMap.get(element)%3F.value%20%3F%3F%20element.value%3B%0A%20%20%20%20%20%20const%20nextValue%20%3D%20replaceText(baseValue)%3B%0A%20%20%20%20%20%20if%20(nextValue%20!%3D%3D%20element.value)%20%7B%0A%20%20%20%20%20%20%20%20element.value%20%3D%20nextValue%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20walkAndReplace%20%3D%20(root)%20%3D%3E%20%7B%0A%20%20%20%20if%20(!root)%20return%3B%0A%0A%20%20%20%20const%20walker%20%3D%20document.createTreeWalker(%0A%20%20%20%20%20%20root%2C%0A%20%20%20%20%20%20NodeFilter.SHOW_TEXT%2C%0A%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20acceptNode(node)%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(!node.parentElement)%20return%20NodeFilter.FILTER_REJECT%3B%0A%20%20%20%20%20%20%20%20%20%20if%20(shouldSkipNode(node))%20return%20NodeFilter.FILTER_REJECT%3B%0A%20%20%20%20%20%20%20%20%20%20return%20NodeFilter.FILTER_ACCEPT%3B%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%7D%0A%20%20%20%20)%3B%0A%0A%20%20%20%20let%20textNode%20%3D%20walker.nextNode()%3B%0A%20%20%20%20while%20(textNode)%20%7B%0A%20%20%20%20%20%20updateTextNode(textNode)%3B%0A%20%20%20%20%20%20textNode%20%3D%20walker.nextNode()%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(root%20instanceof%20Element)%20%7B%0A%20%20%20%20%20%20updateElementValue(root)%3B%0A%20%20%20%20%20%20root.querySelectorAll(%22input%2C%20textarea%22).forEach(updateElementValue)%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20applyFontAdjust%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20active%20%3D%20Boolean(config.fontAdjust%20%26%26%20isReplacementActive())%3B%0A%20%20%20%20document.documentElement.classList.toggle(%22codex-msmail-font-adjust%22%2C%20active)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20updateStatusText%20%3D%20(message)%20%3D%3E%20%7B%0A%20%20%20%20const%20statusNode%20%3D%20document.querySelector(%60%23%24%7BPANEL_ID%7D%20%5Bdata-role%3D%22status%22%5D%60)%3B%0A%20%20%20%20if%20(!statusNode)%20return%3B%0A%0A%20%20%20%20if%20(message)%20%7B%0A%20%20%20%20%20%20statusNode.textContent%20%3D%20message%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(!config.enabled)%20%7B%0A%20%20%20%20%20%20statusNode.textContent%20%3D%20%22%E6%9B%BF%E6%8D%A2%E5%8A%9F%E8%83%BD%E5%B7%B2%E5%85%B3%E9%97%AD%EF%BC%8C%E9%A1%B5%E9%9D%A2%E6%AD%A3%E5%B8%B8%E6%98%BE%E7%A4%BA%E3%80%82%22%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(!hasValidSession())%20%7B%0A%20%20%20%20%20%20statusNode.textContent%20%3D%20%22%E5%B0%9A%E6%9C%AA%E5%BC%80%E5%A7%8B%E8%AE%A1%E6%97%B6%EF%BC%8C%E7%82%B9%E5%87%BB%E4%BF%9D%E5%AD%98%E5%B9%B6%E5%BA%94%E7%94%A8%E5%90%8E%E5%BC%80%E5%A7%8B%204%20%E5%B0%8F%E6%97%B6%E5%80%92%E8%AE%A1%E6%97%B6%E3%80%82%22%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20if%20(hasExpired())%20%7B%0A%20%20%20%20%20%20statusNode.textContent%20%3D%20%22%E5%B7%B2%E8%B6%85%E8%BF%87%204%20%E5%B0%8F%E6%97%B6%EF%BC%8C%E6%9B%BF%E6%8D%A2%E5%8A%9F%E8%83%BD%E8%87%AA%E5%8A%A8%E5%A4%B1%E6%95%88%E3%80%82%22%3B%0A%20%20%20%20%20%20return%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20statusNode.textContent%20%3D%20%60%E6%9B%BF%E6%8D%A2%E5%8A%9F%E8%83%BD%E5%BC%80%E5%90%AF%E4%B8%AD%EF%BC%8C%E5%89%A9%E4%BD%99%E6%97%B6%E9%97%B4%3A%20%24%7BformatRemaining(getRemainingMs())%7D%60%3B%0A%20%20%7D%3B%0A%0A%20%20const%20syncStatusLoop%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(statusUpdater)%20%7B%0A%20%20%20%20%20%20clearInterval(statusUpdater)%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20statusUpdater%20%3D%20window.setInterval(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(config.enabled%20%26%26%20hasExpired())%20%7B%0A%20%20%20%20%20%20%20%20disableReplacement(%22%E5%B7%B2%E8%B6%85%E8%BF%87%204%20%E5%B0%8F%E6%97%B6%EF%BC%8C%E6%9B%BF%E6%8D%A2%E5%8A%9F%E8%83%BD%E8%87%AA%E5%8A%A8%E5%A4%B1%E6%95%88%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20updateStatusText()%3B%0A%20%20%20%20%7D%2C%201000)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20applyReplacements%20%3D%20(statusMessage%20%3D%20%22%22)%20%3D%3E%20%7B%0A%20%20%20%20if%20(applying)%20return%3B%0A%20%20%20%20applying%20%3D%20true%3B%0A%0A%20%20%20%20try%20%7B%0A%20%20%20%20%20%20applyFontAdjust()%3B%0A%20%20%20%20%20%20if%20(isReplacementActive())%20%7B%0A%20%20%20%20%20%20%20%20getReplacementRoots().forEach(walkAndReplace)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20updateStatusText(statusMessage)%3B%0A%20%20%20%20%7D%20finally%20%7B%0A%20%20%20%20%20%20applying%20%3D%20false%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20stopFastScanLoop%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(fastScanInterval)%20%7B%0A%20%20%20%20%20%20clearInterval(fastScanInterval)%3B%0A%20%20%20%20%20%20fastScanInterval%20%3D%20null%3B%0A%20%20%20%20%7D%0A%20%20%20%20if%20(fastScanStopTimer)%20%7B%0A%20%20%20%20%20%20clearTimeout(fastScanStopTimer)%3B%0A%20%20%20%20%20%20fastScanStopTimer%20%3D%20null%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20startFastScanLoop%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20stopFastScanLoop()%3B%0A%0A%20%20%20%20const%20tick%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(document.body)%20%7B%0A%20%20%20%20%20%20%20%20applyReplacements()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20tick()%3B%0A%20%20%20%20fastScanInterval%20%3D%20window.setInterval(tick%2C%20FAST_SCAN_INTERVAL_MS)%3B%0A%20%20%20%20fastScanStopTimer%20%3D%20window.setTimeout(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20stopFastScanLoop()%3B%0A%20%20%20%20%7D%2C%20FAST_SCAN_WINDOW_MS)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20startObserver%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(observer)%20observer.disconnect()%3B%0A%0A%20%20%20%20observer%20%3D%20new%20MutationObserver((mutations)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(applying)%20return%3B%0A%0A%20%20%20%20%20%20for%20(const%20mutation%20of%20mutations)%20%7B%0A%20%20%20%20%20%20%20%20if%20(mutation.type%20%3D%3D%3D%20%22characterData%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20updateTextNode(mutation.target)%3B%0A%20%20%20%20%20%20%20%20%20%20continue%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20mutation.addedNodes.forEach((node)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20if%20(node.nodeType%20%3D%3D%3D%20Node.TEXT_NODE)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20updateTextNode(node)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%20else%20if%20(node.nodeType%20%3D%3D%3D%20Node.ELEMENT_NODE)%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20walkAndReplace(node)%3B%0A%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20observer.observe(document.documentElement%2C%20%7B%0A%20%20%20%20%20%20childList%3A%20true%2C%0A%20%20%20%20%20%20subtree%3A%20true%2C%0A%20%20%20%20%20%20characterData%3A%20true%2C%0A%20%20%20%20%7D)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20ensureStyles%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(document.getElementById(STYLE_ID))%20return%3B%0A%0A%20%20%20%20const%20style%20%3D%20document.createElement(%22style%22)%3B%0A%20%20%20%20style.id%20%3D%20STYLE_ID%3B%0A%20%20%20%20style.textContent%20%3D%20%60%0A%20%20%20%20%20%20.codex-msmail-font-adjust%20body%2C%0A%20%20%20%20%20%20.codex-msmail-font-adjust%20input%2C%0A%20%20%20%20%20%20.codex-msmail-font-adjust%20button%2C%0A%20%20%20%20%20%20.codex-msmail-font-adjust%20textarea%2C%0A%20%20%20%20%20%20.codex-msmail-font-adjust%20select%20%7B%0A%20%20%20%20%20%20%20%20letter-spacing%3A%200.02em%20!important%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20%7B%0A%20%20%20%20%20%20%20%20position%3A%20fixed%3B%0A%20%20%20%20%20%20%20%20top%3A%20max(12px%2C%20env(safe-area-inset-top))%3B%0A%20%20%20%20%20%20%20%20left%3A%2012px%3B%0A%20%20%20%20%20%20%20%20right%3A%2012px%3B%0A%20%20%20%20%20%20%20%20z-index%3A%202147483647%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(247%2C%20244%2C%20237%2C%200.98)%3B%0A%20%20%20%20%20%20%20%20color%3A%20%232e2a26%3B%0A%20%20%20%20%20%20%20%20border%3A%201px%20solid%20rgba(60%2C%2049%2C%2038%2C%200.16)%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%2010px%3B%0A%20%20%20%20%20%20%20%20box-shadow%3A%200%2018px%2040px%20rgba(27%2C%2022%2C%2018%2C%200.18)%3B%0A%20%20%20%20%20%20%20%20padding%3A%2012px%3B%0A%20%20%20%20%20%20%20%20font%3A%2013px%2F1.35%20-apple-system%2C%20BlinkMacSystemFont%2C%20%22PingFang%20SC%22%2C%20%22Hiragino%20Sans%20GB%22%2C%20sans-serif%3B%0A%20%20%20%20%20%20%20%20backdrop-filter%3A%20blur(10px)%3B%0A%20%20%20%20%20%20%20%20-webkit-backdrop-filter%3A%20blur(10px)%3B%0A%20%20%20%20%20%20%20%20max-height%3A%20calc(100vh%20-%20max(24px%2C%20env(safe-area-inset-top))%20-%20max(24px%2C%20env(safe-area-inset-bottom)))%3B%0A%20%20%20%20%20%20%20%20overflow%3A%20hidden%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%5Bhidden%5D%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20none%20!important%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-collapsed%20.codex-body%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20none%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-header%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20align-items%3A%20flex-start%3B%0A%20%20%20%20%20%20%20%20justify-content%3A%20space-between%3B%0A%20%20%20%20%20%20%20%20gap%3A%2010px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-header-main%20%7B%0A%20%20%20%20%20%20%20%20min-width%3A%200%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-title%20%7B%0A%20%20%20%20%20%20%20%20font-size%3A%2018px%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20700%3B%0A%20%20%20%20%20%20%20%20border%3A%200%3B%0A%20%20%20%20%20%20%20%20background%3A%20transparent%3B%0A%20%20%20%20%20%20%20%20color%3A%20%232e2a26%3B%0A%20%20%20%20%20%20%20%20padding%3A%200%3B%0A%20%20%20%20%20%20%20%20text-align%3A%20left%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-close%20%7B%0A%20%20%20%20%20%20%20%20border%3A%200%3B%0A%20%20%20%20%20%20%20%20background%3A%20transparent%3B%0A%20%20%20%20%20%20%20%20color%3A%20%234b433b%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2014px%3B%0A%20%20%20%20%20%20%20%20padding%3A%202px%204px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-meta%20%7B%0A%20%20%20%20%20%20%20%20margin-top%3A%204px%3B%0A%20%20%20%20%20%20%20%20color%3A%20%236d6256%3B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%202px%3B%0A%20%20%20%20%20%20%20%20word-break%3A%20break-all%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-body%20%7B%0A%20%20%20%20%20%20%20%20margin-top%3A%2012px%3B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%2010px%3B%0A%20%20%20%20%20%20%20%20max-height%3A%20calc(100vh%20-%20180px)%3B%0A%20%20%20%20%20%20%20%20overflow-y%3A%20auto%3B%0A%20%20%20%20%20%20%20%20overflow-x%3A%20hidden%3B%0A%20%20%20%20%20%20%20%20padding-right%3A%202px%3B%0A%20%20%20%20%20%20%20%20-webkit-overflow-scrolling%3A%20touch%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20button%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20input%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20textarea%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20select%20%7B%0A%20%20%20%20%20%20%20%20font%3A%20inherit%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-action-row%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20gap%3A%208px%3B%0A%20%20%20%20%20%20%20%20flex-wrap%3A%20wrap%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-btn%20%7B%0A%20%20%20%20%20%20%20%20border%3A%200%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%20999px%3B%0A%20%20%20%20%20%20%20%20padding%3A%208px%2012px%3B%0A%20%20%20%20%20%20%20%20background%3A%20%23ddd4c7%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23352f29%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-btn.primary%20%7B%0A%20%20%20%20%20%20%20%20background%3A%20%23c6b091%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23201915%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-input%20%7B%0A%20%20%20%20%20%20%20%20width%3A%20100%25%3B%0A%20%20%20%20%20%20%20%20box-sizing%3A%20border-box%3B%0A%20%20%20%20%20%20%20%20border%3A%201px%20solid%20%23d5c8b8%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%208px%3B%0A%20%20%20%20%20%20%20%20padding%3A%209px%2010px%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(255%2C%20255%2C%20255%2C%200.9)%3B%0A%20%20%20%20%20%20%20%20color%3A%20%232f2924%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-check-row%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20flex%3B%0A%20%20%20%20%20%20%20%20gap%3A%2012px%3B%0A%20%20%20%20%20%20%20%20flex-wrap%3A%20wrap%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23433b34%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-check%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20inline-flex%3B%0A%20%20%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20%20%20gap%3A%206px%3B%0A%20%20%20%20%20%20%20%20padding%3A%206px%2010px%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%20999px%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(255%2C%20255%2C%20255%2C%200.68)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-table%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%208px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rules-scroll%20%7B%0A%20%20%20%20%20%20%20%20overflow-x%3A%20auto%3B%0A%20%20%20%20%20%20%20%20overflow-y%3A%20visible%3B%0A%20%20%20%20%20%20%20%20-webkit-overflow-scrolling%3A%20touch%3B%0A%20%20%20%20%20%20%20%20padding-bottom%3A%204px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-guide%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%206px%3B%0A%20%20%20%20%20%20%20%20padding%3A%2010px%2012px%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%208px%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(255%2C%20255%2C%20255%2C%200.62)%3B%0A%20%20%20%20%20%20%20%20color%3A%20%235c5248%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-guide-strong%20%7B%0A%20%20%20%20%20%20%20%20font-weight%3A%20700%3B%0A%20%20%20%20%20%20%20%20color%3A%20%232f2924%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-table-head%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20grid-template-columns%3A%20minmax(0%2C%201fr)%20minmax(0%2C%201fr)%2058px%3B%0A%20%20%20%20%20%20%20%20gap%3A%208px%3B%0A%20%20%20%20%20%20%20%20align-items%3A%20center%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-table-head%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20grid-template-columns%3A%20minmax(0%2C%201fr)%20minmax(0%2C%201fr)%20112px%2058px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-col-mode%2C%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-mode-block%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20none%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-col-mode%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20block%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-mode-block%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%204px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-table-head%20%7B%0A%20%20%20%20%20%20%20%20color%3A%20%235d5348%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20600%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(255%2C%20255%2C%20255%2C%200.48)%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%2014px%3B%0A%20%20%20%20%20%20%20%20padding%3A%2012px%3B%0A%20%20%20%20%20%20%20%20border%3A%201px%20solid%20rgba(190%2C%20176%2C%20157%2C%200.5)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-field-block%20%7B%0A%20%20%20%20%20%20%20%20display%3A%20grid%3B%0A%20%20%20%20%20%20%20%20gap%3A%204px%3B%0A%20%20%20%20%20%20%20%20min-width%3A%200%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-field-label%20%7B%0A%20%20%20%20%20%20%20%20font-size%3A%2011px%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20700%3B%0A%20%20%20%20%20%20%20%20letter-spacing%3A%200.01em%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-field-label.original%20%7B%0A%20%20%20%20%20%20%20%20color%3A%20%238a5a26%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-field-label.replacement%20%7B%0A%20%20%20%20%20%20%20%20color%3A%20%231f6b45%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20.codex-input.original%20%7B%0A%20%20%20%20%20%20%20%20border-color%3A%20%23d9bf9a%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(255%2C%20248%2C%20240%2C%200.95)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20.codex-input.replacement%20%7B%0A%20%20%20%20%20%20%20%20border-color%3A%20%239fc7ae%3B%0A%20%20%20%20%20%20%20%20background%3A%20rgba(244%2C%20255%2C%20248%2C%200.95)%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-small-btn%20%7B%0A%20%20%20%20%20%20%20%20border%3A%200%3B%0A%20%20%20%20%20%20%20%20border-radius%3A%208px%3B%0A%20%20%20%20%20%20%20%20background%3A%20%23e4d9cb%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23443b32%3B%0A%20%20%20%20%20%20%20%20padding%3A%2011px%208px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-status%20%7B%0A%20%20%20%20%20%20%20%20color%3A%20%2375685a%3B%0A%20%20%20%20%20%20%20%20font-size%3A%2012px%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-mode-label%20%7B%0A%20%20%20%20%20%20%20%20font-size%3A%2011px%3B%0A%20%20%20%20%20%20%20%20font-weight%3A%20700%3B%0A%20%20%20%20%20%20%20%20color%3A%20%23555048%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%40media%20(max-width%3A%20520px)%20%7B%0A%20%20%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-table-head%2C%0A%20%20%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20%20%20grid-template-columns%3A%20minmax(0%2C%201fr)%20minmax(0%2C%201fr)%2058px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-table-head%2C%0A%20%20%20%20%20%20%20%20%23%24%7BPANEL_ID%7D.is-advanced%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20%20%20grid-template-columns%3A%20minmax(0%2C%201fr)%20minmax(0%2C%201fr)%20104px%2052px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%23%24%7BPANEL_ID%7D%20.codex-rule-row%20%7B%0A%20%20%20%20%20%20%20%20%20%20gap%3A%2010px%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%60%3B%0A%0A%20%20%20%20document.head.appendChild(style)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20escapeHtml%20%3D%20(value)%20%3D%3E%0A%20%20%20%20value%0A%20%20%20%20%20%20.replace(%2F%26%2Fg%2C%20%22%26amp%3B%22)%0A%20%20%20%20%20%20.replace(%2F%3C%2Fg%2C%20%22%26lt%3B%22)%0A%20%20%20%20%20%20.replace(%2F%3E%2Fg%2C%20%22%26gt%3B%22)%0A%20%20%20%20%20%20.replace(%2F%22%2Fg%2C%20%22%26quot%3B%22)%3B%0A%0A%20%20const%20buildMetaText%20%3D%20()%20%3D%3E%20%5B%0A%20%20%20%20%60%E5%BD%93%E5%89%8D%E7%BD%91%E7%AB%99%3A%20%24%7Blocation.hostname%7D%60%2C%0A%20%20%5D%3B%0A%0A%20%20%2F%2F%20%E4%BB%8E%E4%BC%9A%E5%91%98%E9%A1%B5%E9%9D%A2%20HTML%20%E4%B8%AD%E6%8F%90%E5%8F%96%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%E5%AD%97%E6%AE%B5%EF%BC%88%E9%80%9A%E7%94%A8%E8%A7%A3%E6%9E%90%E5%99%A8%EF%BC%89%0A%20%20const%20extractMemberInfo%20%3D%20(html)%20%3D%3E%20%7B%0A%20%20%20%20const%20doc%20%3D%20new%20DOMParser().parseFromString(html%2C%20%22text%2Fhtml%22)%3B%0A%20%20%20%20const%20fields%20%3D%20%5B%5D%3B%0A%20%20%20%20const%20push%20%3D%20(label%2C%20value)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20v%20%3D%20String(value%20%7C%7C%20%22%22).replace(%2F%5Cs%2B%2Fg%2C%20%22%20%22).trim()%3B%0A%20%20%20%20%20%20if%20(v%20%26%26%20v.length%20%3C%3D%2080%20%26%26%20!fields.some((f)%20%3D%3E%20f.value%20%3D%3D%3D%20v))%20%7B%0A%20%20%20%20%20%20%20%20fields.push(%7B%20label%3A%20String(label%20%7C%7C%20%22%22).replace(%2F%5Cs%2B%2Fg%2C%20%22%20%22).trim()%2C%20value%3A%20v%20%7D)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20%2F%2F%201)%20dl%20%3E%20dt%20%2B%20dd%20%E7%BB%93%E6%9E%84%0A%20%20%20%20doc.querySelectorAll(%22dl%22).forEach((dl)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20dt%20%3D%20dl.querySelector(%22dt%22)%3B%0A%20%20%20%20%20%20const%20dd%20%3D%20dl.querySelector(%22dd%22)%3B%0A%20%20%20%20%20%20if%20(dt%20%26%26%20dd)%20push(dt.textContent%2C%20dd.textContent)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%2F%2F%202)%20table%20%E4%B8%AD%20th%20%2B%20td%20%E7%BB%93%E6%9E%84%0A%20%20%20%20doc.querySelectorAll(%22tr%22).forEach((tr)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20th%20%3D%20tr.querySelector(%22th%22)%3B%0A%20%20%20%20%20%20const%20td%20%3D%20tr.querySelector(%22td%22)%3B%0A%20%20%20%20%20%20if%20(th%20%26%26%20td)%20push(th.textContent%2C%20td.textContent)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%2F%2F%203)%20%E8%BE%93%E5%85%A5%E6%A1%86%20value%EF%BC%88%E6%96%87%E6%9C%AC%E7%B1%BB%EF%BC%89%0A%20%20%20%20doc.querySelectorAll(%22input%22).forEach((inp)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20t%20%3D%20(inp.type%20%7C%7C%20%22text%22).toLowerCase()%3B%0A%20%20%20%20%20%20if%20(%5B%22text%22%2C%20%22email%22%2C%20%22tel%22%2C%20%22search%22%5D.includes(t)%20%26%26%20inp.value)%20%7B%0A%20%20%20%20%20%20%20%20const%20label%20%3D%20inp.getAttribute(%22aria-label%22)%20%7C%7C%20inp.getAttribute(%22title%22)%20%7C%7C%20inp.name%20%7C%7C%20%22%E5%85%A5%E5%8A%9B%E5%80%A4%22%3B%0A%20%20%20%20%20%20%20%20push(label%2C%20inp.value)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%2F%2F%204)%20%E5%B8%B8%E8%A7%81%E6%97%A5%E6%96%87%E4%BC%9A%E5%91%98%E5%AD%97%E6%AE%B5%E5%90%8D%EF%BC%9A%E6%89%BE%E5%88%B0%E6%A0%87%E7%AD%BE%E5%85%83%E7%B4%A0%EF%BC%8C%E5%8F%96%E5%85%B6%E7%9B%B8%E9%82%BB%E5%80%BC%0A%20%20%20%20const%20knownLabels%20%3D%20%5B%22%E4%BC%9A%E5%93%A1%E7%95%AA%E5%8F%B7%22%2C%20%22%E4%BC%9A%E5%93%A1No%22%2C%20%22%E4%BC%9A%E5%93%A1ID%22%2C%20%22%E6%B0%8F%E5%90%8D%22%2C%20%22%E3%81%8A%E5%90%8D%E5%89%8D%22%2C%20%22%E3%83%95%E3%83%AA%E3%82%AC%E3%83%8A%22%2C%20%22%E3%83%8B%E3%83%83%E3%82%AF%E3%83%8D%E3%83%BC%E3%83%A0%22%2C%20%22%E3%83%A1%E3%83%BC%E3%83%AB%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%22%2C%20%22%E9%9B%BB%E8%A9%B1%E7%95%AA%E5%8F%B7%22%2C%20%22%E7%94%9F%E5%B9%B4%E6%9C%88%E6%97%A5%22%2C%20%22%E6%80%A7%E5%88%A5%22%2C%20%22%E4%BD%8F%E6%89%80%22%5D%3B%0A%20%20%20%20const%20labelCandidates%20%3D%20doc.querySelectorAll(%22div%2C%20span%2C%20p%2C%20label%2C%20th%2C%20dt%22)%3B%0A%20%20%20%20knownLabels.forEach((lb)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20el%20%3D%20Array.from(labelCandidates).find((e)%20%3D%3E%20e.textContent.replace(%2F%5Cs%2B%2Fg%2C%20%22%22).trim()%20%3D%3D%3D%20lb)%3B%0A%20%20%20%20%20%20if%20(!el)%20return%3B%0A%20%20%20%20%20%20const%20sibling%20%3D%20el.nextElementSibling%3B%0A%20%20%20%20%20%20if%20(sibling)%20push(lb%2C%20sibling.textContent)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%2F%2F%205)%20%E9%A1%B5%E9%9D%A2%E5%86%85%E5%B5%8C%E8%84%9A%E6%9C%AC%E5%8F%98%E9%87%8F%20member_data%EF%BC%88%E4%BC%9A%E5%93%A1%E7%95%AA%E5%8F%B7%20%2F%20%E7%94%9F%E5%B9%B4%E6%9C%88%E6%97%A5%20%2F%20%E6%80%A7%E5%88%A5%EF%BC%89%0A%20%20%20%20const%20memberDataMatch%20%3D%20html.match(%2Fvar%5Cs%2Bmember_data%5Cs*%3D%5Cs*(%5C%7B%5B%5Cs%5CS%5D*%3F%5C%7D)(%3F%3A%5Cs*%3B)%3F%5Cs*(%3F%3A%3C%5C%2Fscript%3E%7C%24)%2Fi)%3B%0A%20%20%20%20if%20(memberDataMatch)%20%7B%0A%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20const%20data%20%3D%20JSON.parse(memberDataMatch%5B1%5D)%3B%0A%20%20%20%20%20%20%20%20if%20(data.member_id)%20push(%22%E4%BC%9A%E5%93%A1%E7%95%AA%E5%8F%B7%22%2C%20data.member_id)%3B%0A%20%20%20%20%20%20%20%20if%20(data.birth)%20push(%22%E7%94%9F%E5%B9%B4%E6%9C%88%E6%97%A5%22%2C%20data.birth)%3B%0A%20%20%20%20%20%20%20%20if%20(data.sex)%20push(%22%E6%80%A7%E5%88%A5%22%2C%20data.sex)%3B%0A%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20%2F%2F%20%E5%BF%BD%E7%95%A5%E8%A7%A3%E6%9E%90%E5%A4%B1%E8%B4%A5%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20%2F%2F%206)%20%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%88%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%9AN%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%EF%BC%8C%E5%80%BC%E5%89%8D%E5%90%8E%E5%8F%AF%E8%83%BD%E5%A4%B9%E7%9D%80%E6%A0%87%E7%AD%BE%EF%BC%89%0A%20%20%20%20const%20pointMatch%20%3D%20html.match(%2F%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%5B%EF%BC%9A%3A%5D%5B%5E0-9%5D*(%5B%5Cd%2C%5D%2B)%5B%5E0-9%5D*%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%2F)%3B%0A%20%20%20%20if%20(pointMatch)%20push(%22%E7%8F%BE%E5%9C%A8%E3%81%AE%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%22%2C%20pointMatch%5B1%5D)%3B%0A%0A%20%20%20%20%2F%2F%207)%20%E9%A1%B5%E9%9D%A2%E6%A0%87%E9%A2%98%20h1%E3%80%8CXXX%20%E3%81%95%E3%82%93%E3%81%AE%E3%83%9E%E3%82%A4%E3%83%9A%E3%83%BC%E3%82%B8%E3%80%8D%E2%86%92%20%E6%98%B5%E7%A7%B0%0A%20%20%20%20const%20titleMatch%20%3D%20html.match(%2F%3Ch1%5B%5E%3E%5D*%3E(%5B%5E%3C%5D*%3F)%5Cs*%E3%81%95%E3%82%93%E3%81%AE%E3%83%9E%E3%82%A4%E3%83%9A%E3%83%BC%E3%82%B8%3C%5C%2Fh1%3E%2F)%3B%0A%20%20%20%20if%20(titleMatch%20%26%26%20titleMatch%5B1%5D)%20push(%22%E3%83%8B%E3%83%83%E3%82%AF%E3%83%8D%E3%83%BC%E3%83%A0(%E9%A1%B5%E9%9D%A2%E6%A0%87%E9%A2%98)%22%2C%20titleMatch%5B1%5D)%3B%0A%0A%20%20%20%20return%20fields%3B%0A%20%20%7D%3B%0A%0A%20%20const%20createRuleRowHtml%20%3D%20(rule%2C%20index)%20%3D%3E%20%60%0A%20%20%20%20%3Cdiv%20class%3D%22codex-rule-row%22%20data-index%3D%22%24%7Bindex%7D%22%3E%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-field-block%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-field-label%20original%22%3E%E7%BD%91%E9%A1%B5%E5%BD%93%E5%89%8D%E6%98%BE%E7%A4%BA%E7%9A%84%E5%8E%9F%E6%96%87%E5%AD%97%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cinput%20class%3D%22codex-input%20original%22%20type%3D%22text%22%20value%3D%22%24%7BescapeHtml(rule.original)%7D%22%20data-rule-field%3D%22original%22%20placeholder%3D%22%E4%BE%8B%E5%A6%82%3A%20user%40example.com%20%2F%20%E6%98%BE%E7%A4%BA%E5%90%8D%E7%A7%B0%22%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-field-block%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-field-label%20replacement%22%3E%E4%BD%A0%E6%83%B3%E6%98%BE%E7%A4%BA%E7%9A%84%E6%96%B0%E6%96%87%E5%AD%97%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cinput%20class%3D%22codex-input%20replacement%22%20type%3D%22text%22%20value%3D%22%24%7BescapeHtml(rule.replacement)%7D%22%20data-rule-field%3D%22replacement%22%20placeholder%3D%22%E4%BE%8B%E5%A6%82%3A%20alias%40example.com%20%2F%20%E6%96%B0%E5%90%8D%E7%A7%B0%22%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-mode-block%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-mode-label%22%3E%E6%A8%A1%E5%BC%8F%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cselect%20class%3D%22codex-input%22%20data-rule-field%3D%22mode%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Coption%20value%3D%22normal%22%20%24%7Brule.mode%20%3D%3D%3D%20%22normal%22%20%3F%20%22selected%22%20%3A%20%22%22%7D%3E%E6%99%AE%E9%80%9A%3C%2Foption%3E%0A%20%20%20%20%20%20%20%20%20%20%3Coption%20value%3D%22regex%22%20%24%7Brule.mode%20%3D%3D%3D%20%22regex%22%20%3F%20%22selected%22%20%3A%20%22%22%7D%3E%E6%AD%A3%E5%88%99%3C%2Foption%3E%0A%20%20%20%20%20%20%20%20%3C%2Fselect%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-small-btn%22%20data-action%3D%22remove-rule%22%3E%E5%88%A0%E9%99%A4%3C%2Fbutton%3E%0A%20%20%20%20%3C%2Fdiv%3E%0A%20%20%60%3B%0A%0A%20%20const%20getPanel%20%3D%20()%20%3D%3E%20document.getElementById(PANEL_ID)%3B%0A%0A%20%20const%20syncPanelVisibility%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20panel%20%3D%20getPanel()%3B%0A%20%20%20%20if%20(!panel)%20return%3B%0A%20%20%20%20panel.hidden%20%3D%20!config.panelVisible%3B%0A%20%20%20%20panel.classList.toggle(%22is-collapsed%22%2C%20Boolean(config.bodyCollapsed))%3B%0A%20%20%7D%3B%0A%0A%20%20const%20showPanel%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20config.panelVisible%20%3D%20true%3B%0A%20%20%20%20saveConfig()%3B%0A%20%20%20%20syncPanelVisibility()%3B%0A%20%20%20%20updateStatusText(%22%E8%AE%BE%E7%BD%AE%E9%9D%A2%E6%9D%BF%E5%B7%B2%E6%89%93%E5%BC%80%E3%80%82%22)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20hidePanel%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20config.panelVisible%20%3D%20false%3B%0A%20%20%20%20saveConfig()%3B%0A%20%20%20%20syncPanelVisibility()%3B%0A%20%20%7D%3B%0A%0A%20%20const%20togglePanelBody%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20config.bodyCollapsed%20%3D%20!config.bodyCollapsed%3B%0A%20%20%20%20saveConfig()%3B%0A%20%20%20%20syncPanelVisibility()%3B%0A%20%20%7D%3B%0A%0A%20%20const%20startSessionNow%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20config.sessionStartedAt%20%3D%20Date.now()%3B%0A%20%20%7D%3B%0A%0A%20%20const%20disableReplacement%20%3D%20(message%20%3D%20%22%E6%9B%BF%E6%8D%A2%E5%8A%9F%E8%83%BD%E5%B7%B2%E5%85%B3%E9%97%AD%EF%BC%8C%E9%A1%B5%E9%9D%A2%E6%AD%A3%E5%B8%B8%E6%98%BE%E7%A4%BA%E3%80%82%22)%20%3D%3E%20%7B%0A%20%20%20%20config.enabled%20%3D%20false%3B%0A%20%20%20%20config.sessionStartedAt%20%3D%20null%3B%0A%20%20%20%20saveConfig()%3B%0A%20%20%20%20hidePanel()%3B%0A%20%20%7D%3B%0A%0A%20%20const%20clearHoldTimer%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(holdTimer)%20%7B%0A%20%20%20%20%20%20clearTimeout(holdTimer)%3B%0A%20%20%20%20%20%20holdTimer%20%3D%20null%3B%0A%20%20%20%20%7D%0A%20%20%20%20threeFingerHold%20%3D%20false%3B%0A%20%20%7D%3B%0A%0A%20%20const%20clearSelection%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20sel%20%3D%20window.getSelection%20%26%26%20window.getSelection()%3B%0A%20%20%20%20if%20(sel%20%26%26%20typeof%20sel.removeAllRanges%20%3D%3D%3D%20%22function%22)%20%7B%0A%20%20%20%20%20%20sel.removeAllRanges()%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20recordTripleClick%20%3D%20(clientY)%20%3D%3E%20%7B%0A%20%20%20%20if%20(config.panelVisible)%20return%3B%0A%20%20%20%20if%20(clientY%20%3E%20PANEL_HOLD_ZONE_PX)%20return%3B%0A%0A%20%20%20%20const%20now%20%3D%20Date.now()%3B%0A%20%20%20%20tripleClickTimes%20%3D%20tripleClickTimes.filter(%0A%20%20%20%20%20%20(t)%20%3D%3E%20now%20-%20t.time%20%3C%3D%20TRIPLE_CLICK_WINDOW_MS%0A%20%20%20%20)%3B%0A%20%20%20%20tripleClickTimes.push(%7B%20time%3A%20now%2C%20y%3A%20clientY%20%7D)%3B%0A%0A%20%20%20%20if%20(tripleClickTimes.length%20%3C%203)%20return%3B%0A%0A%20%20%20%20const%20ys%20%3D%20tripleClickTimes.map((t)%20%3D%3E%20t.y)%3B%0A%20%20%20%20const%20spread%20%3D%20Math.max(...ys)%20-%20Math.min(...ys)%3B%0A%20%20%20%20tripleClickTimes%20%3D%20%5B%5D%3B%0A%0A%20%20%20%20if%20(spread%20%3C%3D%20TRIPLE_CLICK_MAX_SPREAD)%20%7B%0A%20%20%20%20%20%20showPanel()%3B%0A%20%20%20%20%20%20setTimeout(clearSelection%2C%200)%3B%0A%20%20%20%20%7D%0A%20%20%7D%3B%0A%0A%20%20const%20startTopHoldDetector%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20const%20beginHold%20%3D%20(clientY%2C%20isThreeFinger%20%3D%20false)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(config.panelVisible)%20return%3B%0A%20%20%20%20%20%20if%20(!isThreeFinger%20%26%26%20clientY%20%3E%20PANEL_HOLD_ZONE_PX)%20return%3B%0A%0A%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20topHoldStartY%20%3D%20clientY%3B%0A%20%20%20%20%20%20threeFingerHold%20%3D%20isThreeFinger%3B%0A%20%20%20%20%20%20holdTimer%20%3D%20window.setTimeout(()%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20holdTimer%20%3D%20null%3B%0A%20%20%20%20%20%20%20%20threeFingerHold%20%3D%20false%3B%0A%20%20%20%20%20%20%20%20showPanel()%3B%0A%20%20%20%20%20%20%7D%2C%20PANEL_HOLD_MS)%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20moveHold%20%3D%20(clientY%2C%20touchesLength%20%3D%201)%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(!holdTimer)%20return%3B%0A%20%20%20%20%20%20if%20(threeFingerHold%20%26%26%20touchesLength%20!%3D%3D%203)%20%7B%0A%20%20%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20if%20(%0A%20%20%20%20%20%20%20%20Math.abs(clientY%20-%20topHoldStartY)%20%3E%2014%20%7C%7C%0A%20%20%20%20%20%20%20%20(!threeFingerHold%20%26%26%20clientY%20%3E%20PANEL_HOLD_ZONE_PX%20%2B%2020)%0A%20%20%20%20%20%20)%20%7B%0A%20%20%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20document.addEventListener(%0A%20%20%20%20%20%20%22touchstart%22%2C%0A%20%20%20%20%20%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20(event.touches.length%20%3D%3D%3D%201)%20%7B%0A%20%20%20%20%20%20%20%20%20%20beginHold(event.touches%5B0%5D.clientY%2C%20false)%3B%0A%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20if%20(event.touches.length%20%3D%3D%3D%203)%20%7B%0A%20%20%20%20%20%20%20%20%20%20beginHold(event.touches%5B0%5D.clientY%2C%20true)%3B%0A%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%7B%20passive%3A%20true%20%7D%0A%20%20%20%20)%3B%0A%0A%20%20%20%20document.addEventListener(%0A%20%20%20%20%20%20%22touchmove%22%2C%0A%20%20%20%20%20%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20if%20(event.touches.length%20%3C%201)%20%7B%0A%20%20%20%20%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20moveHold(event.touches%5B0%5D.clientY%2C%20event.touches.length)%3B%0A%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%7B%20passive%3A%20true%20%7D%0A%20%20%20%20)%3B%0A%0A%20%20%20%20document.addEventListener(%22touchend%22%2C%20clearHoldTimer%2C%20%7B%20passive%3A%20true%20%7D)%3B%0A%20%20%20%20document.addEventListener(%22touchcancel%22%2C%20clearHoldTimer%2C%20%7B%20passive%3A%20true%20%7D)%3B%0A%0A%20%20%20%20document.addEventListener(%22mousedown%22%2C%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20beginHold(event.clientY)%3B%0A%20%20%20%20%20%20%2F%2F%20%E9%A1%B6%E9%83%A8%E5%8C%BA%E5%9F%9F%E5%86%85%E3%80%81%E4%B8%94%E5%B7%B2%E6%9C%89%E8%BF%91%E6%9C%9F%E7%82%B9%E5%87%BB%EF%BC%88%E6%AD%A3%E5%9C%A8%E5%BD%A2%E6%88%90%E4%B8%89%E5%87%BB%E7%9A%84%E5%90%8E%E7%BB%AD%E7%82%B9%E5%87%BB%EF%BC%89%E2%86%92%20%E9%98%BB%E6%AD%A2%E6%B5%8F%E8%A7%88%E5%99%A8%E9%80%89%E4%B8%AD%E6%96%87%E6%9C%AC%0A%20%20%20%20%20%20const%20isInZone%20%3D%20event.clientY%20%3C%3D%20PANEL_HOLD_ZONE_PX%3B%0A%20%20%20%20%20%20const%20lastTap%20%3D%0A%20%20%20%20%20%20%20%20tripleClickTimes.length%20%3E%200%0A%20%20%20%20%20%20%20%20%20%20%3F%20tripleClickTimes%5BtripleClickTimes.length%20-%201%5D%0A%20%20%20%20%20%20%20%20%20%20%3A%20null%3B%0A%20%20%20%20%20%20const%20isChainTap%20%3D%0A%20%20%20%20%20%20%20%20lastTap%20!%3D%3D%20null%20%26%26%0A%20%20%20%20%20%20%20%20Date.now()%20-%20lastTap.time%20%3C%3D%20TRIPLE_CLICK_WINDOW_MS%3B%0A%20%20%20%20%20%20if%20(isInZone%20%26%26%20isChainTap%20%26%26%20event.defaultPrevented%20%3D%3D%3D%20false)%20%7B%0A%20%20%20%20%20%20%20%20event.preventDefault()%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20document.addEventListener(%22mousemove%22%2C%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20moveHold(event.clientY)%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20document.addEventListener(%22mouseup%22%2C%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20clearHoldTimer()%3B%0A%20%20%20%20%20%20recordTripleClick(event.clientY)%3B%0A%20%20%20%20%7D)%3B%0A%20%20%20%20document.addEventListener(%22mouseleave%22%2C%20clearHoldTimer)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20buildPanel%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(document.getElementById(PANEL_ID))%20return%3B%0A%0A%20%20%20%20ensureStyles()%3B%0A%0A%20%20%20%20const%20panel%20%3D%20document.createElement(%22section%22)%3B%0A%20%20%20%20panel.id%20%3D%20PANEL_ID%3B%0A%0A%20%20%20%20panel.innerHTML%20%3D%20%60%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-header%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-header-main%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-title%22%20data-action%3D%22toggle-body%22%3E%E5%9B%BA%E5%AE%9A%E7%BD%91%E7%AB%99%E6%98%BE%E7%A4%BA%E6%9B%BF%E6%8D%A2%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-meta%22%3E%24%7BbuildMetaText()%0A%20%20%20%20%20%20%20%20%20%20%20%20.map((line)%20%3D%3E%20%60%3Cdiv%3E%24%7BescapeHtml(line)%7D%3C%2Fdiv%3E%60)%0A%20%20%20%20%20%20%20%20%20%20%20%20.join(%22%22)%7D%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-close%22%20data-action%3D%22hide-panel%22%3E%E5%85%B3%E9%97%AD%3C%2Fbutton%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-body%22%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-check-row%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Clabel%20class%3D%22codex-check%22%3E%3Cinput%20type%3D%22checkbox%22%20data-field%3D%22enabled%22%3E%20%E5%90%AF%E7%94%A8%E6%9B%BF%E6%8D%A2%3C%2Flabel%3E%0A%20%20%20%20%20%20%20%20%20%20%3Clabel%20class%3D%22codex-check%22%3E%3Cinput%20type%3D%22checkbox%22%20data-field%3D%22fontAdjust%22%3E%20%E5%90%AF%E7%94%A8%E5%AD%97%E4%BD%93%E5%BE%AE%E8%B0%83%3C%2Flabel%3E%0A%20%20%20%20%20%20%20%20%20%20%3Clabel%20class%3D%22codex-check%22%3E%3Cinput%20type%3D%22checkbox%22%20data-field%3D%22replaceValues%22%3E%20%E5%90%8C%E6%97%B6%E6%9B%BF%E6%8D%A2%E8%BE%93%E5%85%A5%E6%A1%86%E5%86%85%E5%AE%B9%3C%2Flabel%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-action-row%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%22%20data-action%3D%22fetch-member%22%3E%E8%8E%B7%E5%8F%96%E4%BC%9A%E5%91%98%E4%BF%A1%E6%81%AF%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%22%20data-action%3D%22add-rule%22%3E%E6%B7%BB%E5%8A%A0%E8%A7%84%E5%88%99%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%20primary%22%20data-action%3D%22save%22%3E%E4%BF%9D%E5%AD%98%E5%B9%B6%E5%BA%94%E7%94%A8%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%22%20data-action%3D%22disable%22%3E%E5%85%B3%E9%97%AD%E5%8A%9F%E8%83%BD%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%22%20data-action%3D%22restart-hour%22%3E%E9%87%8D%E6%96%B0%E8%AE%A1%E6%97%B6%204%20%E5%B0%8F%E6%97%B6%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-status%22%20data-role%3D%22status%22%3E%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-table%22%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-rule-guide%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-guide-strong%22%3E%E5%B7%A6%E8%BE%B9%E5%A1%AB%E7%BD%91%E9%A1%B5%E5%BD%93%E5%89%8D%E6%98%BE%E7%A4%BA%E7%9A%84%E5%86%85%E5%AE%B9%EF%BC%8C%E5%8F%B3%E8%BE%B9%E5%A1%AB%E4%BD%A0%E6%83%B3%E6%98%BE%E7%A4%BA%E7%9A%84%E6%96%B0%E5%86%85%E5%AE%B9%E3%80%82%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%3E%E8%A7%84%E5%88%99%E4%BC%9A%E6%9B%BF%E6%8D%A2%E9%A1%B5%E9%9D%A2%E9%87%8C%E5%8C%B9%E9%85%8D%E5%88%B0%E7%9A%84%E5%8F%AF%E8%A7%81%E6%96%87%E5%AD%97%EF%BC%9B%E7%99%BB%E5%BD%95%E6%A1%86%E9%87%8C%E6%89%8B%E5%8A%A8%E8%BE%93%E5%85%A5%E7%9A%84%E5%86%85%E5%AE%B9%E4%B8%8D%E4%BC%9A%E8%A2%AB%E4%BF%AE%E6%94%B9%E3%80%82%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%3E%E7%A4%BA%E4%BE%8B%3A%20%E5%8E%9F%E6%96%87%E5%AD%97%20user%40example.com%20-%3E%20%E6%96%B0%E6%96%87%E5%AD%97%20alias%40example.com%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cbutton%20type%3D%22button%22%20class%3D%22codex-btn%20codex-advanced-toggle%22%20data-action%3D%22toggle-advanced%22%3E%E2%9A%99%20%E9%AB%98%E7%BA%A7%E6%9B%BF%E6%8D%A2%EF%BC%88%E6%AD%A3%E5%88%99%EF%BC%89%3C%2Fbutton%3E%0A%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-rules-scroll%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-table-head%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%3E%E5%8E%9F%E6%96%87%E5%AD%97%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%3E%E6%96%B0%E6%96%87%E5%AD%97%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-col-mode%22%3E%E6%A8%A1%E5%BC%8F%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%3E%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cdiv%20class%3D%22codex-rules%22%3E%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%20%20%3C%2Fdiv%3E%0A%20%20%20%20%60%3B%0A%0A%20%20%20%20const%20rulesContainer%20%3D%20panel.querySelector(%22.codex-rules%22)%3B%0A%20%20%20%20const%20advancedToggleBtn%20%3D%20panel.querySelector('%5Bdata-action%3D%22toggle-advanced%22%5D')%3B%0A%0A%20%20%20%20const%20syncAdvancedMode%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20hasRegex%20%3D%20(config.rules%20%7C%7C%20%5B%5D).some((r)%20%3D%3E%20r.mode%20%3D%3D%3D%20%22regex%22)%3B%0A%20%20%20%20%20%20panel.classList.toggle(%22is-advanced%22%2C%20hasRegex)%3B%0A%20%20%20%20%20%20if%20(advancedToggleBtn)%20%7B%0A%20%20%20%20%20%20%20%20advancedToggleBtn.textContent%20%3D%20hasRegex%20%3F%20%22%E2%86%A9%20%E8%BF%94%E5%9B%9E%E7%AE%80%E5%8D%95%E6%9B%BF%E6%8D%A2%22%20%3A%20%22%E2%9A%99%20%E9%AB%98%E7%BA%A7%E6%9B%BF%E6%8D%A2%EF%BC%88%E6%AD%A3%E5%88%99%EF%BC%89%22%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20renderRules%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(!rulesContainer)%20return%3B%0A%20%20%20%20%20%20rulesContainer.innerHTML%20%3D%20config.rules.map((rule%2C%20index)%20%3D%3E%20createRuleRowHtml(rule%2C%20index)).join(%22%22)%3B%0A%20%20%20%20%20%20syncAdvancedMode()%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20syncFields%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20panel.querySelectorAll(%22%5Bdata-field%5D%22).forEach((input)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20field%20%3D%20input.getAttribute(%22data-field%22)%3B%0A%20%20%20%20%20%20%20%20if%20(!field)%20return%3B%0A%0A%20%20%20%20%20%20%20%20if%20(input%20instanceof%20HTMLInputElement%20%26%26%20input.type%20%3D%3D%3D%20%22checkbox%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20input.checked%20%3D%20Boolean(config%5Bfield%5D)%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20if%20(input%20instanceof%20HTMLInputElement)%20%7B%0A%20%20%20%20%20%20%20%20%20%20input.value%20%3D%20String(config%5Bfield%5D%20%7C%7C%20%22%22)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20readFields%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20panel.querySelectorAll(%22%5Bdata-field%5D%22).forEach((input)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20field%20%3D%20input.getAttribute(%22data-field%22)%3B%0A%20%20%20%20%20%20%20%20if%20(!field)%20return%3B%0A%0A%20%20%20%20%20%20%20%20if%20(input%20instanceof%20HTMLInputElement%20%26%26%20input.type%20%3D%3D%3D%20%22checkbox%22)%20%7B%0A%20%20%20%20%20%20%20%20%20%20config%5Bfield%5D%20%3D%20input.checked%3B%0A%20%20%20%20%20%20%20%20%7D%20else%20if%20(input%20instanceof%20HTMLInputElement)%20%7B%0A%20%20%20%20%20%20%20%20%20%20config%5Bfield%5D%20%3D%20input.value.trim()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20readRules%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20nextRules%20%3D%20%5B%5D%3B%0A%20%20%20%20%20%20panel.querySelectorAll(%22.codex-rule-row%22).forEach((row)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20const%20original%20%3D%20row.querySelector('%5Bdata-rule-field%3D%22original%22%5D')%3B%0A%20%20%20%20%20%20%20%20const%20replacement%20%3D%20row.querySelector('%5Bdata-rule-field%3D%22replacement%22%5D')%3B%0A%20%20%20%20%20%20%20%20const%20mode%20%3D%20row.querySelector('%5Bdata-rule-field%3D%22mode%22%5D')%3B%0A%0A%20%20%20%20%20%20%20%20nextRules.push(%7B%0A%20%20%20%20%20%20%20%20%20%20enabled%3A%20true%2C%0A%20%20%20%20%20%20%20%20%20%20original%3A%20original%20instanceof%20HTMLInputElement%20%3F%20original.value.trim()%20%3A%20%22%22%2C%0A%20%20%20%20%20%20%20%20%20%20replacement%3A%20replacement%20instanceof%20HTMLInputElement%20%3F%20replacement.value.trim()%20%3A%20%22%22%2C%0A%20%20%20%20%20%20%20%20%20%20mode%3A%20mode%20instanceof%20HTMLSelectElement%20%26%26%20mode.value%20%3D%3D%3D%20%22regex%22%20%3F%20%22regex%22%20%3A%20%22normal%22%2C%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%7D)%3B%0A%0A%20%20%20%20%20%20config.rules%20%3D%20nextRules.length%20%3F%20nextRules%20%3A%20cloneRules(DEFAULT_RULES)%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20const%20handleFetchMember%20%3D%20async%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(location.hostname%20!%3D%3D%20%22parks2.bandainamco-am.co.jp%22)%20%7B%0A%20%20%20%20%20%20%20%20updateStatusText(%22%E2%9A%A0%EF%B8%8F%20%E8%AF%B7%E5%85%88%E5%9C%A8%20Bandai%20Parks%20%E7%BD%91%E7%AB%99%EF%BC%88parks2.bandainamco-am.co.jp%EF%BC%89%E4%B8%8A%E4%BD%BF%E7%94%A8%E6%AD%A4%E5%8A%9F%E8%83%BD%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20updateStatusText(%22%E2%8F%B3%20%E6%AD%A3%E5%9C%A8%E8%8E%B7%E5%8F%96%E4%BC%9A%E5%91%98%E4%BF%A1%E6%81%AF%E2%80%A6%22)%3B%0A%20%20%20%20%20%20try%20%7B%0A%20%20%20%20%20%20%20%20const%20resp%20%3D%20await%20fetch(%22https%3A%2F%2Fparks2.bandainamco-am.co.jp%2Fmember_mypage.html%22%2C%20%7B%0A%20%20%20%20%20%20%20%20%20%20credentials%3A%20%22include%22%2C%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20if%20(!resp.ok)%20throw%20new%20Error(%60HTTP%20%24%7Bresp.status%7D%60)%3B%0A%20%20%20%20%20%20%20%20const%20html%20%3D%20await%20resp.text()%3B%0A%20%20%20%20%20%20%20%20const%20fields%20%3D%20extractMemberInfo(html)%3B%0A%20%20%20%20%20%20%20%20if%20(!fields.length)%20%7B%0A%20%20%20%20%20%20%20%20%20%20updateStatusText(%22%E2%9A%A0%EF%B8%8F%20%E6%9C%AA%E8%8E%B7%E5%8F%96%E5%88%B0%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%EF%BC%88%E5%8F%AF%E8%83%BD%E6%9C%AA%E7%99%BB%E5%BD%95%E6%88%96%E9%A1%B5%E9%9D%A2%E7%BB%93%E6%9E%84%E5%8F%98%E5%8C%96%EF%BC%89%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20readRules()%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20%E6%B8%85%E6%8E%89%E7%A9%BA%E8%A1%8C%EF%BC%8C%E9%81%BF%E5%85%8D%E5%92%8C%E6%8A%93%E5%8F%96%E5%88%B0%E7%9A%84%E4%BF%A1%E6%81%AF%E6%B7%B7%E5%9C%A8%E4%B8%80%E8%B5%B7%0A%20%20%20%20%20%20%20%20config.rules%20%3D%20config.rules.filter((r)%20%3D%3E%20r.original.trim()%20!%3D%3D%20%22%22)%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20%E6%AF%8F%E4%B8%AA%E5%AD%97%E6%AE%B5%E5%80%BC%E4%BD%9C%E4%B8%BA%E4%B8%80%E6%9D%A1%E8%A7%84%E5%88%99%E7%9A%84%E3%80%8C%E5%8E%9F%E6%96%87%E5%AD%97%E3%80%8D%EF%BC%8C%E6%96%B0%E6%96%87%E5%AD%97%E7%95%99%E7%A9%BA%E7%94%B1%E7%94%A8%E6%88%B7%E5%A1%AB%E5%86%99%0A%20%20%20%20%20%20%20%20fields.forEach((f)%20%3D%3E%20%7B%0A%20%20%20%20%20%20%20%20%20%20config.rules.push(%7B%20enabled%3A%20true%2C%20original%3A%20f.value%2C%20replacement%3A%20%22%22%2C%20mode%3A%20%22normal%22%20%7D)%3B%0A%20%20%20%20%20%20%20%20%7D)%3B%0A%20%20%20%20%20%20%20%20if%20(!config.rules.length)%20config.rules%20%3D%20cloneRules(DEFAULT_RULES)%3B%0A%20%20%20%20%20%20%20%20renderRules()%3B%0A%20%20%20%20%20%20%20%20updateStatusText(%60%E2%9C%85%20%E5%B7%B2%E8%8E%B7%E5%8F%96%20%24%7Bfields.length%7D%20%E9%A1%B9%E7%94%A8%E6%88%B7%E4%BF%A1%E6%81%AF%EF%BC%8C%E8%AF%B7%E5%9C%A8%E3%80%8C%E6%96%B0%E6%96%87%E5%AD%97%E3%80%8D%E4%B8%AD%E5%A1%AB%E5%86%99%E8%A6%81%E6%98%BE%E7%A4%BA%E7%9A%84%E5%86%85%E5%AE%B9%E3%80%82%60)%3B%0A%20%20%20%20%20%20%7D%20catch%20(e)%20%7B%0A%20%20%20%20%20%20%20%20updateStatusText(%22%E2%9A%A0%EF%B8%8F%20%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%3A%20%22%20%2B%20(e%20%26%26%20e.message%20%3F%20e.message%20%3A%20%22%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF%22))%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20panel.addEventListener(%22click%22%2C%20(event)%20%3D%3E%20%7B%0A%20%20%20%20%20%20const%20target%20%3D%20event.target%3B%0A%20%20%20%20%20%20if%20(!(target%20instanceof%20HTMLElement))%20return%3B%0A%0A%20%20%20%20%20%20const%20action%20%3D%20target.getAttribute(%22data-action%22)%3B%0A%20%20%20%20%20%20if%20(!action)%20return%3B%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22toggle-advanced%22)%20%7B%0A%20%20%20%20%20%20%20%20const%20isAdvanced%20%3D%20panel.classList.toggle(%22is-advanced%22)%3B%0A%20%20%20%20%20%20%20%20target.textContent%20%3D%20isAdvanced%20%3F%20%22%E2%86%A9%20%E8%BF%94%E5%9B%9E%E7%AE%80%E5%8D%95%E6%9B%BF%E6%8D%A2%22%20%3A%20%22%E2%9A%99%20%E9%AB%98%E7%BA%A7%E6%9B%BF%E6%8D%A2%EF%BC%88%E6%AD%A3%E5%88%99%EF%BC%89%22%3B%0A%20%20%20%20%20%20%20%20updateStatusText(isAdvanced%20%3F%20%22%E5%B7%B2%E5%BC%80%E5%90%AF%E9%AB%98%E7%BA%A7%E6%9B%BF%E6%8D%A2%EF%BC%8C%E6%94%AF%E6%8C%81%E6%AD%A3%E5%88%99%E5%8C%B9%E9%85%8D%E3%80%82%22%20%3A%20%22%E5%B7%B2%E5%85%B3%E9%97%AD%E9%AB%98%E7%BA%A7%E6%9B%BF%E6%8D%A2%EF%BC%8C%E4%BB%85%E6%99%AE%E9%80%9A%E5%8C%B9%E9%85%8D%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22toggle-body%22)%20%7B%0A%20%20%20%20%20%20%20%20togglePanelBody()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22hide-panel%22)%20%7B%0A%20%20%20%20%20%20%20%20hidePanel()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22fetch-member%22)%20%7B%0A%20%20%20%20%20%20%20%20handleFetchMember()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22add-rule%22)%20%7B%0A%20%20%20%20%20%20%20%20readRules()%3B%0A%20%20%20%20%20%20%20%20config.rules.push(%7B%20enabled%3A%20true%2C%20original%3A%20%22%22%2C%20replacement%3A%20%22%22%2C%20mode%3A%20%22normal%22%20%7D)%3B%0A%20%20%20%20%20%20%20%20renderRules()%3B%0A%20%20%20%20%20%20%20%20updateStatusText(%22%E5%B7%B2%E6%B7%BB%E5%8A%A0%E4%B8%80%E6%9D%A1%E8%A7%84%E5%88%99%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22remove-rule%22)%20%7B%0A%20%20%20%20%20%20%20%20const%20row%20%3D%20target.closest(%22.codex-rule-row%22)%3B%0A%20%20%20%20%20%20%20%20if%20(!row)%20return%3B%0A%20%20%20%20%20%20%20%20const%20index%20%3D%20Number(row.getAttribute(%22data-index%22))%3B%0A%20%20%20%20%20%20%20%20readRules()%3B%0A%20%20%20%20%20%20%20%20config.rules.splice(index%2C%201)%3B%0A%20%20%20%20%20%20%20%20if%20(!config.rules.length)%20%7B%0A%20%20%20%20%20%20%20%20%20%20config.rules%20%3D%20cloneRules(DEFAULT_RULES)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20renderRules()%3B%0A%20%20%20%20%20%20%20%20updateStatusText(%22%E8%A7%84%E5%88%99%E5%B7%B2%E5%88%A0%E9%99%A4%E3%80%82%22)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22save%22)%20%7B%0A%20%20%20%20%20%20%20%20readFields()%3B%0A%20%20%20%20%20%20%20%20readRules()%3B%0A%20%20%20%20%20%20%20%20config.enabled%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20startSessionNow()%3B%0A%20%20%20%20%20%20%20%20saveConfig()%3B%0A%20%20%20%20%20%20%20%20applyReplacements(%0A%20%20%20%20%20%20%20%20%20%20%60%E5%B7%B2%E5%BA%94%E7%94%A8%20%24%7Bconfig.rules.filter((rule)%20%3D%3E%20rule.enabled%20%26%26%20rule.original%20%26%26%20rule.replacement).length%7D%20%E6%9D%A1%E8%A7%84%E5%88%99%EF%BC%8C4%20%E5%B0%8F%E6%97%B6%E5%90%8E%E8%87%AA%E5%8A%A8%E5%A4%B1%E6%95%88%E3%80%82%60%0A%20%20%20%20%20%20%20%20)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22disable%22)%20%7B%0A%20%20%20%20%20%20%20%20disableReplacement()%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20if%20(action%20%3D%3D%3D%20%22restart-hour%22)%20%7B%0A%20%20%20%20%20%20%20%20readFields()%3B%0A%20%20%20%20%20%20%20%20readRules()%3B%0A%20%20%20%20%20%20%20%20config.enabled%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20startSessionNow()%3B%0A%20%20%20%20%20%20%20%20saveConfig()%3B%0A%20%20%20%20%20%20%20%20applyReplacements(%22%E5%B7%B2%E9%87%8D%E6%96%B0%E5%BC%80%E5%A7%8B%E8%AE%A1%E6%97%B6%EF%BC%8C4%20%E5%B0%8F%E6%97%B6%E5%90%8E%E8%87%AA%E5%8A%A8%E5%A4%B1%E6%95%88%E3%80%82%22)%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20syncFields()%3B%0A%20%20%20%20renderRules()%3B%0A%20%20%20%20document.body.appendChild(panel)%3B%0A%20%20%20%20syncPanelVisibility()%3B%0A%20%20%20%20updateStatusText(%22%E9%95%BF%E6%8C%89%E9%A1%B6%E9%83%A8%202%20%E7%A7%92%EF%BC%8C%E6%88%96%E5%BF%AB%E9%80%9F%E4%B8%89%E5%87%BB%E9%A1%B6%E9%83%A8%E5%8C%BA%E5%9F%9F%E5%8F%AF%E5%86%8D%E6%AC%A1%E6%89%93%E5%BC%80%E8%AE%BE%E7%BD%AE%E9%9D%A2%E6%9D%BF%E3%80%82%22)%3B%0A%20%20%7D%3B%0A%0A%20%20const%20boot%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20if%20(config.enabled%20%26%26%20hasExpired())%20%7B%0A%20%20%20%20%20%20config.enabled%20%3D%20false%3B%0A%20%20%20%20%20%20config.sessionStartedAt%20%3D%20null%3B%0A%20%20%20%20%20%20saveConfig()%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20config.panelVisible%20%3D%20false%3B%0A%0A%20%20%20%20startObserver()%3B%0A%20%20%20%20startTopHoldDetector()%3B%0A%20%20%20%20syncStatusLoop()%3B%0A%0A%20%20%20%20const%20mountUi%20%3D%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20if%20(!document.body)%20%7B%0A%20%20%20%20%20%20%20%20requestAnimationFrame(mountUi)%3B%0A%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20buildPanel()%3B%0A%20%20%20%20%20%20applyReplacements()%3B%0A%20%20%20%20%20%20startFastScanLoop()%3B%0A%20%20%20%20%7D%3B%0A%0A%20%20%20%20mountUi()%3B%0A%0A%20%20%20%20document.addEventListener(%22readystatechange%22%2C%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20applyReplacements()%3B%0A%20%20%20%20%7D)%3B%0A%0A%20%20%20%20window.addEventListener(%22load%22%2C%20()%20%3D%3E%20%7B%0A%20%20%20%20%20%20applyReplacements()%3B%0A%20%20%20%20%20%20startFastScanLoop()%3B%0A%20%20%20%20%7D)%3B%0A%20%20%7D%3B%0A%0A%20%20boot()%3B%0A%0A%20%20if%20(isReplacementActive())%20%7B%0A%20%20%20%20applyReplacements()%3B%0A%20%20%20%20showToast(%22%E2%9C%85%20%E5%B7%B2%E5%BA%94%E7%94%A8%E6%9B%BF%E6%8D%A2%22)%3B%0A%20%20%7D%20else%20%7B%0A%20%20%20%20showPanel()%3B%0A%20%20%7D%0A%7D)()%3B \ No newline at end of file diff --git a/fixed-site-replacer-main/code-v0.6.1.user.js b/fixed-site-replacer-main/code-v0.6.1.user.js new file mode 100644 index 0000000..d0474f6 --- /dev/null +++ b/fixed-site-replacer-main/code-v0.6.1.user.js @@ -0,0 +1,1244 @@ +// ==UserScript== +// @name Fixed Site Name Replacer2 +// @namespace local.codex.fixed-site-replacer +// @version 0.6.1 +// @description 本地替换 Microsoft 登录页、Outlook 和 Bandai Parks 页面上的显示文字。(iOS Safari Userscripts / 油猴双兼容;触发:长按 2 秒或三击顶部区域) +// @match https://login.microsoftonline.com/* +// @match https://outlook.live.com/* +// @match https://parks2.bandainamco-am.co.jp/* +// @grant none +// @run-at document-start +// ==/UserScript== + +(() => { + "use strict"; + + const STORAGE_KEY = "codex.fixedSite.nameReplacer.config.v1"; + const SUPPORTED_HOSTS = [ + "login.microsoftonline.com", + "outlook.live.com", + "parks2.bandainamco-am.co.jp", + ]; + const PANEL_ID = "codex-msmail-name-panel"; + const STYLE_ID = "codex-msmail-name-style"; + const SESSION_DURATION_MS = 4 * 60 * 60 * 1000; + const PANEL_HOLD_MS = 2000; + const PANEL_HOLD_ZONE_PX = 100; + const TRIPLE_CLICK_WINDOW_MS = 500; + const TRIPLE_CLICK_MAX_SPREAD = 40; + const FAST_SCAN_WINDOW_MS = 4000; + const FAST_SCAN_INTERVAL_MS = 120; + const DEFAULT_RULES = [ + { enabled: true, original: "", replacement: "", mode: "normal" }, + { enabled: true, original: "", replacement: "", mode: "normal" }, + ]; + + let originalTextMap = new WeakMap(); + let originalValueMap = new WeakMap(); + const touchedTextNodes = new Set(); + const touchedElements = new Set(); + let observer = null; + let applying = false; + let statusUpdater = null; + let holdTimer = null; + let topHoldStartY = 0; + let threeFingerHold = false; + let tripleClickTimes = []; + let fastScanInterval = null; + let fastScanStopTimer = null; + + const defaultConfig = { + enabled: false, + fontAdjust: false, + panelVisible: false, + bodyCollapsed: false, + sessionStartedAt: null, + rules: DEFAULT_RULES, + }; + + const cloneRules = (rules) => + (Array.isArray(rules) ? rules : DEFAULT_RULES).map((rule) => ({ + enabled: rule.enabled !== false, + original: String(rule.original || ""), + replacement: String(rule.replacement || ""), + mode: rule.mode === "regex" ? "regex" : "normal", + })); + + const readStoredConfig = () => { + if (typeof GM_getValue === "function") { + return GM_getValue(STORAGE_KEY, "{}"); + } + return localStorage.getItem(STORAGE_KEY) || "{}"; + }; + + const writeStoredConfig = (value) => { + if (typeof GM_setValue === "function") { + GM_setValue(STORAGE_KEY, value); + return; + } + localStorage.setItem(STORAGE_KEY, value); + }; + + const loadConfig = () => { + try { + const saved = JSON.parse(readStoredConfig()); + return { + ...defaultConfig, + ...saved, + panelVisible: false, + sessionStartedAt: + typeof saved.sessionStartedAt === "number" && saved.sessionStartedAt > 0 + ? saved.sessionStartedAt + : null, + rules: cloneRules(saved.rules), + }; + } catch { + return { + ...defaultConfig, + rules: cloneRules(defaultConfig.rules), + }; + } + }; + + let config = loadConfig(); + config.panelVisible = false; + + const saveConfig = () => { + writeStoredConfig( + JSON.stringify({ + ...config, + rules: cloneRules(config.rules), + }) + ); + }; + + const normalize = (value) => value.replace(/\s+/g, " ").trim(); + + const siteAuthorized = () => { + return SUPPORTED_HOSTS.includes(location.hostname); + }; + + const isOutlookPage = () => location.hostname === "outlook.live.com"; + + const shouldSkipNode = (node) => { + const element = + node instanceof Element ? node : node?.parentElement instanceof Element ? node.parentElement : null; + + if (!element) return false; + if (element.closest(`#${PANEL_ID}`)) return true; + if (["SCRIPT", "STYLE", "NOSCRIPT"].includes(element.tagName)) return true; + return Boolean(element.closest('iframe, [contenteditable="true"]')); + }; + + const getReplacementRoots = () => { + if (!document.body) return []; + return [document.body]; + }; + + const hasValidSession = () => typeof config.sessionStartedAt === "number" && config.sessionStartedAt > 0; + + const getSessionExpiresAt = () => + hasValidSession() ? config.sessionStartedAt + SESSION_DURATION_MS : 0; + + const getRemainingMs = () => + hasValidSession() ? Math.max(0, getSessionExpiresAt() - Date.now()) : 0; + + const formatRemaining = (ms) => { + const totalSeconds = Math.ceil(ms / 1000); + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + }; + + const hasExpired = () => hasValidSession() && getRemainingMs() <= 0; + + const isReplacementActive = () => + config.enabled && siteAuthorized() && hasValidSession() && !hasExpired(); + + const replaceByRule = (input, rule) => { + if (!rule.enabled || !rule.original || !rule.replacement) { + return input; + } + + if (rule.mode === "regex") { + try { + return input.replace(new RegExp(rule.original, "g"), rule.replacement); + } catch { + return input; + } + } + + return input.split(rule.original).join(rule.replacement); + }; + + const replaceText = (text) => { + if (!isReplacementActive()) return text; + return config.rules.reduce((next, rule) => replaceByRule(next, rule), text); + }; + + const rememberOriginalText = (node, value) => { + touchedTextNodes.add(node); + if (!originalTextMap.has(node)) { + originalTextMap.set(node, value); + } + }; + + const rememberOriginalValue = (element, key, value) => { + touchedElements.add(element); + let item = originalValueMap.get(element); + if (!item) { + item = {}; + originalValueMap.set(element, item); + } + if (!(key in item)) { + item[key] = value; + } + }; + + const restoreTouchedContent = () => { + touchedTextNodes.forEach((node) => { + if (node.isConnected) { + restoreNode(node); + } + }); + + touchedElements.forEach((element) => { + if (element.isConnected) { + restoreElement(element); + } + }); + + touchedTextNodes.clear(); + touchedElements.clear(); + originalTextMap = new WeakMap(); + originalValueMap = new WeakMap(); + }; + + const restoreNode = (node) => { + if (!originalTextMap.has(node)) return; + + const original = originalTextMap.get(node); + if (node.nodeValue !== original) { + node.nodeValue = original; + } + }; + + const restoreElement = (element) => { + const item = originalValueMap.get(element); + if (!item) return; + + if ("value" in item && typeof element.value === "string" && element.value !== item.value) { + element.value = item.value; + } + + if ( + "placeholder" in item && + typeof element.placeholder === "string" && + element.placeholder !== item.placeholder + ) { + element.placeholder = item.placeholder; + } + }; + + const updateTextNode = (node) => { + const current = node.nodeValue; + if (!current || !normalize(current)) return; + + if (!isReplacementActive()) return; + + rememberOriginalText(node, current); + const base = originalTextMap.get(node) || current; + const next = replaceText(base); + if (next !== current) { + node.nodeValue = next; + } + }; + + const updateElementValue = (element) => { + if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) { + return; + } + + if (shouldSkipNode(element)) return; + + if (!isReplacementActive()) return; + + if (typeof element.placeholder === "string") { + rememberOriginalValue(element, "placeholder", element.placeholder); + const nextPlaceholder = replaceText( + originalValueMap.get(element)?.placeholder || element.placeholder + ); + if (nextPlaceholder !== element.placeholder) { + element.placeholder = nextPlaceholder; + } + } + }; + + const walkAndReplace = (root) => { + if (!root) return; + + const walker = document.createTreeWalker( + root, + NodeFilter.SHOW_TEXT, + { + acceptNode(node) { + if (!node.parentElement) return NodeFilter.FILTER_REJECT; + if (shouldSkipNode(node)) return NodeFilter.FILTER_REJECT; + return NodeFilter.FILTER_ACCEPT; + }, + } + ); + + let textNode = walker.nextNode(); + while (textNode) { + updateTextNode(textNode); + textNode = walker.nextNode(); + } + + if (root instanceof Element) { + updateElementValue(root); + root.querySelectorAll("input, textarea").forEach(updateElementValue); + } + }; + + const applyFontAdjust = () => { + const active = Boolean(config.fontAdjust && isReplacementActive()); + document.documentElement.classList.toggle("codex-msmail-font-adjust", active); + }; + + const updateStatusText = (message) => { + const statusNode = document.querySelector(`#${PANEL_ID} [data-role="status"]`); + if (!statusNode) return; + + if (message) { + statusNode.textContent = message; + return; + } + + if (!config.enabled) { + statusNode.textContent = "替换功能已关闭,页面正常显示。"; + return; + } + + if (!hasValidSession()) { + statusNode.textContent = "尚未开始计时,点击保存并应用后开始 4 小时倒计时。"; + return; + } + + if (hasExpired()) { + statusNode.textContent = "已超过 4 小时,替换功能自动失效。"; + return; + } + + statusNode.textContent = `替换功能开启中,剩余时间: ${formatRemaining(getRemainingMs())}`; + }; + + const syncStatusLoop = () => { + if (statusUpdater) { + clearInterval(statusUpdater); + } + + statusUpdater = window.setInterval(() => { + if (config.enabled && hasExpired()) { + disableReplacement("已超过 4 小时,替换功能自动失效。"); + return; + } + updateStatusText(); + }, 1000); + }; + + const applyReplacements = (statusMessage = "") => { + if (applying) return; + applying = true; + + try { + applyFontAdjust(); + if (isReplacementActive()) { + getReplacementRoots().forEach(walkAndReplace); + } + updateStatusText(statusMessage); + } finally { + applying = false; + } + }; + + const stopFastScanLoop = () => { + if (fastScanInterval) { + clearInterval(fastScanInterval); + fastScanInterval = null; + } + if (fastScanStopTimer) { + clearTimeout(fastScanStopTimer); + fastScanStopTimer = null; + } + }; + + const startFastScanLoop = () => { + stopFastScanLoop(); + + const tick = () => { + if (document.body) { + applyReplacements(); + } + }; + + tick(); + fastScanInterval = window.setInterval(tick, FAST_SCAN_INTERVAL_MS); + fastScanStopTimer = window.setTimeout(() => { + stopFastScanLoop(); + }, FAST_SCAN_WINDOW_MS); + }; + + const startObserver = () => { + if (observer) observer.disconnect(); + + observer = new MutationObserver((mutations) => { + if (applying) return; + + for (const mutation of mutations) { + if (mutation.type === "characterData") { + updateTextNode(mutation.target); + continue; + } + + mutation.addedNodes.forEach((node) => { + if (node.nodeType === Node.TEXT_NODE) { + updateTextNode(node); + } else if (node.nodeType === Node.ELEMENT_NODE) { + walkAndReplace(node); + } + }); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true, + characterData: true, + }); + }; + + const ensureStyles = () => { + if (document.getElementById(STYLE_ID)) return; + + const style = document.createElement("style"); + style.id = STYLE_ID; + style.textContent = ` + .codex-msmail-font-adjust body, + .codex-msmail-font-adjust input, + .codex-msmail-font-adjust button, + .codex-msmail-font-adjust textarea, + .codex-msmail-font-adjust select { + letter-spacing: 0.02em !important; + } + + #${PANEL_ID} { + position: fixed; + top: max(12px, env(safe-area-inset-top)); + left: 12px; + right: 12px; + z-index: 2147483647; + background: rgba(247, 244, 237, 0.98); + color: #2e2a26; + border: 1px solid rgba(60, 49, 38, 0.16); + border-radius: 10px; + box-shadow: 0 18px 40px rgba(27, 22, 18, 0.18); + padding: 12px; + font: 13px/1.35 -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + max-height: calc(100vh - max(24px, env(safe-area-inset-top)) - max(24px, env(safe-area-inset-bottom))); + overflow: hidden; + } + + #${PANEL_ID}[hidden] { + display: none !important; + } + + #${PANEL_ID}.is-collapsed .codex-body { + display: none; + } + + #${PANEL_ID} .codex-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + } + + #${PANEL_ID} .codex-header-main { + min-width: 0; + } + + #${PANEL_ID} .codex-title { + font-size: 18px; + font-weight: 700; + border: 0; + background: transparent; + color: #2e2a26; + padding: 0; + text-align: left; + } + + #${PANEL_ID} .codex-close { + border: 0; + background: transparent; + color: #4b433b; + font-size: 14px; + padding: 2px 4px; + } + + #${PANEL_ID} .codex-meta { + margin-top: 4px; + color: #6d6256; + display: grid; + gap: 2px; + word-break: break-all; + } + + #${PANEL_ID} .codex-body { + margin-top: 12px; + display: grid; + gap: 10px; + max-height: calc(100vh - 180px); + overflow-y: auto; + overflow-x: hidden; + padding-right: 2px; + -webkit-overflow-scrolling: touch; + } + + #${PANEL_ID} button, + #${PANEL_ID} input, + #${PANEL_ID} textarea, + #${PANEL_ID} select { + font: inherit; + } + + #${PANEL_ID} .codex-action-row { + display: flex; + gap: 8px; + flex-wrap: wrap; + } + + #${PANEL_ID} .codex-btn { + border: 0; + border-radius: 999px; + padding: 8px 12px; + background: #ddd4c7; + color: #352f29; + } + + #${PANEL_ID} .codex-btn.primary { + background: #c6b091; + color: #201915; + } + + #${PANEL_ID} .codex-input { + width: 100%; + box-sizing: border-box; + border: 1px solid #d5c8b8; + border-radius: 8px; + padding: 9px 10px; + background: rgba(255, 255, 255, 0.9); + color: #2f2924; + } + + #${PANEL_ID} .codex-check-row { + display: flex; + gap: 12px; + flex-wrap: wrap; + color: #433b34; + } + + #${PANEL_ID} .codex-check { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.68); + } + + #${PANEL_ID} .codex-table { + display: grid; + gap: 8px; + } + + #${PANEL_ID} .codex-rules-scroll { + overflow-x: auto; + overflow-y: visible; + -webkit-overflow-scrolling: touch; + padding-bottom: 4px; + } + + #${PANEL_ID} .codex-rule-guide { + display: grid; + gap: 6px; + padding: 10px 12px; + border-radius: 8px; + background: rgba(255, 255, 255, 0.62); + color: #5c5248; + } + + #${PANEL_ID} .codex-guide-strong { + font-weight: 700; + color: #2f2924; + } + + #${PANEL_ID} .codex-table-head, + #${PANEL_ID} .codex-rule-row { + display: grid; + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 58px; + gap: 8px; + align-items: center; + } + + #${PANEL_ID}.is-advanced .codex-table-head, + #${PANEL_ID}.is-advanced .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 112px 58px; + } + + #${PANEL_ID} .codex-col-mode, + #${PANEL_ID} .codex-mode-block { + display: none; + } + + #${PANEL_ID}.is-advanced .codex-col-mode, + #${PANEL_ID}.is-advanced .codex-mode-block { + display: block; + } + + #${PANEL_ID} .codex-table-head { + color: #5d5348; + font-weight: 600; + } + + #${PANEL_ID} .codex-rule-row { + background: rgba(255, 255, 255, 0.48); + border-radius: 14px; + padding: 12px; + border: 1px solid rgba(190, 176, 157, 0.5); + } + + #${PANEL_ID} .codex-field-block { + display: grid; + gap: 4px; + min-width: 0; + } + + #${PANEL_ID} .codex-field-label { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.01em; + } + + #${PANEL_ID} .codex-field-label.original { + color: #8a5a26; + } + + #${PANEL_ID} .codex-field-label.replacement { + color: #1f6b45; + } + + #${PANEL_ID} .codex-rule-row .codex-input.original { + border-color: #d9bf9a; + background: rgba(255, 248, 240, 0.95); + } + + #${PANEL_ID} .codex-rule-row .codex-input.replacement { + border-color: #9fc7ae; + background: rgba(244, 255, 248, 0.95); + } + + #${PANEL_ID} .codex-small-btn { + border: 0; + border-radius: 8px; + background: #e4d9cb; + color: #443b32; + padding: 11px 8px; + } + + #${PANEL_ID} .codex-status { + color: #75685a; + font-size: 12px; + } + + #${PANEL_ID} .codex-rule-toggle { + display: flex; + align-items: center; + justify-content: center; + } + + #${PANEL_ID} .codex-rule-toggle input { + width: 22px; + height: 22px; + } + + #${PANEL_ID} .codex-mode-block { + display: grid; + gap: 4px; + } + + #${PANEL_ID} .codex-mode-label { + font-size: 11px; + font-weight: 700; + color: #555048; + } + + @media (max-width: 520px) { + #${PANEL_ID} .codex-table-head, + #${PANEL_ID} .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 58px; + } + + #${PANEL_ID}.is-advanced .codex-table-head, + #${PANEL_ID}.is-advanced .codex-rule-row { + grid-template-columns: 40px minmax(0, 1fr) minmax(0, 1fr) 104px 52px; + } + + #${PANEL_ID} .codex-rule-row { + gap: 10px; + } + } + `; + + document.head.appendChild(style); + }; + + const escapeHtml = (value) => + value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); + + const buildMetaText = () => [ + `当前网站: ${location.hostname}`, + ]; + + // 从会员页面 HTML 中提取用户信息字段(通用解析器) + const extractMemberInfo = (html) => { + const doc = new DOMParser().parseFromString(html, "text/html"); + const fields = []; + const push = (label, value) => { + const v = String(value || "").replace(/\s+/g, " ").trim(); + if (v && v.length <= 80 && !fields.some((f) => f.value === v)) { + fields.push({ label: String(label || "").replace(/\s+/g, " ").trim(), value: v }); + } + }; + + // 1) dl > dt + dd 结构 + doc.querySelectorAll("dl").forEach((dl) => { + const dt = dl.querySelector("dt"); + const dd = dl.querySelector("dd"); + if (dt && dd) push(dt.textContent, dd.textContent); + }); + + // 2) table 中 th + td 结构 + doc.querySelectorAll("tr").forEach((tr) => { + const th = tr.querySelector("th"); + const td = tr.querySelector("td"); + if (th && td) push(th.textContent, td.textContent); + }); + + // 3) 输入框 value(文本类) + doc.querySelectorAll("input").forEach((inp) => { + const t = (inp.type || "text").toLowerCase(); + if (["text", "email", "tel", "search"].includes(t) && inp.value) { + const label = inp.getAttribute("aria-label") || inp.getAttribute("title") || inp.name || "入力値"; + push(label, inp.value); + } + }); + + // 4) 常见日文会员字段名:找到标签元素,取其相邻值 + const knownLabels = ["会員番号", "会員No", "会員ID", "氏名", "お名前", "フリガナ", "ニックネーム", "メールアドレス", "電話番号", "生年月日", "性別", "住所"]; + const labelCandidates = doc.querySelectorAll("div, span, p, label, th, dt"); + knownLabels.forEach((lb) => { + const el = Array.from(labelCandidates).find((e) => e.textContent.replace(/\s+/g, "").trim() === lb); + if (!el) return; + const sibling = el.nextElementSibling; + if (sibling) push(lb, sibling.textContent); + }); + + // 5) 页面内嵌脚本变量 member_data(会員番号 / 生年月日 / 性別) + const memberDataMatch = html.match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})(?:\s*;)?\s*(?:<\/script>|$)/i); + if (memberDataMatch) { + try { + const data = JSON.parse(memberDataMatch[1]); + if (data.member_id) push("会員番号", data.member_id); + if (data.birth) push("生年月日", data.birth); + if (data.sex) push("性別", data.sex); + } catch (e) { + // 忽略解析失败 + } + } + + // 6) ポイント(現在のポイント:Nポイント,值前后可能夹着标签) + const pointMatch = html.match(/現在のポイント[::][^0-9]*([\d,]+)[^0-9]*ポイント/); + if (pointMatch) push("現在のポイント", pointMatch[1]); + + // 7) 页面标题 h1「XXX さんのマイページ」→ 昵称 + const titleMatch = html.match(/]*>([^<]*?)\s*さんのマイページ<\/h1>/); + if (titleMatch && titleMatch[1]) push("ニックネーム(页面标题)", titleMatch[1]); + + return fields; + }; + + const createRuleRowHtml = (rule, index) => ` +
    +
    + +
    +
    +
    网页当前显示的原文字
    + +
    +
    +
    你想显示的新文字
    + +
    +
    +
    模式
    + +
    + +
    + `; + + const getPanel = () => document.getElementById(PANEL_ID); + + const syncPanelVisibility = () => { + const panel = getPanel(); + if (!panel) return; + panel.hidden = !config.panelVisible; + panel.classList.toggle("is-collapsed", Boolean(config.bodyCollapsed)); + }; + + const showPanel = () => { + config.panelVisible = true; + saveConfig(); + syncPanelVisibility(); + updateStatusText("设置面板已打开。"); + }; + + const hidePanel = () => { + config.panelVisible = false; + saveConfig(); + syncPanelVisibility(); + }; + + const togglePanelBody = () => { + config.bodyCollapsed = !config.bodyCollapsed; + saveConfig(); + syncPanelVisibility(); + }; + + const startSessionNow = () => { + config.sessionStartedAt = Date.now(); + }; + + const disableReplacement = (message = "替换功能已关闭,页面正常显示。") => { + config.enabled = false; + config.sessionStartedAt = null; + saveConfig(); + hidePanel(); + }; + + const clearHoldTimer = () => { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; + } + threeFingerHold = false; + }; + + const clearSelection = () => { + const sel = window.getSelection && window.getSelection(); + if (sel && typeof sel.removeAllRanges === "function") { + sel.removeAllRanges(); + } + }; + + const recordTripleClick = (clientY) => { + if (config.panelVisible) return; + if (clientY > PANEL_HOLD_ZONE_PX) return; + + const now = Date.now(); + tripleClickTimes = tripleClickTimes.filter( + (t) => now - t.time <= TRIPLE_CLICK_WINDOW_MS + ); + tripleClickTimes.push({ time: now, y: clientY }); + + if (tripleClickTimes.length < 3) return; + + const ys = tripleClickTimes.map((t) => t.y); + const spread = Math.max(...ys) - Math.min(...ys); + tripleClickTimes = []; + + if (spread <= TRIPLE_CLICK_MAX_SPREAD) { + showPanel(); + setTimeout(clearSelection, 0); + } + }; + + const startTopHoldDetector = () => { + const beginHold = (clientY, isThreeFinger = false) => { + if (config.panelVisible) return; + if (!isThreeFinger && clientY > PANEL_HOLD_ZONE_PX) return; + + clearHoldTimer(); + topHoldStartY = clientY; + threeFingerHold = isThreeFinger; + holdTimer = window.setTimeout(() => { + holdTimer = null; + threeFingerHold = false; + showPanel(); + }, PANEL_HOLD_MS); + }; + + const moveHold = (clientY, touchesLength = 1) => { + if (!holdTimer) return; + if (threeFingerHold && touchesLength !== 3) { + clearHoldTimer(); + return; + } + if ( + Math.abs(clientY - topHoldStartY) > 14 || + (!threeFingerHold && clientY > PANEL_HOLD_ZONE_PX + 20) + ) { + clearHoldTimer(); + } + }; + + document.addEventListener( + "touchstart", + (event) => { + if (event.touches.length === 1) { + beginHold(event.touches[0].clientY, false); + return; + } + + if (event.touches.length === 3) { + beginHold(event.touches[0].clientY, true); + return; + } + + clearHoldTimer(); + }, + { passive: true } + ); + + document.addEventListener( + "touchmove", + (event) => { + if (event.touches.length < 1) { + clearHoldTimer(); + return; + } + moveHold(event.touches[0].clientY, event.touches.length); + }, + { passive: true } + ); + + document.addEventListener("touchend", clearHoldTimer, { passive: true }); + document.addEventListener("touchcancel", clearHoldTimer, { passive: true }); + + document.addEventListener("mousedown", (event) => { + beginHold(event.clientY); + // 顶部区域内、且已有近期点击(正在形成三击的后续点击)→ 阻止浏览器选中文本 + const isInZone = event.clientY <= PANEL_HOLD_ZONE_PX; + const lastTap = + tripleClickTimes.length > 0 + ? tripleClickTimes[tripleClickTimes.length - 1] + : null; + const isChainTap = + lastTap !== null && + Date.now() - lastTap.time <= TRIPLE_CLICK_WINDOW_MS; + if (isInZone && isChainTap && event.defaultPrevented === false) { + event.preventDefault(); + } + }); + + document.addEventListener("mousemove", (event) => { + moveHold(event.clientY); + }); + + document.addEventListener("mouseup", (event) => { + clearHoldTimer(); + recordTripleClick(event.clientY); + }); + document.addEventListener("mouseleave", clearHoldTimer); + }; + + const buildPanel = () => { + if (document.getElementById(PANEL_ID)) return; + + ensureStyles(); + + const panel = document.createElement("section"); + panel.id = PANEL_ID; + + panel.innerHTML = ` +
    +
    + +
    ${buildMetaText() + .map((line) => `
    ${escapeHtml(line)}
    `) + .join("")}
    +
    + +
    +
    +
    + + +
    +
    + + + + + +
    +
    +
    +
    +
    左边填网页当前显示的内容,右边填你想显示的新内容。
    +
    规则会替换页面里匹配到的可见文字;登录框里手动输入的内容不会被修改。
    +
    示例: 原文字 user@example.com -> 新文字 alias@example.com
    +
    + +
    +
    +
    +
    原文字
    +
    新文字
    +
    模式
    +
    +
    +
    +
    +
    +
    + `; + + const rulesContainer = panel.querySelector(".codex-rules"); + const advancedToggleBtn = panel.querySelector('[data-action="toggle-advanced"]'); + + const syncAdvancedMode = () => { + const hasRegex = (config.rules || []).some((r) => r.mode === "regex"); + panel.classList.toggle("is-advanced", hasRegex); + if (advancedToggleBtn) { + advancedToggleBtn.textContent = hasRegex ? "↩ 返回简单替换" : "⚙ 高级替换(正则)"; + } + }; + + const renderRules = () => { + if (!rulesContainer) return; + rulesContainer.innerHTML = config.rules.map((rule, index) => createRuleRowHtml(rule, index)).join(""); + syncAdvancedMode(); + }; + + const syncFields = () => { + panel.querySelectorAll("[data-field]").forEach((input) => { + const field = input.getAttribute("data-field"); + if (!field) return; + + if (input instanceof HTMLInputElement && input.type === "checkbox") { + input.checked = Boolean(config[field]); + } else if (input instanceof HTMLInputElement) { + input.value = String(config[field] || ""); + } + }); + }; + + const readFields = () => { + panel.querySelectorAll("[data-field]").forEach((input) => { + const field = input.getAttribute("data-field"); + if (!field) return; + + if (input instanceof HTMLInputElement && input.type === "checkbox") { + config[field] = input.checked; + } else if (input instanceof HTMLInputElement) { + config[field] = input.value.trim(); + } + }); + }; + + const readRules = () => { + const nextRules = []; + panel.querySelectorAll(".codex-rule-row").forEach((row) => { + const enabled = row.querySelector('[data-rule-field="enabled"]'); + const original = row.querySelector('[data-rule-field="original"]'); + const replacement = row.querySelector('[data-rule-field="replacement"]'); + const mode = row.querySelector('[data-rule-field="mode"]'); + + nextRules.push({ + enabled: enabled instanceof HTMLInputElement ? enabled.checked : true, + original: original instanceof HTMLInputElement ? original.value.trim() : "", + replacement: replacement instanceof HTMLInputElement ? replacement.value.trim() : "", + mode: mode instanceof HTMLSelectElement && mode.value === "regex" ? "regex" : "normal", + }); + }); + + config.rules = nextRules.length ? nextRules : cloneRules(DEFAULT_RULES); + }; + + const handleFetchMember = async () => { + if (location.hostname !== "parks2.bandainamco-am.co.jp") { + updateStatusText("⚠️ 请先在 Bandai Parks 网站(parks2.bandainamco-am.co.jp)上使用此功能。"); + return; + } + updateStatusText("⏳ 正在获取会员信息…"); + try { + const resp = await fetch("https://parks2.bandainamco-am.co.jp/member_mypage.html", { + credentials: "include", + }); + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); + const html = await resp.text(); + const fields = extractMemberInfo(html); + if (!fields.length) { + updateStatusText("⚠️ 未获取到用户信息(可能未登录或页面结构变化)。"); + return; + } + readRules(); + // 清掉空行,避免和抓取到的信息混在一起 + config.rules = config.rules.filter((r) => r.original.trim() !== ""); + // 每个字段值作为一条规则的「原文字」,新文字留空由用户填写 + fields.forEach((f) => { + config.rules.push({ enabled: true, original: f.value, replacement: "", mode: "normal" }); + }); + if (!config.rules.length) config.rules = cloneRules(DEFAULT_RULES); + renderRules(); + updateStatusText(`✅ 已获取 ${fields.length} 项用户信息,请在「新文字」中填写要显示的内容。`); + } catch (e) { + updateStatusText("⚠️ 获取失败: " + (e && e.message ? e.message : "未知错误")); + } + }; + + panel.addEventListener("click", (event) => { + const target = event.target; + if (!(target instanceof HTMLElement)) return; + + const action = target.getAttribute("data-action"); + if (!action) return; + + if (action === "toggle-advanced") { + const isAdvanced = panel.classList.toggle("is-advanced"); + target.textContent = isAdvanced ? "↩ 返回简单替换" : "⚙ 高级替换(正则)"; + updateStatusText(isAdvanced ? "已开启高级替换,支持正则匹配。" : "已关闭高级替换,仅普通匹配。"); + return; + } + + if (action === "toggle-body") { + togglePanelBody(); + return; + } + + if (action === "hide-panel") { + hidePanel(); + return; + } + + if (action === "fetch-member") { + handleFetchMember(); + return; + } + + if (action === "add-rule") { + readRules(); + config.rules.push({ enabled: true, original: "", replacement: "", mode: "normal" }); + renderRules(); + updateStatusText("已添加一条规则。"); + return; + } + + if (action === "remove-rule") { + const row = target.closest(".codex-rule-row"); + if (!row) return; + const index = Number(row.getAttribute("data-index")); + readRules(); + config.rules.splice(index, 1); + if (!config.rules.length) { + config.rules = cloneRules(DEFAULT_RULES); + } + renderRules(); + updateStatusText("规则已删除。"); + return; + } + + if (action === "save") { + readFields(); + readRules(); + config.enabled = true; + startSessionNow(); + saveConfig(); + applyReplacements( + `已应用 ${config.rules.filter((rule) => rule.enabled && rule.original && rule.replacement).length} 条规则,4 小时后自动失效。` + ); + return; + } + + if (action === "disable") { + disableReplacement(); + return; + } + + if (action === "restart-hour") { + readFields(); + readRules(); + config.enabled = true; + startSessionNow(); + saveConfig(); + applyReplacements("已重新开始计时,4 小时后自动失效。"); + } + }); + + syncFields(); + renderRules(); + document.body.appendChild(panel); + syncPanelVisibility(); + updateStatusText("长按顶部 2 秒,或快速三击顶部区域可再次打开设置面板。"); + }; + + const boot = () => { + if (config.enabled && hasExpired()) { + config.enabled = false; + config.sessionStartedAt = null; + saveConfig(); + } + + config.panelVisible = false; + + startObserver(); + startTopHoldDetector(); + syncStatusLoop(); + + const mountUi = () => { + if (!document.body) { + requestAnimationFrame(mountUi); + return; + } + buildPanel(); + applyReplacements(); + startFastScanLoop(); + }; + + mountUi(); + + document.addEventListener("readystatechange", () => { + applyReplacements(); + }); + + window.addEventListener("load", () => { + applyReplacements(); + startFastScanLoop(); + }); + }; + + boot(); +})(); + diff --git a/fixed-site-replacer-main/code-v1.6.1.user.js b/fixed-site-replacer-main/code-v1.6.1.user.js new file mode 100644 index 0000000..3c9057b --- /dev/null +++ b/fixed-site-replacer-main/code-v1.6.1.user.js @@ -0,0 +1,1166 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息 *(通用版本) +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.0 +// @description 改会员资料姓名/生日/性别;可隐藏按钮与券面强制显示;支持 Excel 复制快速填充 +// @grant unsafeWindow +// @author park-tools +// @match https://parks2.bandainamco-am.co.jp/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_deleteValue +// ==/UserScript== + +(function () { + 'use strict'; + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + const LS_HIDE_UI = 'namco_hide_plugin_ui_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function getHideUi() { + const saved = store.get(LS_HIDE_UI, null); + if (saved != null) return saved; + return { hidden: false }; + } + + function setHideUi(cfg) { + store.set(LS_HIDE_UI, cfg); + } + + function shouldHidePluginUi() { + const cfg = getHideUi(); + return !!(cfg && cfg.hidden); + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})[-/\.](\d{1,2})[-/\.](\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = changes.gender || profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + SEX: sex, + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + SEX: sex, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input, .np-row select, .np-row textarea{width:100%;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;background:#fff;} +.np-row input, .np-row select{height:42px;} +.np-row textarea{padding:8px 12px;font-size:14px;resize:vertical;} +.np-row input:focus, .np-row select:focus, .np-row textarea:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名、生日与性别,无 SMS(手机号不变)。

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日/性别)。官网编辑页生日/性别虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    + 隐藏插件按钮 + +
    +

    隐藏后连点屏幕右下角两次可再打开设置

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + if (shouldHidePluginUi()) { + overlayBadge.style.display = 'none'; + return; + } + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = shouldHidePluginUi() ? 'none' : ''; + refreshOverlayBadge(); + } + + function loadHideUiToUI() { + $('#npHideUi', root).checked = shouldHidePluginUi(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + loadHideUiToUI(); + refreshPluginUiVisibility(); + } + + function clearAllInputs() { + $('#npPaste', root).value = ''; + $('#npL', root).value = ''; + $('#npF', root).value = ''; + $('#npLk', root).value = ''; + $('#npFk', root).value = ''; + $('#npBirthday', root).value = ''; + $('#npGender', root).value = 'M'; + $('#npPwd', root).value = ''; + $('#npOverlayName', root).value = ''; + } + + function openPanel() { + mask.style.display = 'block'; + panel.classList.add('open'); + + clearAllInputs(); + + loadOverlayToUI(); + loadHideUiToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + $('#npHideUi', root).addEventListener('change', () => { + setHideUi({ hidden: $('#npHideUi', root).checked }); + refreshPluginUiVisibility(); + }); + + (function setupSecretOpen() { + let lastTap = 0; + function hitCorner(x, y) { + const margin = 72; + return x >= window.innerWidth - margin && y >= window.innerHeight - margin; + } + function onCornerTap(clientX, clientY) { + if (!shouldHidePluginUi()) return; + if (panel.classList.contains('open')) return; + if (!hitCorner(clientX, clientY)) return; + const now = Date.now(); + if (now - lastTap < 450) { + lastTap = 0; + openPanel(); + } else { + lastTap = now; + } + } + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + if (t) onCornerTap(t.clientX, t.clientY); + }, + { passive: true } + ); + document.addEventListener('click', (e) => { + if (e.target.closest('#npRenameRoot')) return; + onCornerTap(e.clientX, e.clientY); + }); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在下方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // 快速填充逻辑:解析从 Excel 复制的整行内容(已补全平假名/片假名支持) + $('#npQuickFill', root).addEventListener('click', () => { + const rawText = $('#npPaste', root).value.trim(); + if (!rawText) { + log('请先粘贴 Excel 行数据到快速录入框'); + return; + } + + // 1. 优先按 Tab 制表符(Excel 复制的默认分隔符)或 2 个以上空格拆分 + const cols = rawText.split(/\t+|\s{2,}/).map(c => c.trim()).filter(Boolean); + const tokens = cols.length > 1 ? cols : rawText.split(/\s+/).map(c => c.trim()).filter(Boolean); + + let nameStr = ''; + let kanaStr = ''; + let genderStr = ''; + let bdayStr = ''; + let pwdStr = ''; + + // 匹配平假名与片假名的正则表达式(包含长音符号 ー) + const kanaRegex = /^[\u3040-\u309F\u30A0-\u30FF\u30FC\s]+$/; + + // 2. 智能提取字段 + tokens.forEach(token => { + // 匹配生日: YYYY-MM-DD / YYYY/MM/DD / 8位数字 + if (!bdayStr && (/^\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2}$/.test(token) || /^\d{8}$/.test(token))) { + bdayStr = normalizeBirthday(token); + } + // 匹配性别: 男 / 女 / M / F / Male / Female + else if (!genderStr && /^(男|女|M|F|Male|Female)$/i.test(token)) { + genderStr = token; + } + // 匹配密码: 包含字母和数字组合且长度 >= 6 + else if (!pwdStr && /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/.test(token)) { + pwdStr = token; + } + // 匹配平假名/片假名 + else if (!kanaStr && kanaRegex.test(token)) { + kanaStr = token; + } + // 剩余非纯数字文本作为汉字/英文姓名候选 + else if (!nameStr && !/^\d+$/.test(token)) { + nameStr = token; + } + }); + + if (!nameStr && tokens.length > 0) nameStr = tokens[0]; + + // 3. 拆分汉字/英文 姓与名 + const { l, f } = splitFullName(nameStr); + $('#npL', root).value = l; + $('#npF', root).value = f; + + // 4. 拆分假名 姓与名 并填充 + if (kanaStr) { + const { l: lk, f: fk } = splitFullName(kanaStr); + $('#npLk', root).value = lk; + $('#npFk', root).value = fk; + } + + // 5. 填充性别 + if (genderStr) { + if (/^(女|F|Female)$/i.test(genderStr)) { + $('#npGender', root).value = 'F'; + } else if (/^(男|M|Male)$/i.test(genderStr)) { + $('#npGender', root).value = 'M'; + } + } + + // 6. 填充生日 + if (bdayStr) { + $('#npBirthday', root).value = bdayStr; + } + + // 7. 填充密码 + if (pwdStr) { + $('#npPwd', root).value = pwdStr; + } + + // 8. 同步填充券面显示姓名 + const fullName = `${l} ${f}`.trim(); + if (fullName) { + $('#npOverlayName', root).value = fullName; + } + + const lkVal = $('#npLk', root).value; + const fkVal = $('#npFk', root).value; + log(`✅ 快速填充完成:\n• 姓名:${l} ${f}\n• 假名:${lkVal || fkVal ? `${lkVal} ${fkVal}` : '未匹配'}\n• 性别:${$('#npGender', root).value === 'F' ? '女 (F)' : '男 (M)'}\n• 生日:${bdayStr || '未匹配'}\n• 密码:${pwdStr ? '已自动填充' : '未匹配'}`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + const sexLabel = p.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n性别:${sexLabel}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + $('#npGender', root).value = p.gender || 'M'; + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + const gender = $('#npGender', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + gender, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + const sexLabel = after.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `性别:${sexLabel}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + const sexLabel = prof.gender === 'F' ? '女 (F)' : '男 (M)'; + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n性别:${sexLabel}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); \ No newline at end of file diff --git a/fixed-site-replacer-main/code-v1.6.2jr.user.js b/fixed-site-replacer-main/code-v1.6.2jr.user.js new file mode 100644 index 0000000..dd6e8d8 --- /dev/null +++ b/fixed-site-replacer-main/code-v1.6.2jr.user.js @@ -0,0 +1,1181 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息 *(通用版本) +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.0 +// @description 改会员资料姓名/生日/性别;可隐藏按钮与券面强制显示;支持 Excel 复制快速填充 +// @grant unsafeWindow +// @author park-tools +// @match https://parks2.bandainamco-am.co.jp/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_deleteValue +// ==/UserScript== + +(function () { + 'use strict'; + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + const LS_HIDE_UI = 'namco_hide_plugin_ui_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?:^|\D)(070\d{8}|080\d{8}|090\d{8})(?!\d)/); + if (tm) tel = tm[1]; + } + const y = parseInput(html, 'BIRTH_YEAR'); + const mo = parseInput(html, 'BIRTH_MONTH'); + const d = parseInput(html, 'BIRTH_DAY'); + let birthday = (md.birth || '').replace(/\//g, '-'); + if (y && mo && d) { + birthday = `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`; + } + const sex = parseCheckedRadio(html, 'SEX') || (md.sex || 'M').toString().charAt(0).toUpperCase(); + return { + email: parseInput(html, 'PC_MAIL'), + last_name: parseInput(html, 'L_NAME'), + first_name: parseInput(html, 'F_NAME'), + last_name_kana: parseInput(html, 'L_KANA') || parseInput(html, 'L_NAME_KANA'), + first_name_kana: parseInput(html, 'F_KANA') || parseInput(html, 'F_NAME_KANA'), + nickname: parseInput(html, 'NICKNAME'), + addr1: parseSelected(html, 'ADDR1') || parseInput(html, 'ADDR1') || '東京都', + zip: (parseInput(html, 'ZIP') || '').replace(/-/g, ''), + addr2: parseInput(html, 'ADDR2'), + addr_street: parseInput(html, 'MEMBER.FREE_ITEM16'), + addr3: parseInput(html, 'ADDR3'), + tel, + gender: sex === 'F' || sex === '2' ? 'F' : (sex === 'M' || sex === '1' ? 'M' : 'M'), + birthday: birthday || '1990-01-01', + formFields: parseHiddenFields(parseFormChunk(html, 'memberFrm').chunk || html), + }; + } + + function splitFullName(full) { + const s = String(full || '').trim().replace(/[\t\r\n]+/g, ' ').replace(/\s+/g, ' '); + if (!s) return { l: '', f: '' }; + if (/^[A-Za-z]/.test(s)) { + const parts = s.split(' '); + if (parts.length >= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function getHideUi() { + const saved = store.get(LS_HIDE_UI, null); + if (saved != null) return saved; + return { hidden: false }; + } + + function setHideUi(cfg) { + store.set(LS_HIDE_UI, cfg); + } + + function shouldHidePluginUi() { + const cfg = getHideUi(); + return !!(cfg && cfg.hidden); + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})[-/\.](\d{1,2})[-/\.](\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = changes.gender || profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + SEX: sex, + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + SEX: sex, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + gender: sex, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + // const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + // ✅ 修改为(兼容 iOS 15): + const orders = []; + const orderRe = /admission_use_ticket\.html\?order_no=(\d+)/g; + let om; + while ((om = orderRe.exec(r.text)) !== null) { + orders.push(om[1]); + } + + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + // const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + // ✅ 修改为(兼容 iOS 15): + const clients = []; + const clientRe = /ご依頼主<\/dt>\s*]*>\s*([^<]+)/g; + let cm; + while ((cm = clientRe.exec(hist.text)) !== null) { + clients.push(cm[1].trim()); + } + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input, .np-row select, .np-row textarea{width:100%;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;background:#fff;} +.np-row input, .np-row select{height:42px;} +.np-row textarea{padding:8px 12px;font-size:14px;resize:vertical;} +.np-row input:focus, .np-row select:focus, .np-row textarea:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名、生日与性别,无 SMS(手机号不变)。

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日/性别)。官网编辑页生日/性别虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    + 隐藏插件按钮 + +
    +

    隐藏后连点屏幕右下角两次可再打开设置

    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + if (shouldHidePluginUi()) { + overlayBadge.style.display = 'none'; + return; + } + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = shouldHidePluginUi() ? 'none' : ''; + refreshOverlayBadge(); + } + + function loadHideUiToUI() { + $('#npHideUi', root).checked = shouldHidePluginUi(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + loadHideUiToUI(); + refreshPluginUiVisibility(); + } + + function clearAllInputs() { + $('#npPaste', root).value = ''; + $('#npL', root).value = ''; + $('#npF', root).value = ''; + $('#npLk', root).value = ''; + $('#npFk', root).value = ''; + $('#npBirthday', root).value = ''; + $('#npGender', root).value = 'M'; + $('#npPwd', root).value = ''; + $('#npOverlayName', root).value = ''; + } + + function openPanel() { + mask.style.display = 'block'; + panel.classList.add('open'); + + clearAllInputs(); + + loadOverlayToUI(); + loadHideUiToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + $('#npHideUi', root).addEventListener('change', () => { + setHideUi({ hidden: $('#npHideUi', root).checked }); + refreshPluginUiVisibility(); + }); + + (function setupSecretOpen() { + let lastTap = 0; + function hitCorner(x, y) { + const margin = 72; + return x >= window.innerWidth - margin && y >= window.innerHeight - margin; + } + function onCornerTap(clientX, clientY) { + if (!shouldHidePluginUi()) return; + if (panel.classList.contains('open')) return; + if (!hitCorner(clientX, clientY)) return; + const now = Date.now(); + if (now - lastTap < 450) { + lastTap = 0; + openPanel(); + } else { + lastTap = now; + } + } + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + if (t) onCornerTap(t.clientX, t.clientY); + }, + { passive: true } + ); + document.addEventListener('click', (e) => { + if (e.target.closest('#npRenameRoot')) return; + onCornerTap(e.clientX, e.clientY); + }); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在下方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // 快速填充逻辑:解析从 Excel 复制的整行内容(已补全平假名/片假名支持) + $('#npQuickFill', root).addEventListener('click', () => { + const rawText = $('#npPaste', root).value.trim(); + if (!rawText) { + log('请先粘贴 Excel 行数据到快速录入框'); + return; + } + + // 1. 优先按 Tab 制表符(Excel 复制的默认分隔符)或 2 个以上空格拆分 + const cols = rawText.split(/\t+|\s{2,}/).map(c => c.trim()).filter(Boolean); + const tokens = cols.length > 1 ? cols : rawText.split(/\s+/).map(c => c.trim()).filter(Boolean); + + let nameStr = ''; + let kanaStr = ''; + let genderStr = ''; + let bdayStr = ''; + let pwdStr = ''; + + // 匹配平假名与片假名的正则表达式(包含长音符号 ー) + const kanaRegex = /^[\u3040-\u309F\u30A0-\u30FF\u30FC\s]+$/; + + // 2. 智能提取字段 + tokens.forEach(token => { + // 匹配生日: YYYY-MM-DD / YYYY/MM/DD / 8位数字 + if (!bdayStr && (/^\d{4}[-/\.]\d{1,2}[-/\.]\d{1,2}$/.test(token) || /^\d{8}$/.test(token))) { + bdayStr = normalizeBirthday(token); + } + // 匹配性别: 男 / 女 / M / F / Male / Female + else if (!genderStr && /^(男|女|M|F|Male|Female)$/i.test(token)) { + genderStr = token; + } + // 匹配密码: 包含字母和数字组合且长度 >= 6 + else if (!pwdStr && /^(?=.*[a-zA-Z])(?=.*\d).{6,}$/.test(token)) { + pwdStr = token; + } + // 匹配平假名/片假名 + else if (!kanaStr && kanaRegex.test(token)) { + kanaStr = token; + } + // 剩余非纯数字文本作为汉字/英文姓名候选 + else if (!nameStr && !/^\d+$/.test(token)) { + nameStr = token; + } + }); + + if (!nameStr && tokens.length > 0) nameStr = tokens[0]; + + // 3. 拆分汉字/英文 姓与名 + const { l, f } = splitFullName(nameStr); + $('#npL', root).value = l; + $('#npF', root).value = f; + + // 4. 拆分假名 姓与名 并填充 + if (kanaStr) { + const { l: lk, f: fk } = splitFullName(kanaStr); + $('#npLk', root).value = lk; + $('#npFk', root).value = fk; + } + + // 5. 填充性别 + if (genderStr) { + if (/^(女|F|Female)$/i.test(genderStr)) { + $('#npGender', root).value = 'F'; + } else if (/^(男|M|Male)$/i.test(genderStr)) { + $('#npGender', root).value = 'M'; + } + } + + // 6. 填充生日 + if (bdayStr) { + $('#npBirthday', root).value = bdayStr; + } + + // 7. 填充密码 + if (pwdStr) { + $('#npPwd', root).value = pwdStr; + } + + // 8. 同步填充券面显示姓名 + const fullName = `${l} ${f}`.trim(); + if (fullName) { + $('#npOverlayName', root).value = fullName; + } + + const lkVal = $('#npLk', root).value; + const fkVal = $('#npFk', root).value; + log(`✅ 快速填充完成:\n• 姓名:${l} ${f}\n• 假名:${lkVal || fkVal ? `${lkVal} ${fkVal}` : '未匹配'}\n• 性别:${$('#npGender', root).value === 'F' ? '女 (F)' : '男 (M)'}\n• 生日:${bdayStr || '未匹配'}\n• 密码:${pwdStr ? '已自动填充' : '未匹配'}`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + const sexLabel = p.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n性别:${sexLabel}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + $('#npGender', root).value = p.gender || 'M'; + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + const gender = $('#npGender', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + gender, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + const sexLabel = after.gender === 'F' ? '女 (F)' : '男 (M)'; + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `性别:${sexLabel}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + const sexLabel = prof.gender === 'F' ? '女 (F)' : '男 (M)'; + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n性别:${sexLabel}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); \ No newline at end of file diff --git a/fixed-site-replacer-main/code.js b/fixed-site-replacer-main/code.js new file mode 100644 index 0000000..b61ab10 --- /dev/null +++ b/fixed-site-replacer-main/code.js @@ -0,0 +1,656 @@ +// ==UserScript== +// @name 20260831测试代码 +// @namespace local.codex.fixed-site-replacer +// @version 0.10.1 +// @description 读取姓名=直接读页面DOM(零请求);提交=登录态confirm→execute拿token +// @match *://*.bandainamco-am.co.jp/* +// @match *://bandainamco-am.co.jp/* +// @match *://baidu.com/* +// @match *://www.baidu.com/* +// @grant none +// @run-at document-end +// ==/UserScript== + +// ===================================================================== +// v0.10.1: +// 1. 「读取姓名」= 直接解析当前页面 DOM(不发起任何网络请求): +// - 编辑页(member_regist):读 input[name=L_NAME/F_NAME] 的 value +// - 会员页/其他页:找「氏名」dt 的相邻 dd 文本 +// 2. 「提交到服务器」= 才联网:GET 编辑页拿 token/字段 → POST confirm → +// POST execute(登录态会话,密码验证),永久生效。 +// 3. 所有错误实时上报服务器。 +// ===================================================================== + +(() => { + 'use strict'; + + const LOG_URL = 'https://www.fugui188.site/userscript-log.php?token=***'; + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const ICON_ID = 'codex-parks-rename-icon'; + const PANEL_ID = 'codex-parks-rename-panel'; + const STYLE_ID = 'codex-parks-rename-style'; + + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + /* ---------- 日志上报 ---------- */ + + let _lastLog = 0; + function reportLog(msg) { + try { + const now = Date.now(); + if (now - _lastLog < 800) return; + _lastLog = now; + const payload = JSON.stringify({ t: new Date().toISOString(), u: location.href, v: '0.10.1', m: String(msg).slice(0, 500) }); + if (navigator.sendBeacon) { + navigator.sendBeacon(LOG_URL, payload); + } else if (window.fetch) { + fetch(LOG_URL, { method: 'POST', mode: 'no-cors', body: payload }).catch(() => {}); + } + } catch (e) { /* ignore */ } + } + + window.addEventListener('error', (e) => { + reportLog('ERR ' + (e && e.message ? e.message : 'unknown') + (e && e.lineno ? ' line ' + e.lineno : '')); + }); + window.addEventListener('unhandledrejection', (e) => { + const r = e && e.reason; + reportLog('REJ ' + ((r && (r.message || r)) || 'unknown')); + }); + + /* ================================================================= + * 读取姓名:直接读页面 DOM,零请求 + * ================================================================= */ + + function readNameFromDom() { + const l = document.querySelector('input[name="L_NAME"]'); + const f = document.querySelector('input[name="F_NAME"]'); + if (l || f) { + const res = { + last_name: l ? l.value.trim() : '', + first_name: f ? f.value.trim() : '', + source: '编辑页input', + }; + reportLog('DOM读取(编辑页): 姓="' + res.last_name + '" 名="' + res.first_name + '"'); + return res; + } + + // 会员页 dl/dt+dd 结构:找包含「氏名」的 dt,取其相邻 dd + const dts = Array.from(document.querySelectorAll('dt')); + const dt = dts.find((d) => (d.textContent || '').indexOf('氏名') !== -1); + if (dt && dt.nextElementSibling) { + const txt = (dt.nextElementSibling.textContent || '').replace(/\s+/g, ' ').trim(); + const parts = txt.split(' '); + let last_name = txt, first_name = ''; + if (parts.length >= 2) { + last_name = parts[0]; + first_name = parts.slice(1).join(' '); + } else if (txt.length >= 2) { + last_name = txt.charAt(0); + first_name = txt.slice(1); + } + reportLog('DOM读取(会员页): 姓="' + last_name + '" 名="' + first_name + '"'); + return { last_name, first_name, source: '会员页' }; + } + + reportLog('DOM读取失败: 页面上没有 L_NAME/F_NAME input 也没有「氏名」区块'); + return null; + } + + /* ================================================================= + * 提交:登录态 confirm → execute(拿 token) + * ================================================================= */ + + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npF_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + JSON.stringify(url) + ',' + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + try { + document.documentElement.appendChild(script); + script.remove(); + } catch (e) { + reject(e); + return; + } + const start = Date.now(); + const timer = setInterval(() => { + const box = (PAGE && PAGE[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete window[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete window[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 60000) { + clearInterval(timer); + try { delete window[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 60); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e1) { + return await pageFetch(url, { method: 'GET', headers }); + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e1) { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp(']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', 'i'); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match(new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i')); + if (!sm) return ''; + const opt = sm[1].match(/]*\bselected\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp(']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', 'gi'); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match(new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i')); + const body = html.match(new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i')); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?× +

    Bandai Parks 会员资料(v0.10.1)

    +

    「读取姓名」直接解析当前页面 DOM(零请求);「提交」时才联网走登录态拿 token。

    +
    ⚠ 读取不联网一定成功;提交会真实修改服务器资料(姓名+生日)。
    +
    +
    +
    +
    +
    + + + +
    +
    就绪:点「读取姓名」从当前页面 DOM 提取。
    + `; + + document.body.appendChild(icon); + document.body.appendChild(panel); + + const logEl = panel.querySelector('#cpxLog'); + const setLog = (msg) => { logEl.textContent = msg; }; + const open = () => panel.classList.add('open'); + const close = () => panel.classList.remove('open'); + + icon.addEventListener('click', () => { + if (panel.classList.contains('open')) close(); + else open(); + }); + panel.querySelector('#cpxClose').addEventListener('click', close); + + // 读取:直接读当前页面 DOM(零请求) + panel.querySelector('#cpxLoad').addEventListener('click', () => { + reportLog('用户点击「读取姓名」'); + const n = readNameFromDom(); + if (!n) { + setLog('❌ 当前页面没有姓名信息。\n请到以下页面再点:\n• 编辑页 member_regist.html?request=edit\n• 会员页 member_mypage.html'); + return; + } + panel.querySelector('#cpxL').value = n.last_name; + panel.querySelector('#cpxF').value = n.first_name; + setLog('✅ 已从页面 DOM 读取(来源:' + n.source + ')\n姓:' + n.last_name + '\n名:' + n.first_name); + }); + + panel.querySelector('#cpxClear').addEventListener('click', () => { + panel.querySelector('#cpxL').value = ''; + panel.querySelector('#cpxF').value = ''; + panel.querySelector('#cpxB').value = ''; + setLog('已清空。'); + }); + + // 提交:登录态 confirm → execute(拿 token) + panel.querySelector('#cpxSubmit').addEventListener('click', async () => { + const ln = panel.querySelector('#cpxL').value.trim(); + const fn = panel.querySelector('#cpxF').value.trim(); + const bRaw = panel.querySelector('#cpxB').value.trim(); + const pwd = panel.querySelector('#cpxP').value; + if (!ln || !fn) { setLog('请先读取或填写姓和名'); return; } + if (!pwd) { setLog('请填写登录密码'); return; } + setLog('提交中…(confirm → execute,最多等 60 秒)'); + reportLog('用户点击「提交」姓=' + ln + ' 名=' + fn); + try { + const profile = await loadProfileForSubmit(); + const res = await submitNameBirthday(profile, ln, fn, bRaw, pwd); + setLog('✅ 已提交到服务器并永久生效\n新氏名:' + res.last_name + ' ' + res.first_name + '\n生日:' + res.birthday); + } catch (e) { + setLog('❌ ' + e.message); + } + }); + } + + /* ---------- 启动 ---------- */ + + function boot() { + reportLog('BOOT UI v0.10.1'); + if (document.body) { + buildUI(); + reportLog('UI 已注入'); + return; + } + const t = setInterval(() => { + if (document.body) { + clearInterval(t); + buildUI(); + reportLog('UI 已注入'); + } + }, 200); + } + + boot(); +})(); diff --git a/fixed-site-replacer-main/code.user.js b/fixed-site-replacer-main/code.user.js new file mode 100644 index 0000000..b61ab10 --- /dev/null +++ b/fixed-site-replacer-main/code.user.js @@ -0,0 +1,656 @@ +// ==UserScript== +// @name 20260831测试代码 +// @namespace local.codex.fixed-site-replacer +// @version 0.10.1 +// @description 读取姓名=直接读页面DOM(零请求);提交=登录态confirm→execute拿token +// @match *://*.bandainamco-am.co.jp/* +// @match *://bandainamco-am.co.jp/* +// @match *://baidu.com/* +// @match *://www.baidu.com/* +// @grant none +// @run-at document-end +// ==/UserScript== + +// ===================================================================== +// v0.10.1: +// 1. 「读取姓名」= 直接解析当前页面 DOM(不发起任何网络请求): +// - 编辑页(member_regist):读 input[name=L_NAME/F_NAME] 的 value +// - 会员页/其他页:找「氏名」dt 的相邻 dd 文本 +// 2. 「提交到服务器」= 才联网:GET 编辑页拿 token/字段 → POST confirm → +// POST execute(登录态会话,密码验证),永久生效。 +// 3. 所有错误实时上报服务器。 +// ===================================================================== + +(() => { + 'use strict'; + + const LOG_URL = 'https://www.fugui188.site/userscript-log.php?token=***'; + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const ICON_ID = 'codex-parks-rename-icon'; + const PANEL_ID = 'codex-parks-rename-panel'; + const STYLE_ID = 'codex-parks-rename-style'; + + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + /* ---------- 日志上报 ---------- */ + + let _lastLog = 0; + function reportLog(msg) { + try { + const now = Date.now(); + if (now - _lastLog < 800) return; + _lastLog = now; + const payload = JSON.stringify({ t: new Date().toISOString(), u: location.href, v: '0.10.1', m: String(msg).slice(0, 500) }); + if (navigator.sendBeacon) { + navigator.sendBeacon(LOG_URL, payload); + } else if (window.fetch) { + fetch(LOG_URL, { method: 'POST', mode: 'no-cors', body: payload }).catch(() => {}); + } + } catch (e) { /* ignore */ } + } + + window.addEventListener('error', (e) => { + reportLog('ERR ' + (e && e.message ? e.message : 'unknown') + (e && e.lineno ? ' line ' + e.lineno : '')); + }); + window.addEventListener('unhandledrejection', (e) => { + const r = e && e.reason; + reportLog('REJ ' + ((r && (r.message || r)) || 'unknown')); + }); + + /* ================================================================= + * 读取姓名:直接读页面 DOM,零请求 + * ================================================================= */ + + function readNameFromDom() { + const l = document.querySelector('input[name="L_NAME"]'); + const f = document.querySelector('input[name="F_NAME"]'); + if (l || f) { + const res = { + last_name: l ? l.value.trim() : '', + first_name: f ? f.value.trim() : '', + source: '编辑页input', + }; + reportLog('DOM读取(编辑页): 姓="' + res.last_name + '" 名="' + res.first_name + '"'); + return res; + } + + // 会员页 dl/dt+dd 结构:找包含「氏名」的 dt,取其相邻 dd + const dts = Array.from(document.querySelectorAll('dt')); + const dt = dts.find((d) => (d.textContent || '').indexOf('氏名') !== -1); + if (dt && dt.nextElementSibling) { + const txt = (dt.nextElementSibling.textContent || '').replace(/\s+/g, ' ').trim(); + const parts = txt.split(' '); + let last_name = txt, first_name = ''; + if (parts.length >= 2) { + last_name = parts[0]; + first_name = parts.slice(1).join(' '); + } else if (txt.length >= 2) { + last_name = txt.charAt(0); + first_name = txt.slice(1); + } + reportLog('DOM读取(会员页): 姓="' + last_name + '" 名="' + first_name + '"'); + return { last_name, first_name, source: '会员页' }; + } + + reportLog('DOM读取失败: 页面上没有 L_NAME/F_NAME input 也没有「氏名」区块'); + return null; + } + + /* ================================================================= + * 提交:登录态 confirm → execute(拿 token) + * ================================================================= */ + + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npF_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + JSON.stringify(url) + ',' + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + try { + document.documentElement.appendChild(script); + script.remove(); + } catch (e) { + reject(e); + return; + } + const start = Date.now(); + const timer = setInterval(() => { + const box = (PAGE && PAGE[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete window[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete window[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 60000) { + clearInterval(timer); + try { delete window[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 60); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e1) { + return await pageFetch(url, { method: 'GET', headers }); + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e1) { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp(']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', 'i'); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match(new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i')); + if (!sm) return ''; + const opt = sm[1].match(/]*\bselected\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp(']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', 'gi'); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match(new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i')); + const body = html.match(new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i')); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?× +

    Bandai Parks 会员资料(v0.10.1)

    +

    「读取姓名」直接解析当前页面 DOM(零请求);「提交」时才联网走登录态拿 token。

    +
    ⚠ 读取不联网一定成功;提交会真实修改服务器资料(姓名+生日)。
    +
    +
    +
    +
    +
    + + + +
    +
    就绪:点「读取姓名」从当前页面 DOM 提取。
    + `; + + document.body.appendChild(icon); + document.body.appendChild(panel); + + const logEl = panel.querySelector('#cpxLog'); + const setLog = (msg) => { logEl.textContent = msg; }; + const open = () => panel.classList.add('open'); + const close = () => panel.classList.remove('open'); + + icon.addEventListener('click', () => { + if (panel.classList.contains('open')) close(); + else open(); + }); + panel.querySelector('#cpxClose').addEventListener('click', close); + + // 读取:直接读当前页面 DOM(零请求) + panel.querySelector('#cpxLoad').addEventListener('click', () => { + reportLog('用户点击「读取姓名」'); + const n = readNameFromDom(); + if (!n) { + setLog('❌ 当前页面没有姓名信息。\n请到以下页面再点:\n• 编辑页 member_regist.html?request=edit\n• 会员页 member_mypage.html'); + return; + } + panel.querySelector('#cpxL').value = n.last_name; + panel.querySelector('#cpxF').value = n.first_name; + setLog('✅ 已从页面 DOM 读取(来源:' + n.source + ')\n姓:' + n.last_name + '\n名:' + n.first_name); + }); + + panel.querySelector('#cpxClear').addEventListener('click', () => { + panel.querySelector('#cpxL').value = ''; + panel.querySelector('#cpxF').value = ''; + panel.querySelector('#cpxB').value = ''; + setLog('已清空。'); + }); + + // 提交:登录态 confirm → execute(拿 token) + panel.querySelector('#cpxSubmit').addEventListener('click', async () => { + const ln = panel.querySelector('#cpxL').value.trim(); + const fn = panel.querySelector('#cpxF').value.trim(); + const bRaw = panel.querySelector('#cpxB').value.trim(); + const pwd = panel.querySelector('#cpxP').value; + if (!ln || !fn) { setLog('请先读取或填写姓和名'); return; } + if (!pwd) { setLog('请填写登录密码'); return; } + setLog('提交中…(confirm → execute,最多等 60 秒)'); + reportLog('用户点击「提交」姓=' + ln + ' 名=' + fn); + try { + const profile = await loadProfileForSubmit(); + const res = await submitNameBirthday(profile, ln, fn, bRaw, pwd); + setLog('✅ 已提交到服务器并永久生效\n新氏名:' + res.last_name + ' ' + res.first_name + '\n生日:' + res.birthday); + } catch (e) { + setLog('❌ ' + e.message); + } + }); + } + + /* ---------- 启动 ---------- */ + + function boot() { + reportLog('BOOT UI v0.10.1'); + if (document.body) { + buildUI(); + reportLog('UI 已注入'); + return; + } + const t = setInterval(() => { + if (document.body) { + clearInterval(t); + buildUI(); + reportLog('UI 已注入'); + } + }, 200); + } + + boot(); +})(); diff --git a/fixed-site-replacer-main/index.html b/fixed-site-replacer-main/index.html new file mode 100644 index 0000000..498c362 --- /dev/null +++ b/fixed-site-replacer-main/index.html @@ -0,0 +1,349 @@ + + + + + +固定网站显示替换 - 脚本下载 + + + +

    📦 下载脚本(最新版)

    + +

    在 iPhone 上点 code-v0.7.0.user.js(最新版)即开始下载,然后用 Userscripts 应用导入。
    需要旧版本时点对应版本文件。

    +

    🧪 注入测试(排查用)

    +
      +
    • test-inject.user.js (任意网站左上角显示蓝色标记,验证 Userscripts 是否正常注入)
    • +
    +
    +

    📌 bookmarklet 书签版(不依赖扩展)

    +

    不想装扩展?点下方按钮把新脚本复制到剪贴板,然后粘贴到书签 URL 即可:
    打开任意网页 → 分享 → 添加书签 → 书本按钮 → 编辑 → 把地址全部删掉 → 长按粘贴 → 完成。
    之后在目标网站点这个书签,文字立即替换。

    + +

    💡 复制成功后,把内容粘贴到任意书签的「地址」栏(以 javascript: 开头)。
    规则配置与油猴版共用,首次点击弹出设置面板时填写即可。

    +
    +

    🔗 目标网站

    +

    + https://parks2.bandainamco-am.co.jp/ +

    +

    👆 点击在新页签打开网站;长按链接可弹出菜单「拷贝」,复制网址。

    +
    + + + + + + + +
    + + +
    + + +
    +
    SECONDS
    +
    +
    + + + 5 + +
    +
    +
    時空忍術発動中…
    +
    + +
    + + diff --git a/fixed-site-replacer-main/koshigaya-accounts.js b/fixed-site-replacer-main/koshigaya-accounts.js new file mode 100644 index 0000000..f18336d --- /dev/null +++ b/fixed-site-replacer-main/koshigaya-accounts.js @@ -0,0 +1,1012 @@ +const KOSHIGAYA_ACCOUNTS = [ + { + "email": "JaniyahDantico2194@outlook.com", + "pass": "ljseqa35917", + "time": "11:00" + }, + { + "email": "madalynholt616459@outlook.com", + "pass": "wpnbf786410", + "time": "11:00" + }, + { + "email": "brytonyotter31434@outlook.com", + "pass": "pcrhe86501", + "time": "11:00" + }, + { + "email": "shaniwuchter32810@outlook.com", + "pass": "mstvy58766", + "time": "11:00" + }, + { + "email": "paulopeek670416@outlook.com", + "pass": "isabur894871", + "time": "11:00" + }, + { + "email": "BlancaCaldarella13667@outlook.com", + "pass": "fjonbr197510", + "time": "11:00" + }, + { + "email": "lindberghwoollen56139@outlook.com", + "pass": "bawbje068824", + "time": "11:00" + }, + { + "email": "herminaangela7054@outlook.com", + "pass": "qexwsl248778", + "time": "12:00" + }, + { + "email": "kathernleow9578@outlook.com", + "pass": "xacbju42791", + "time": "12:00" + }, + { + "email": "binajentz429310@outlook.com", + "pass": "dntdlb97231", + "time": "12:00" + }, + { + "email": "brucebeaudrot4231@outlook.com", + "pass": "ppwhk729593", + "time": "12:00" + }, + { + "email": "riannacrisanto301358@outlook.com", + "pass": "kymgjf921698", + "time": "12:00" + }, + { + "email": "zellanasiatka2236@outlook.com", + "pass": "rozbm35749", + "time": "12:00" + }, + { + "email": "aftonmerski9516@outlook.com", + "pass": "ijkfwp577542", + "time": "12:00" + }, + { + "email": "roscoerippa449985@outlook.com", + "pass": "rdkdsz49411", + "time": "12:00" + }, + { + "email": "LorenzDeluke2450@outlook.com", + "pass": "inliv83321", + "time": "12:00" + }, + { + "email": "christenabeshears2059@outlook.com", + "pass": "fneoq89363", + "time": "12:00" + }, + { + "email": "viciemartzahl135720@outlook.com", + "pass": "gpufo747762", + "time": "12:00" + }, + { + "email": "NorvalWerfel63861@outlook.com", + "pass": "drppt38582", + "time": "12:00" + }, + { + "email": "KathleenNakao9531@outlook.com", + "pass": "pndkmt73926", + "time": "12:00" + }, + { + "email": "FriedaCuzzo9112@outlook.com", + "pass": "aguhc04283", + "time": "12:00" + }, + { + "email": "PenelopeYamabe69775@outlook.com", + "pass": "wfehzh658649", + "time": "12:00" + }, + { + "email": "JennifferFurnia37754@outlook.com", + "pass": "vutjpz70515", + "time": "12:00" + }, + { + "email": "AlanzoPadre993075@outlook.com", + "pass": "dyhsy291566", + "time": "12:00" + }, + { + "email": "deshawnmanganelli1249@outlook.com", + "pass": "flbli03982", + "time": "12:00" + }, + { + "email": "ryancampanard581792@outlook.com", + "pass": "hnbxb16261", + "time": "13:00" + }, + { + "email": "kylerboufford34480@outlook.com", + "pass": "eecsl09537", + "time": "13:00" + }, + { + "email": "giancarloambriz13995@outlook.com", + "pass": "ttfonj844173", + "time": "13:00" + }, + { + "email": "noemiesalgado9116@outlook.com", + "pass": "uadbx86420", + "time": "13:00" + }, + { + "email": "norbertoplumly189896@outlook.com", + "pass": "wyhiv72826", + "time": "13:00" + }, + { + "email": "ridgebellizzi18163@outlook.com", + "pass": "muyufb053003", + "time": "13:00" + }, + { + "email": "johanastrosser5999@outlook.com", + "pass": "epbls25268", + "time": "13:00" + }, + { + "email": "joellenmalliet29351@outlook.com", + "pass": "bmizx61457", + "time": "13:00" + }, + { + "email": "allenewillars4799@outlook.com", + "pass": "ybnbcm37877", + "time": "13:00" + }, + { + "email": "maryjanedempsey5730@outlook.com", + "pass": "wjsuwi264212", + "time": "13:00" + }, + { + "email": "dejongoedtel384774@outlook.com", + "pass": "ugxvoo42523", + "time": "13:00" + }, + { + "email": "nathanialmurua308621@outlook.com", + "pass": "apboyj85433", + "time": "13:00" + }, + { + "email": "ChrissyHelker8173@outlook.com", + "pass": "rgeju884096", + "time": "13:00" + }, + { + "email": "DeonteSholly6069@outlook.com", + "pass": "rdygo96255", + "time": "13:00" + }, + { + "email": "EdithCorlee48016@outlook.com", + "pass": "seohth896097", + "time": "13:00" + }, + { + "email": "CecilKluczynski55874@outlook.com", + "pass": "uityy49085", + "time": "13:00" + }, + { + "email": "LoreneBirtley61090@outlook.com", + "pass": "jfbrrn404762", + "time": "13:00" + }, + { + "email": "freemanpourroy86447@outlook.com", + "pass": "awsdtr763001", + "time": "13:00" + }, + { + "email": "mistyportmann3332@outlook.com", + "pass": "xfdpib06265", + "time": "14:00" + }, + { + "email": "nicksojda496948@outlook.com", + "pass": "xmpes200624", + "time": "14:00" + }, + { + "email": "demarcusarcoren12576@outlook.com", + "pass": "bkbojl649878", + "time": "14:00" + }, + { + "email": "willisdahlquist8407@outlook.com", + "pass": "vumjc725058", + "time": "14:00" + }, + { + "email": "reynoldscarnagey786326@outlook.com", + "pass": "fvmhc95463", + "time": "14:00" + }, + { + "email": "alaniczubek284097@outlook.com", + "pass": "snheyz456034", + "time": "14:00" + }, + { + "email": "jarrettlifer6478@outlook.com", + "pass": "trdsd67969", + "time": "14:00" + }, + { + "email": "kristalgeerts665112@outlook.com", + "pass": "gjlvrw59178", + "time": "14:00" + }, + { + "email": "estacrowder56912@outlook.com", + "pass": "eaouo172449", + "time": "14:00" + }, + { + "email": "tiawilcockson97856@outlook.com", + "pass": "fztnaj401649", + "time": "14:00" + }, + { + "email": "jeraldineacri2859@outlook.com", + "pass": "qifcyk102778", + "time": "14:00" + }, + { + "email": "addiesalewsky982726@outlook.com", + "pass": "rckpij78517", + "time": "14:00" + }, + { + "email": "coltondesplinter901548@outlook.com", + "pass": "xuxpeb01588", + "time": "14:00" + }, + { + "email": "colonschlagel140555@outlook.com", + "pass": "syouf788439", + "time": "14:00" + }, + { + "email": "FlorrieDrabczyk33402@outlook.com", + "pass": "ezunn827721", + "time": "14:00" + }, + { + "email": "RusselVialpando4549@outlook.com", + "pass": "fisgl58242", + "time": "14:00" + }, + { + "email": "NannaBeahan0337@outlook.com", + "pass": "qtgbh05046", + "time": "14:00" + }, + { + "email": "DestiniBedinger8675@outlook.com", + "pass": "akzpg740487", + "time": "14:00" + }, + { + "email": "shardeankerson861438@outlook.com", + "pass": "tgpylx436394", + "time": "14:00" + }, + { + "email": "everettstreit4693@outlook.com", + "pass": "mxwymy00653", + "time": "14:00" + }, + { + "email": "liampozo203822@outlook.com", + "pass": "ybocpl833242", + "time": "14:00" + }, + { + "email": "myrtavalach563647@outlook.com", + "pass": "egnfn496003", + "time": "14:00" + }, + { + "email": "harlandhandelong964545@outlook.com", + "pass": "oyvzpa84253", + "time": "14:00" + }, + { + "email": "ozzieklarich20054@outlook.com", + "pass": "ywyrat20190", + "time": "15:00" + }, + { + "email": "dafnegeorgetti909788@outlook.com", + "pass": "wnszkz635457", + "time": "15:00" + }, + { + "email": "olindablanner826737@outlook.com", + "pass": "vmuol152019", + "time": "15:00" + }, + { + "email": "aloysiusmagcalas96324@outlook.com", + "pass": "dnjkoe390048", + "time": "15:00" + }, + { + "email": "mortonsignorello176428@outlook.com", + "pass": "aoaqk275928", + "time": "15:00" + }, + { + "email": "kamarirugenstein05065@outlook.com", + "pass": "ldkfhr58041", + "time": "15:00" + }, + { + "email": "WillaimBarts121410@outlook.com", + "pass": "mqwdl35218", + "time": "15:00" + }, + { + "email": "roderickboatfield023855@outlook.com", + "pass": "aftnn77372", + "time": "15:00" + }, + { + "email": "conwaylockhart2236@outlook.com", + "pass": "vumwr50409", + "time": "15:00" + }, + { + "email": "burnswestlie2806@outlook.com", + "pass": "ednyb42795", + "time": "15:00" + }, + { + "email": "alphonseardente81013@outlook.com", + "pass": "ckxmsf02511", + "time": "15:00" + }, + { + "email": "LeifRup1489@outlook.com", + "pass": "eqdjz542446", + "time": "15:00" + }, + { + "email": "jabaridaughton8466@outlook.com", + "pass": "ahwye553722", + "time": "15:00" + }, + { + "email": "dinahbros57463@outlook.com", + "pass": "rhzqo23103", + "time": "15:00" + }, + { + "email": "GideonHoltmeier0968@outlook.com", + "pass": "oqizli40667", + "time": "15:00" + }, + { + "email": "delmaszeni34319@outlook.com", + "pass": "jsdam946795", + "time": "15:00" + }, + { + "email": "murphysimrell59631@outlook.com", + "pass": "ebxfv391144", + "time": "15:00" + }, + { + "email": "MozellaRibaudo62525@outlook.com", + "pass": "hycum641457", + "time": "15:00" + }, + { + "email": "WillardVillere88975@outlook.com", + "pass": "xvyhs681432", + "time": "15:00" + }, + { + "email": "BenjaminPinamonti31711@outlook.com", + "pass": "ytvjrk06595", + "time": "15:00" + }, + { + "email": "VelvaWilleby63475@outlook.com", + "pass": "cejdt97637", + "time": "15:00" + }, + { + "email": "cornelcockley7275@outlook.com", + "pass": "chnmiv056920", + "time": "15:00" + }, + { + "email": "VashonSchmick4689@outlook.com", + "pass": "dosgnp562160", + "time": "15:00" + }, + { + "email": "kathietakahata58795@outlook.com", + "pass": "ansprg048295", + "time": "15:00" + }, + { + "email": "andonyelverton4931@outlook.com", + "pass": "ddnswn856550", + "time": "15:00" + }, + { + "email": "maribeldegreef45479@outlook.com", + "pass": "pogkrf75892", + "time": "15:00" + }, + { + "email": "charlizetroope0396@outlook.com", + "pass": "gllnge330779", + "time": "15:00" + }, + { + "email": "jeseniaprehoda5108@outlook.com", + "pass": "uexru659943", + "time": "15:00" + }, + { + "email": "metaskokan944187@outlook.com", + "pass": "wpiyqg758971", + "time": "15:00" + }, + { + "email": "josephinelopezjimenez067231@outlook.com", + "pass": "nzkuv370472", + "time": "15:00" + }, + { + "email": "hymenhurdle5807@outlook.com", + "pass": "zoksd623142", + "time": "16:00" + }, + { + "email": "niraperches0559@outlook.com", + "pass": "rbssne518349", + "time": "16:00" + }, + { + "email": "deasiahartmann625805@outlook.com", + "pass": "xzfkaj11574", + "time": "16:00" + }, + { + "email": "kyleearellanos940611@outlook.com", + "pass": "fmxgxw41982", + "time": "16:00" + }, + { + "email": "dorinecarasquillo320061@outlook.com", + "pass": "qxvht168261", + "time": "16:00" + }, + { + "email": "shaynasafranski3430@outlook.com", + "pass": "ntubd793827", + "time": "16:00" + }, + { + "email": "breannlounsbery4902@outlook.com", + "pass": "mxfqzd32431", + "time": "16:00" + }, + { + "email": "JoellenMiloro968699@outlook.com", + "pass": "fsdunl69783", + "time": "16:00" + }, + { + "email": "BrandieFlugstad8328@outlook.com", + "pass": "jxhrb600903", + "time": "16:00" + }, + { + "email": "francescomoyao75282@outlook.com", + "pass": "opvrm890234", + "time": "16:00" + }, + { + "email": "hammitsui08851@outlook.com", + "pass": "vldwvf38755", + "time": "16:00" + }, + { + "email": "beckyschnegg6305@outlook.com", + "pass": "wdwegq090839", + "time": "16:00" + }, + { + "email": "imanoldaddio562684@outlook.com", + "pass": "mmpvc04434", + "time": "16:00" + }, + { + "email": "katarinaduft16919@outlook.com", + "pass": "oszvm683823", + "time": "16:00" + }, + { + "email": "TorieBowler946231@outlook.com", + "pass": "swednx428141", + "time": "16:00" + }, + { + "email": "DemondOverlander146072@outlook.com", + "pass": "ejgty75807", + "time": "16:00" + }, + { + "email": "ButlerPascuzzo8213@outlook.com", + "pass": "jsmoc51739", + "time": "16:00" + }, + { + "email": "MerriPrestridge0504@outlook.com", + "pass": "shgjqe42756", + "time": "16:00" + }, + { + "email": "ChauncyUren5051@outlook.com", + "pass": "ogbcva576533", + "time": "16:00" + }, + { + "email": "rayfordmarceline74930@outlook.com", + "pass": "stsku71328", + "time": "16:00" + }, + { + "email": "blazepapson499687@outlook.com", + "pass": "bifmih71902", + "time": "16:00" + }, + { + "email": "tempiefraize58200@outlook.com", + "pass": "pyowmo556239", + "time": "16:00" + }, + { + "email": "arniepontillas46096@outlook.com", + "pass": "pqtqi157505", + "time": "16:00" + }, + { + "email": "MayeKat835184@outlook.com", + "pass": "zlabtk347208", + "time": "16:00" + }, + { + "email": "MemphisKrems459002@outlook.com", + "pass": "mustt069050", + "time": "16:00" + }, + { + "email": "EmilioBarbarick8199@outlook.com", + "pass": "qtmzxz98992", + "time": "17:00" + }, + { + "email": "woodmezzetti75238@outlook.com", + "pass": "ulafr93451", + "time": "17:00" + }, + { + "email": "tillamcclennan327436@outlook.com", + "pass": "iamffx338595", + "time": "17:00" + }, + { + "email": "galesowels1848@outlook.com", + "pass": "aawdb00090", + "time": "17:00" + }, + { + "email": "veronicacorday4091@outlook.com", + "pass": "qzuii221223", + "time": "17:00" + }, + { + "email": "rethaleach83861@outlook.com", + "pass": "drqdpw52444", + "time": "17:00" + }, + { + "email": "audleyepes95426@outlook.com", + "pass": "ffxzlo208389", + "time": "17:00" + }, + { + "email": "aishabucknam32934@outlook.com", + "pass": "bgviyo408711", + "time": "17:00" + }, + { + "email": "chandlermarkovitch86853@outlook.com", + "pass": "yvuht30385", + "time": "17:00" + }, + { + "email": "franklineppens42620@outlook.com", + "pass": "vkgft671828", + "time": "17:00" + }, + { + "email": "krististaude535854@outlook.com", + "pass": "fzbong83649", + "time": "17:00" + }, + { + "email": "krystinawinberg46860@outlook.com", + "pass": "lzkbkr76321", + "time": "17:00" + }, + { + "email": "lenardhoy360691@outlook.com", + "pass": "asbzzg50171", + "time": "17:00" + }, + { + "email": "debbranerren7188@outlook.com", + "pass": "eefozf480472", + "time": "17:00" + }, + { + "email": "lelandsaeedi043052@outlook.com", + "pass": "nhhckn79044", + "time": "17:00" + }, + { + "email": "nikolaistrasser48590@outlook.com", + "pass": "bxvvcn237249", + "time": "17:00" + }, + { + "email": "elainetomko2224@outlook.com", + "pass": "skmpd622305", + "time": "17:00" + }, + { + "email": "LandonBoston54950@outlook.com", + "pass": "snmet74117", + "time": "17:00" + }, + { + "email": "KelisMarkatos672157@outlook.com", + "pass": "rqhwui237605", + "time": "17:00" + }, + { + "email": "EsmeraldaGun975575@outlook.com", + "pass": "nrouvf02985", + "time": "17:00" + }, + { + "email": "VernalWillam14197@outlook.com", + "pass": "kbfyw674691", + "time": "17:00" + }, + { + "email": "KaitlynnGraffam279728@outlook.com", + "pass": "gioeq039386", + "time": "17:00" + }, + { + "email": "evanderbarbella627525@outlook.com", + "pass": "tegvkl14575", + "time": "17:00" + }, + { + "email": "deanadanovich3536@outlook.com", + "pass": "fpqvn69939", + "time": "17:00" + }, + { + "email": "infantholic0674@outlook.com", + "pass": "vtwiq39090", + "time": "17:00" + }, + { + "email": "taurusnessim21681@outlook.com", + "pass": "yxneoq21892", + "time": "17:00" + }, + { + "email": "domoniqueceddia6429@outlook.com", + "pass": "miupp010517", + "time": "17:00" + }, + { + "email": "lunacarthel0791@outlook.com", + "pass": "tpjdcl105635", + "time": "17:00" + }, + { + "email": "ethylerenon59963@outlook.com", + "pass": "zkrvy39970", + "time": "17:00" + }, + { + "email": "cleoladeinlein305075@outlook.com", + "pass": "atsmqk610359", + "time": "17:00" + }, + { + "email": "HenrettaRinge28158@outlook.com", + "pass": "uamzlr33349", + "time": "18:00" + }, + { + "email": "hakeembehuniak831061@outlook.com", + "pass": "jyvto19041", + "time": "18:00" + }, + { + "email": "arvillafraiman121079@outlook.com", + "pass": "doyeo252319", + "time": "18:00" + }, + { + "email": "keeshaarano83550@outlook.com", + "pass": "ckhre78500", + "time": "18:00" + }, + { + "email": "hadendigaetano217756@outlook.com", + "pass": "rhdxy942851", + "time": "18:00" + }, + { + "email": "rachealjungwirth5068@outlook.com", + "pass": "owrzau581974", + "time": "18:00" + }, + { + "email": "chaunceystraten778357@outlook.com", + "pass": "yohyx730685", + "time": "18:00" + }, + { + "email": "merlehoellein453331@outlook.com", + "pass": "uosdx13087", + "time": "18:00" + }, + { + "email": "bethzychustz14832@outlook.com", + "pass": "ugjlpc339494", + "time": "18:00" + }, + { + "email": "bobbyareizaga59996@outlook.com", + "pass": "urcyl241089", + "time": "18:00" + }, + { + "email": "jailenemargaret2501@outlook.com", + "pass": "ktfzl44644", + "time": "18:00" + }, + { + "email": "allenerzewnicki78140@outlook.com", + "pass": "biyjic28611", + "time": "18:00" + }, + { + "email": "brooksriechel39493@outlook.com", + "pass": "dliou83372", + "time": "18:00" + }, + { + "email": "adellafonoti8174@outlook.com", + "pass": "bmxyv574450", + "time": "18:00" + }, + { + "email": "valeriaburnight552949@outlook.com", + "pass": "gxktvv35276", + "time": "18:00" + }, + { + "email": "DarryleOdden7473@outlook.com", + "pass": "idduo68897", + "time": "18:00" + }, + { + "email": "VondaPortuguez1515@outlook.com", + "pass": "zhvdx670781", + "time": "18:00" + }, + { + "email": "NicholeAntrim0981@outlook.com", + "pass": "cuyqrb70058", + "time": "18:00" + }, + { + "email": "SuzyBerg719442@outlook.com", + "pass": "hwscn039103", + "time": "18:00" + }, + { + "email": "GenoMansmann1764@outlook.com", + "pass": "kfqbu570168", + "time": "18:00" + }, + { + "email": "clemmabillbe4904@outlook.com", + "pass": "wpgcy47618", + "time": "18:00" + }, + { + "email": "shaneshull83092@outlook.com", + "pass": "tliqib914514", + "time": "18:00" + }, + { + "email": "purlklamerus0607@outlook.com", + "pass": "lhang765285", + "time": "18:00" + }, + { + "email": "montserratbroman6594@outlook.com", + "pass": "xaqewv073727", + "time": "18:00" + }, + { + "email": "rebajohnsrud9887@outlook.com", + "pass": "wdipp13167", + "time": "18:00" + }, + { + "email": "DollieHorlacher7730@outlook.com", + "pass": "fmnex013968", + "time": "18:00" + }, + { + "email": "rowenauhlmann5918@outlook.com", + "pass": "cszoyh41283", + "time": "18:00" + }, + { + "email": "cathisybert70805@outlook.com", + "pass": "rirnxu94266", + "time": "19:00" + }, + { + "email": "shyannheelan5123@outlook.com", + "pass": "tprbeg32450", + "time": "19:00" + }, + { + "email": "acykapaun467272@outlook.com", + "pass": "ixoop08510", + "time": "19:00" + }, + { + "email": "robbinkaumans4572@outlook.com", + "pass": "csikc484507", + "time": "19:00" + }, + { + "email": "mahaliaharried382404@outlook.com", + "pass": "vpvoh74507", + "time": "19:00" + }, + { + "email": "cieravernali671598@outlook.com", + "pass": "gsgxgd086897", + "time": "19:00" + }, + { + "email": "adonishensarling61713@outlook.com", + "pass": "qlgof10419", + "time": "19:00" + }, + { + "email": "dawnknell998824@outlook.com", + "pass": "pgefct935745", + "time": "19:00" + }, + { + "email": "arvoturvin83563@outlook.com", + "pass": "igqnil247226", + "time": "19:00" + }, + { + "email": "oriemakupson218842@outlook.com", + "pass": "zmoabx33124", + "time": "19:00" + }, + { + "email": "kyreedieckow7901@outlook.com", + "pass": "pmzzu65219", + "time": "19:00" + }, + { + "email": "lorainegiovannetti6825@outlook.com", + "pass": "diaoc938963", + "time": "19:00" + }, + { + "email": "starlingleveston58640@outlook.com", + "pass": "znexga789691", + "time": "19:00" + }, + { + "email": "ethanshader306313@outlook.com", + "pass": "dxizf426739", + "time": "19:00" + }, + { + "email": "jimenaaprea6736@outlook.com", + "pass": "edezix035724", + "time": "19:00" + }, + { + "email": "olympiaklement787355@outlook.com", + "pass": "tvodb90021", + "time": "19:00" + }, + { + "email": "AlfredKillip6301@outlook.com", + "pass": "nbtelc959469", + "time": "19:00" + }, + { + "email": "OmieBrucculeri2961@outlook.com", + "pass": "erfbss554372", + "time": "19:00" + }, + { + "email": "GustaveLemmerman79866@outlook.com", + "pass": "odvsth016364", + "time": "19:00" + }, + { + "email": "AaronMagagna54473@outlook.com", + "pass": "ggkhv130802", + "time": "19:00" + }, + { + "email": "MarcieRidall93135@outlook.com", + "pass": "uwtzq32940", + "time": "19:00" + }, + { + "email": "TrevonForshee56549@outlook.com", + "pass": "qimbi067337", + "time": "19:00" + }, + { + "email": "adalinelemmerman579445@outlook.com", + "pass": "mdupcn978016", + "time": "19:00" + }, + { + "email": "PerlaFermanian72330@outlook.com", + "pass": "flhbh569985", + "time": "19:00" + } +]; \ No newline at end of file diff --git a/fixed-site-replacer-main/nagoya-accounts.js b/fixed-site-replacer-main/nagoya-accounts.js new file mode 100644 index 0000000..0365009 --- /dev/null +++ b/fixed-site-replacer-main/nagoya-accounts.js @@ -0,0 +1,302 @@ +const NAGOYA_ACCOUNTS = [ + { + "email": "BritneySmithamucst@hotmail.com", + "pass": "Pl012150", + "time": "11:00" + }, + { + "email": "SchlieperTombs663@outlook.com", + "pass": "Kx789719", + "time": "11:00" + }, + { + "email": "RondeNoblewoman7262@outlook.com", + "pass": "Kc839781", + "time": "11:00" + }, + { + "email": "LouveniaHegmannbcjn@hotmail.com", + "pass": "Ut525273", + "time": "11:00" + }, + { + "email": "NormentChimenti20@outlook.com", + "pass": "Ho621820", + "time": "11:00" + }, + { + "email": "ArauzKrysiak0022@outlook.com", + "pass": "Hl210773", + "time": "11:00" + }, + { + "email": "RoetzlerLenske9131@outlook.com", + "pass": "Xz915133", + "time": "11:00" + }, + { + "email": "CherubinoManusyants1823@outlook.com", + "pass": "Rr093458", + "time": "11:00" + }, + { + "email": "HabyCena5436@outlook.com", + "pass": "Nf474656", + "time": "11:00" + }, + { + "email": "MankoSaulnier74@outlook.com", + "pass": "Hh873737", + "time": "11:00" + }, + { + "email": "TetersElmo602@outlook.com", + "pass": "Vj255440", + "time": "11:00" + }, + { + "email": "SchoemerPerrins2672@outlook.com", + "pass": "Sm610507", + "time": "11:00" + }, + { + "email": "LawriePloude805@outlook.com", + "pass": "Na391011", + "time": "11:00" + }, + { + "email": "GummoSeburg0078@outlook.com", + "pass": "Oh213935", + "time": "11:00" + }, + { + "email": "BezEyre3289@outlook.com", + "pass": "Ee102928", + "time": "11:00" + }, + { + "email": "ClementKassulkemiy@hotmail.com", + "pass": "Pl566991", + "time": "13:30" + }, + { + "email": "VogelzangMaw95@outlook.com", + "pass": "Pq675273", + "time": "13:30" + }, + { + "email": "OppermanSmida9752@outlook.com", + "pass": "Gf653737", + "time": "13:30" + }, + { + "email": "StrongGaribai451@outlook.com", + "pass": "Wx622773", + "time": "13:30" + }, + { + "email": "CadaviecoFromberg4317@outlook.com", + "pass": "Ep499850", + "time": "13:30" + }, + { + "email": "FurnerKirchmeier975@outlook.com", + "pass": "Mg446603", + "time": "13:30" + }, + { + "email": "RhodaBueter50@outlook.com", + "pass": "Jt323937", + "time": "13:30" + }, + { + "email": "RasburyBriones3033@outlook.com", + "pass": "Dw218230", + "time": "13:30" + }, + { + "email": "MenckeKurelko68@outlook.com", + "pass": "Yc767491", + "time": "13:30" + }, + { + "email": "CassidyVolkmandk@hotmail.com", + "pass": "Us142866", + "time": "13:30" + }, + { + "email": "PerreraHinzman56@outlook.com", + "pass": "Tb107484", + "time": "13:30" + }, + { + "email": "AitchisonColetti78@outlook.com", + "pass": "Tz696603", + "time": "13:30" + }, + { + "email": "CelisFurgeson5249@outlook.com", + "pass": "Hz924031", + "time": "13:30" + }, + { + "email": "CrabtreeCousain8672@outlook.com", + "pass": "Ba448380", + "time": "13:30" + }, + { + "email": "MauraisHinkston096@outlook.com", + "pass": "Bu738292", + "time": "13:30" + }, + { + "email": "CabadaDelmundo579@outlook.com", + "pass": "Hd147321", + "time": "16:00" + }, + { + "email": "AlmenSimons7217@outlook.com", + "pass": "Yp542004", + "time": "16:00" + }, + { + "email": "WesselFliger8960@outlook.com", + "pass": "Tf833595", + "time": "16:00" + }, + { + "email": "FeuchtBrining098@outlook.com", + "pass": "In878325", + "time": "16:00" + }, + { + "email": "LalLamay66@outlook.com", + "pass": "Le666572", + "time": "16:00" + }, + { + "email": "SonyaPacochaeyhou@hotmail.com", + "pass": "Te434832", + "time": "16:00" + }, + { + "email": "AubreyBrady069@outlook.com", + "pass": "Tv006007", + "time": "16:00" + }, + { + "email": "DarcieVigor243@outlook.com", + "pass": "Jd881147", + "time": "16:00" + }, + { + "email": "LuevanoYacavone002@outlook.com", + "pass": "We117645", + "time": "16:00" + }, + { + "email": "MalstromRellihan4771@outlook.com", + "pass": "Zo820195", + "time": "16:00" + }, + { + "email": "RobbBarges952@outlook.com", + "pass": "Ec392147", + "time": "16:00" + }, + { + "email": "LintsLohn687@outlook.com", + "pass": "Ri607475", + "time": "16:00" + }, + { + "email": "JuniperCribb2453@outlook.com", + "pass": "Am225783", + "time": "16:00" + }, + { + "email": "HarlandJuckett6737@outlook.com", + "pass": "Cc439113", + "time": "16:00" + }, + { + "email": "AnitaLeuschkecoi@hotmail.com", + "pass": "Ox982365", + "time": "16:00" + }, + { + "email": "NehaCaspereh@hotmail.com", + "pass": "Iv445364", + "time": "18:30" + }, + { + "email": "DrewTreutelbf@hotmail.com", + "pass": "Kz932859", + "time": "18:30" + }, + { + "email": "JanaHanemtr@hotmail.com", + "pass": "Ks396834", + "time": "18:30" + }, + { + "email": "EllenDenesikshrw@hotmail.com", + "pass": "Ud476658", + "time": "18:30" + }, + { + "email": "BaloghChaudhry754@outlook.com", + "pass": "Eo610154", + "time": "18:30" + }, + { + "email": "DockenMagness698@outlook.com", + "pass": "Bi209550", + "time": "18:30" + }, + { + "email": "AceboBoughan770@outlook.com", + "pass": "Fd322015", + "time": "18:30" + }, + { + "email": "SchisslerDorame805@outlook.com", + "pass": "Ra102622", + "time": "18:30" + }, + { + "email": "WantSpier882@outlook.com", + "pass": "Ko480705", + "time": "18:30" + }, + { + "email": "BatesonShow057@outlook.com", + "pass": "Pk255318", + "time": "18:30" + }, + { + "email": "ParisDenesikdaq@hotmail.com", + "pass": "Ut936530", + "time": "18:30" + }, + { + "email": "ClaudetteKorfhage4548@outlook.com", + "pass": "Ji731430", + "time": "18:30" + }, + { + "email": "MyricksLuttmer676@outlook.com", + "pass": "Rh350294", + "time": "18:30" + }, + { + "email": "TianaFeilhq@hotmail.com", + "pass": "Fe823107", + "time": "18:30" + }, + { + "email": "RocchiPratcher97@outlook.com", + "pass": "Nu004568", + "time": "18:30" + } +]; \ No newline at end of file diff --git a/fixed-site-replacer-main/namco-parks-rename.user.js b/fixed-site-replacer-main/namco-parks-rename.user.js new file mode 100644 index 0000000..f16c354 --- /dev/null +++ b/fixed-site-replacer-main/namco-parks-rename.user.js @@ -0,0 +1,1155 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息(手机版) +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.3 +// @description 改会员资料姓名/生日;面板自动显示;调试版:角落显示运行状态 +// @grant unsafeWindow +// @author park-tools +// @match https://parks2.bandainamco-am.co.jp/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// @grant GM_setValue +// @grant GM_getValue +// @grant GM_deleteValue +// ==/UserScript== + +(function () { + 'use strict'; + + // ========== 调试标记(v1.6.3,定位“页面无任何展示元素”用)========== + function npDebug(msg, isError) { + try { + if (typeof console !== 'undefined' && console.log) console.log('[namco-debug]', msg); + var badge = document.getElementById('npDebugBadge'); + if (!badge) { + badge = document.createElement('div'); + badge.id = 'npDebugBadge'; + badge.style.cssText = + 'position:fixed;top:6px;left:6px;z-index:2147483647;background:' + + (isError ? '#b00020' : '#059669') + + ';color:#fff;font-size:11px;line-height:1.4;padding:4px 8px;border-radius:6px;' + + 'max-width:85vw;white-space:pre-wrap;word-break:break-all;font-family:-apple-system,sans-serif;box-shadow:0 2px 8px rgba(0,0,0,.3);'; + try { + document.documentElement.appendChild(badge); + } catch (e) { /* ignore */ } + } + if (badge) badge.textContent = (isError ? '❌ ' : '🔧 ') + msg; + } catch (e) { /* 调试代码自身异常不影响主逻辑 */ } + } + window.addEventListener('error', function (ev) { + npDebug('脚本错误: ' + (ev && ev.message ? ev.message : '未知') + (ev && ev.lineno ? ' (行 ' + ev.lineno + ')' : ''), true); + }); + npDebug('脚本已注入 @ ' + location.hostname); + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function shouldHidePluginUi() { + return false; + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + /** 底部姓名+EC 的 dl(排除整理券番号那个 dl) */ + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + /** 找到或创建券面姓名节点(手机端使用済み时官方可能不输出姓名) */ + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + /** 券面/入場页姓名节点(排除 EC- 编号、整理券纯数字) */ + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + SEX: sex, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + SEX: sex, + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input{width:100%;height:42px;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;} +.np-row input:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名与生日,无 SMS(手机号不变)。
    面板随页面自动打开,无需任何操作;关闭后可用右下角按钮 / 长按顶部 2 秒 / 三击顶部重新打开

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日)。官网编辑页生日虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + npDebug('UI 元素已注入 @ ' + location.hostname + location.pathname); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = ''; // v1.6.1: 修改按钮常驻显示,不受任何设置影响 + refreshOverlayBadge(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim(), + $('#npFull', root).value.trim() || $('#npOverlayName', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + refreshPluginUiVisibility(); + } + + function openPanel(noMask) { + if (!noMask) mask.style.display = 'block'; + panel.classList.add('open'); + const draft = store.get(LS_KEY, {}); + if (draft.full) $('#npFull', root).value = draft.full; + if (draft.l) $('#npL', root).value = draft.l; + if (draft.f) $('#npF', root).value = draft.f; + if (draft.lk) $('#npLk', root).value = draft.lk; + if (draft.fk) $('#npFk', root).value = draft.fk; + if (draft.birthday) $('#npBirthday', root).value = draft.birthday; + loadOverlayToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + store.set(LS_KEY, { + full: $('#npFull', root).value, + l: $('#npL', root).value, + f: $('#npF', root).value, + lk: $('#npLk', root).value, + fk: $('#npFk', root).value, + birthday: $('#npBirthday', root).value, + }); + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + /** 触发:长按页面顶部 2 秒 / 快速三击顶部 */ + (function setupTopTriggers() { + const HOLD_MS = 2000; + const TOP_ZONE = 100; + const TRIPLE_MS = 500; + const TRIPLE_SPREAD = 40; + let holdTimer = null; + let startY = 0; + let touchStartTime = 0; + let tripleTimes = []; + + function clearHold() { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; + } + } + + function openFromTop() { + if (!panel.classList.contains('open')) openPanel(); + } + + function beginHold(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + clearHold(); + startY = clientY; + touchStartTime = Date.now(); + holdTimer = setTimeout(() => { + holdTimer = null; + openFromTop(); + }, HOLD_MS); + } + + function moveHold(clientY) { + if (!holdTimer) return; + if (Math.abs(clientY - startY) > 20 || clientY > TOP_ZONE + 20) { + clearHold(); + } + } + + /** 长按结束时的兜底:若计时器被系统打断(如长按文字弹放大镜/链接菜单)但按住时长已够,仍然打开 */ + function endHold(clientY) { + if (holdTimer) { + clearHold(); + return; + } + if ( + clientY <= TOP_ZONE && + Date.now() - touchStartTime >= HOLD_MS && + !panel.classList.contains('open') + ) { + openFromTop(); + } + } + + /** 快速三击顶部(鼠标事件在 iOS 上由点击合成,一次点击只记一次) */ + function recordTopTap(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + const now = Date.now(); + tripleTimes = tripleTimes.filter((t) => now - t.time <= TRIPLE_MS); + tripleTimes.push({ time: now, y: clientY }); + if (tripleTimes.length < 3) return; + const ys = tripleTimes.map((t) => t.y); + const spread = Math.max(...ys) - Math.min(...ys); + tripleTimes = []; + if (spread <= TRIPLE_SPREAD) openFromTop(); + } + + // 捕获阶段监听,避免页面自身 touch 处理拦截事件 + document.addEventListener( + 'touchstart', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + beginHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchmove', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + moveHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + clearHold(); + if (t) endHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener('touchcancel', clearHold, { capture: true, passive: true }); + + document.addEventListener('mousedown', (e) => beginHold(e.clientY)); + document.addEventListener('mousemove', (e) => moveHold(e.clientY)); + document.addEventListener('mouseup', (e) => { + clearHold(); + recordTopTap(e.clientY); + }); + document.addEventListener('mouseleave', clearHold); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在上方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // v1.6.2:页面加载即自动打开设置面板,无需任何唤起动作;券面页不弹(避免挡住给店员看的券面) + if (!isTicketPage()) { + openPanel(true); + npDebug('UI 初始化完成,面板已自动打开'); + } else { + npDebug('券面页:面板不自动弹(右下角按钮可用)'); + } + + $('#npSplit', root).addEventListener('click', () => { + const { l, f } = splitFullName($('#npFull', root).value); + $('#npL', root).value = l; + $('#npF', root).value = f; + log(`已拆分:姓「${l}」名「${f}」`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + const dn = `${after.last_name} ${after.first_name}`.trim(); + if (dn) { + $('#npOverlayName', root).value = dn; + if (!$('#npOverlayOn', root).checked) { + $('#npOverlayOn', root).checked = true; + } + saveOverlayFromUI(); + } + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); diff --git a/fixed-site-replacer-main/namco2.js b/fixed-site-replacer-main/namco2.js new file mode 100644 index 0000000..f5958cb --- /dev/null +++ b/fixed-site-replacer-main/namco2.js @@ -0,0 +1,1151 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息(手机版)2 +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.5 +// @description 改会员资料姓名/生日;全站版;调试标记;零依赖(无 @grant,与测试脚本同格式) +// @author park-tools +// @match *://*/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// ==/UserScript== + +(function () { + 'use strict'; + + // ========== 调试标记(v1.6.3,定位“页面无任何展示元素”用)========== + function npDebug(msg, isError) { + try { + if (typeof console !== 'undefined' && console.log) console.log('[namco-debug]', msg); + var badge = document.getElementById('npDebugBadge'); + if (!badge) { + badge = document.createElement('div'); + badge.id = 'npDebugBadge'; + badge.style.cssText = + 'position:fixed;top:6px;left:6px;z-index:2147483647;background:' + + (isError ? '#b00020' : '#059669') + + ';color:#fff;font-size:11px;line-height:1.4;padding:4px 8px;border-radius:6px;' + + 'max-width:85vw;white-space:pre-wrap;word-break:break-all;font-family:-apple-system,sans-serif;box-shadow:0 2px 8px rgba(0,0,0,.3);'; + try { + document.documentElement.appendChild(badge); + } catch (e) { /* ignore */ } + } + if (badge) badge.textContent = (isError ? '❌ ' : '🔧 ') + msg; + } catch (e) { /* 调试代码自身异常不影响主逻辑 */ } + } + window.addEventListener('error', function (ev) { + npDebug('脚本错误: ' + (ev && ev.message ? ev.message : '未知') + (ev && ev.lineno ? ' (行 ' + ev.lineno + ')' : ''), true); + }); + npDebug('脚本已注入 @ ' + location.hostname); + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function shouldHidePluginUi() { + return false; + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + /** 底部姓名+EC 的 dl(排除整理券番号那个 dl) */ + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + /** 找到或创建券面姓名节点(手机端使用済み时官方可能不输出姓名) */ + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + /** 券面/入場页姓名节点(排除 EC- 编号、整理券纯数字) */ + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + SEX: sex, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + SEX: sex, + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input{width:100%;height:42px;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;} +.np-row input:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名与生日,无 SMS(手机号不变)。
    面板随页面自动打开,无需任何操作;关闭后可用右下角按钮 / 长按顶部 2 秒 / 三击顶部重新打开

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日)。官网编辑页生日虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + npDebug('UI 元素已注入 @ ' + location.hostname + location.pathname); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = ''; // v1.6.1: 修改按钮常驻显示,不受任何设置影响 + refreshOverlayBadge(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim(), + $('#npFull', root).value.trim() || $('#npOverlayName', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + refreshPluginUiVisibility(); + } + + function openPanel(noMask) { + if (!noMask) mask.style.display = 'block'; + panel.classList.add('open'); + const draft = store.get(LS_KEY, {}); + if (draft.full) $('#npFull', root).value = draft.full; + if (draft.l) $('#npL', root).value = draft.l; + if (draft.f) $('#npF', root).value = draft.f; + if (draft.lk) $('#npLk', root).value = draft.lk; + if (draft.fk) $('#npFk', root).value = draft.fk; + if (draft.birthday) $('#npBirthday', root).value = draft.birthday; + loadOverlayToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + store.set(LS_KEY, { + full: $('#npFull', root).value, + l: $('#npL', root).value, + f: $('#npF', root).value, + lk: $('#npLk', root).value, + fk: $('#npFk', root).value, + birthday: $('#npBirthday', root).value, + }); + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + /** 触发:长按页面顶部 2 秒 / 快速三击顶部 */ + (function setupTopTriggers() { + const HOLD_MS = 2000; + const TOP_ZONE = 100; + const TRIPLE_MS = 500; + const TRIPLE_SPREAD = 40; + let holdTimer = null; + let startY = 0; + let touchStartTime = 0; + let tripleTimes = []; + + function clearHold() { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; + } + } + + function openFromTop() { + if (!panel.classList.contains('open')) openPanel(); + } + + function beginHold(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + clearHold(); + startY = clientY; + touchStartTime = Date.now(); + holdTimer = setTimeout(() => { + holdTimer = null; + openFromTop(); + }, HOLD_MS); + } + + function moveHold(clientY) { + if (!holdTimer) return; + if (Math.abs(clientY - startY) > 20 || clientY > TOP_ZONE + 20) { + clearHold(); + } + } + + /** 长按结束时的兜底:若计时器被系统打断(如长按文字弹放大镜/链接菜单)但按住时长已够,仍然打开 */ + function endHold(clientY) { + if (holdTimer) { + clearHold(); + return; + } + if ( + clientY <= TOP_ZONE && + Date.now() - touchStartTime >= HOLD_MS && + !panel.classList.contains('open') + ) { + openFromTop(); + } + } + + /** 快速三击顶部(鼠标事件在 iOS 上由点击合成,一次点击只记一次) */ + function recordTopTap(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + const now = Date.now(); + tripleTimes = tripleTimes.filter((t) => now - t.time <= TRIPLE_MS); + tripleTimes.push({ time: now, y: clientY }); + if (tripleTimes.length < 3) return; + const ys = tripleTimes.map((t) => t.y); + const spread = Math.max(...ys) - Math.min(...ys); + tripleTimes = []; + if (spread <= TRIPLE_SPREAD) openFromTop(); + } + + // 捕获阶段监听,避免页面自身 touch 处理拦截事件 + document.addEventListener( + 'touchstart', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + beginHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchmove', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + moveHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + clearHold(); + if (t) endHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener('touchcancel', clearHold, { capture: true, passive: true }); + + document.addEventListener('mousedown', (e) => beginHold(e.clientY)); + document.addEventListener('mousemove', (e) => moveHold(e.clientY)); + document.addEventListener('mouseup', (e) => { + clearHold(); + recordTopTap(e.clientY); + }); + document.addEventListener('mouseleave', clearHold); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在上方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // v1.6.2:页面加载即自动打开设置面板,无需任何唤起动作;券面页不弹(避免挡住给店员看的券面) + if (!isTicketPage()) { + openPanel(true); + npDebug('UI 初始化完成,面板已自动打开'); + } else { + npDebug('券面页:面板不自动弹(右下角按钮可用)'); + } + + $('#npSplit', root).addEventListener('click', () => { + const { l, f } = splitFullName($('#npFull', root).value); + $('#npL', root).value = l; + $('#npF', root).value = f; + log(`已拆分:姓「${l}」名「${f}」`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + const dn = `${after.last_name} ${after.first_name}`.trim(); + if (dn) { + $('#npOverlayName', root).value = dn; + if (!$('#npOverlayOn', root).checked) { + $('#npOverlayOn', root).checked = true; + } + saveOverlayFromUI(); + } + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); diff --git a/fixed-site-replacer-main/namco2.user.js b/fixed-site-replacer-main/namco2.user.js new file mode 100644 index 0000000..f5958cb --- /dev/null +++ b/fixed-site-replacer-main/namco2.user.js @@ -0,0 +1,1151 @@ +// ==UserScript== +// @name NAMCO Parks 改个人信息(手机版)2 +// @namespace https://parks2.bandainamco-am.co.jp/ +// @version 1.6.5 +// @description 改会员资料姓名/生日;全站版;调试标记;零依赖(无 @grant,与测试脚本同格式) +// @author park-tools +// @match *://*/* +// @icon https://parks2.bandainamco-am.co.jp/client_info/BNAM_LBC_EC/view/userweb/favicon.ico +// @run-at document-end +// ==/UserScript== + +(function () { + 'use strict'; + + // ========== 调试标记(v1.6.3,定位“页面无任何展示元素”用)========== + function npDebug(msg, isError) { + try { + if (typeof console !== 'undefined' && console.log) console.log('[namco-debug]', msg); + var badge = document.getElementById('npDebugBadge'); + if (!badge) { + badge = document.createElement('div'); + badge.id = 'npDebugBadge'; + badge.style.cssText = + 'position:fixed;top:6px;left:6px;z-index:2147483647;background:' + + (isError ? '#b00020' : '#059669') + + ';color:#fff;font-size:11px;line-height:1.4;padding:4px 8px;border-radius:6px;' + + 'max-width:85vw;white-space:pre-wrap;word-break:break-all;font-family:-apple-system,sans-serif;box-shadow:0 2px 8px rgba(0,0,0,.3);'; + try { + document.documentElement.appendChild(badge); + } catch (e) { /* ignore */ } + } + if (badge) badge.textContent = (isError ? '❌ ' : '🔧 ') + msg; + } catch (e) { /* 调试代码自身异常不影响主逻辑 */ } + } + window.addEventListener('error', function (ev) { + npDebug('脚本错误: ' + (ev && ev.message ? ev.message : '未知') + (ev && ev.lineno ? ' (行 ' + ev.lineno + ')' : ''), true); + }); + npDebug('脚本已注入 @ ' + location.hostname); + + const ORIGIN = 'https://parks2.bandainamco-am.co.jp'; + const LS_KEY = 'namco_rename_draft_v1'; + const LS_OVERLAY = 'namco_ticket_overlay_v1'; + + const TICKET_PATH_RE = /\/admission_(use_)?ticket\.html/i; + const PAGE = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window; + + function isLoggedInFromDom() { + if (document.querySelector('a[href*="logoff"], a[href*="request=logoff"]')) return true; + const html = document.documentElement.innerHTML; + if (html.includes('ログアウト')) return true; + return !!parseMemberData(html).member_id; + } + + function htmlLooksLoggedIn(html) { + if (!html) return false; + if (html.includes('ログアウト')) return true; + if (parseMemberData(html).member_id) return true; + if (parseInput(html, 'PC_MAIL') && (parseInput(html, 'TEL') || parseInput(html, 'L_NAME'))) return true; + return false; + } + + /** iOS Tampermonkey 沙箱 fetch 不带 Cookie;结果放页面 window,避免把整页 HTML 塞进 DOM 属性被截断 */ + function pageFetch(url, options) { + return new Promise((resolve, reject) => { + const id = '__npFetch_' + Date.now() + '_' + Math.random().toString(36).slice(2); + const opt = { + method: (options && options.method) || 'GET', + credentials: 'same-origin', + redirect: 'follow', + headers: (options && options.headers) || {}, + }; + if (options && options.body) opt.body = options.body; + const win = PAGE; + const script = document.createElement('script'); + script.textContent = + '(function(){var id=' + + JSON.stringify(id) + + ';window[id]={p:1};fetch(' + + JSON.stringify(url) + + ',' + + JSON.stringify(opt) + + ').then(function(r){return r.text().then(function(t){window[id]={s:r.status,u:r.url,t:t};});}).catch(function(e){window[id]={e:String(e&&e.message||e)};});})();'; + document.documentElement.appendChild(script); + script.remove(); + + const start = Date.now(); + const timer = setInterval(() => { + const box = (win && win[id]) || window[id]; + if (box && box.e) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error(box.e)); + return; + } + if (box && typeof box.t === 'string') { + clearInterval(timer); + const out = { status: Number(box.s || 0), url: box.u || url, text: box.t }; + try { delete win[id]; } catch (e) { /* ignore */ } + resolve(out); + return; + } + if (Date.now() - start > 90000) { + clearInterval(timer); + try { delete win[id]; } catch (e) { /* ignore */ } + reject(new Error('请求超时')); + } + }, 40); + }); + } + + async function httpGet(path, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { Referer: referer || ORIGIN + '/member_mypage.html' }; + try { + return await pageFetch(url, { method: 'GET', headers }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'GET', credentials: 'include', headers }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + async function httpPost(path, body, referer) { + const url = path.startsWith('http') ? path : ORIGIN + path; + const headers = { + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + Origin: ORIGIN, + Referer: referer || ORIGIN + '/member_regist.html?request=edit', + }; + const bodyStr = new URLSearchParams(body).toString(); + try { + return await pageFetch(url, { method: 'POST', headers, body: bodyStr }); + } catch (e1) { + try { + const r = await PAGE.fetch(url, { method: 'POST', credentials: 'include', headers, body: bodyStr }); + return { status: r.status, text: await r.text(), url: r.url }; + } catch (e2) { + throw e1; + } + } + } + + const store = { + get(k, def) { + try { + if (typeof GM_getValue === 'function') return GM_getValue(k, def); + } catch (e) { /* ignore */ } + try { + const raw = localStorage.getItem(k); + return raw == null ? def : JSON.parse(raw); + } catch (e2) { + return def; + } + }, + set(k, v) { + try { + if (typeof GM_setValue === 'function') GM_setValue(k, v); + } catch (e) { /* ignore */ } + try { + localStorage.setItem(k, JSON.stringify(v)); + } catch (e2) { /* ignore */ } + }, + }; + + function $(sel, root) { + return (root || document).querySelector(sel); + } + + function escapeRe(name) { + return String(name).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + } + + function parseInput(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'i' + ); + const m = html.match(re); + if (!m) return ''; + const v = m[0].match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseSelected(html, name) { + const sm = html.match( + new RegExp(']*name=["\']' + escapeRe(name) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + if (!sm) return ''; + const opt = sm[1].match(/]*\\bselected\\b[^>]*>/i) || sm[1].match(/]*selected=["']selected["'][^>]*>/i); + if (!opt) return ''; + const v = opt[0].match(/\\bvalue=["']([^"']*)["']/i); + return v ? v[1].trim() : ''; + } + + function parseCheckedRadio(html, name) { + const re = new RegExp( + ']*\\bname=["\']' + escapeRe(name) + '["\'][^>]*>', + 'gi' + ); + let m; + while ((m = re.exec(html))) { + const tag = m[0]; + if (!/\bchecked\b/i.test(tag)) continue; + const v = tag.match(/\bvalue=["']([^"']*)["']/i); + return v ? v[1] : ''; + } + return ''; + } + + function parseFormChunk(html, formName) { + const head = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>', 'i') + ); + const body = html.match( + new RegExp(']*name=["\']' + escapeRe(formName) + '["\'][^>]*>([\\s\\S]*?)', 'i') + ); + let action = ''; + if (head) { + const am = head[0].match(/\baction=["']([^"']+)/i); + if (am) action = am[1]; + } + return { action, chunk: body ? body[1] : '' }; + } + + function parseHiddenFields(chunk) { + const fields = {}; + const re = /]*>/gi; + let m; + while ((m = re.exec(chunk))) { + const tag = m[0]; + if (!/type=["']hidden["']/i.test(tag) && !/type=["']checkbox["']/i.test(tag) && !/type=["']radio["']/i.test(tag)) { + const nm = tag.match(/\bname=["']([^"']+)["']/i); + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + if (nm && !/^jp\.co\.interfactory\.framework\./i.test(nm[1])) { + fields[nm[1]] = vm ? vm[1] : ''; + } + continue; + } + const nm = tag.match(/\bname=["']([^"']+)["']/i); + if (!nm) continue; + const name = nm[1]; + if (/^jp\.co\.interfactory\.framework\./i.test(name)) continue; + if (/type=["']checkbox["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : '1'; + } + continue; + } + if (/type=["']radio["']/i.test(tag)) { + if (/\bchecked\b/i.test(tag)) { + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + continue; + } + const vm = tag.match(/\bvalue=["']([^"']*)["']/i); + fields[name] = vm ? vm[1] : ''; + } + const selRe = /]*name=["']([^"']+)["'][^>]*>([\s\S]*?)<\/select>/gi; + let sm; + while ((sm = selRe.exec(chunk))) { + const name = sm[1]; + const opt = sm[2].match(/]*\bselected\b[^>]*>/i); + if (!opt) continue; + const v = opt[0].match(/\bvalue=["']([^"']*)["']/i); + fields[name] = v ? v[1] : ''; + } + return fields; + } + + function extractParksError(html) { + const text = html || ''; + const pats = [ + /form-error-message[\s\S]*?
  • ([^<]+)/i, + /]*>([^<]{4,200})<\/li>/i, + /class="error[^"]*"[^>]*>([^<]+)/i, + /errorMessage[^>]*>([^<]+)/i, + ]; + for (let i = 0; i < pats.length; i++) { + const m = text.match(pats[i]); + if (m && m[1] && m[1].trim()) return m[1].replace(/\s+/g, ' ').trim(); + } + if (text.includes('セッションがタイムアウト') || text.includes('セキュリティのため')) { + return '会话超时,请刷新页面重新登录后再提交'; + } + return ''; + } + + function looksExecuteSuccess(resp) { + const t = (resp && resp.text) || ''; + const u = (resp && resp.url) || ''; + if (t.includes('会員情報を更新しました')) return true; + if (t.includes('form-message') && t.includes('更新しました')) return true; + if (u.includes('member_regist_confirm') && t.includes('更新')) return true; + return false; + } + + function parseToken(html) { + const m = (html || '').match(/name="token"\s+value="([0-9a-f]+)"/i); + return m ? m[1] : ''; + } + + function parseMemberData(html) { + const m = (html || '').match(/var\s+member_data\s*=\s*(\{[\s\S]*?\})\s*;/); + if (!m) return {}; + try { + return JSON.parse(m[1]); + } catch (e) { + return {}; + } + } + + function parseProfile(html) { + const md = parseMemberData(html); + let tel = parseInput(html, 'TEL'); + if (!tel) { + const tm = html.match(/(?= 2) return { l: parts[0], f: parts.slice(1).join(' ') }; + return { l: s.charAt(0), f: s.slice(1) || s }; + } + return { l: s.charAt(0), f: s.slice(1) }; + } + + function getOverlayConfig() { + return store.get(LS_OVERLAY, { enabled: false, displayName: '' }); + } + + function setOverlayConfig(cfg) { + store.set(LS_OVERLAY, cfg); + } + + function shouldHidePluginUi() { + return false; + } + + function buildDisplayName(l, f, full) { + if (full && full.trim()) return full.trim().replace(/\s+/g, ' '); + return `${l || ''} ${f || ''}`.trim(); + } + + function isTicketPage() { + return TICKET_PATH_RE.test(location.pathname + location.search); + } + + /** 底部姓名+EC 的 dl(排除整理券番号那个 dl) */ + function getTicketNameDl() { + const dls = document.querySelectorAll('dl.block-mypage-ticket-detail-code'); + for (let i = 0; i < dls.length; i++) { + const dl = dls[i]; + if (dl.classList.contains('block-mypage-ticket-detail-code-margin-small')) continue; + if (dl.querySelector('dd.block-mypage-ticket-detail-code-value')) return dl; + } + return null; + } + + function injectOverlayStyles() { + const css = + 'dd[data-np-overlay="1"],dd.np-injected-name{' + + 'display:block!important;visibility:visible!important;opacity:1!important;' + + '-webkit-text-fill-color:currentColor!important}'; + let st = document.getElementById('np-overlay-style'); + if (!st) { + st = document.createElement('style'); + st.id = 'np-overlay-style'; + document.head.appendChild(st); + } + st.textContent = css; + } + + /** 找到或创建券面姓名节点(手机端使用済み时官方可能不输出姓名) */ + function ensureNameSlot() { + const dl = getTicketNameDl(); + if (!dl) return null; + let nameDd = null; + dl.querySelectorAll('dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (nameDd) return; + const t = (dd.textContent || '').trim(); + if (!/^EC-\d/i.test(t) && !/^\d+$/.test(t)) nameDd = dd; + }); + if (!nameDd) { + nameDd = document.createElement('dd'); + nameDd.className = 'block-mypage-coupon-list-item-code-value np-injected-name'; + const ec = dl.querySelector('dd.block-mypage-ticket-detail-code-value'); + if (ec) dl.insertBefore(nameDd, ec); + else dl.appendChild(nameDd); + } + return nameDd; + } + + /** 券面/入場页姓名节点(排除 EC- 编号、整理券纯数字) */ + function findTicketNameNodes(scope, createIfMissing) { + const root = scope || document; + const nodes = []; + const seen = new Set(); + if (createIfMissing) { + const slot = ensureNameSlot(); + if (slot && !seen.has(slot)) { + seen.add(slot); + nodes.push(slot); + } + } + root.querySelectorAll('dl.block-mypage-ticket-detail-code dd.block-mypage-coupon-list-item-code-value').forEach((dd) => { + if (seen.has(dd)) return; + const t = (dd.textContent || '').trim(); + if (/^EC-\d/i.test(t)) return; + if (/^\d+$/.test(t)) return; + seen.add(dd); + nodes.push(dd); + }); + return nodes; + } + + function restoreTicketNames() { + document.querySelectorAll('dd.np-injected-name').forEach((el) => el.remove()); + findTicketNameNodes(document, false).forEach((el) => { + if (el.dataset.npOrig != null) { + el.textContent = el.dataset.npOrig; + delete el.dataset.npPatched; + delete el.dataset.npOverlay; + } + }); + } + + function applyTicketOverlay(force) { + const cfg = getOverlayConfig(); + if (!cfg.enabled || !cfg.displayName) { + restoreTicketNames(); + return 0; + } + if (!isTicketPage() && !force) return 0; + injectOverlayStyles(); + let n = 0; + const nodes = findTicketNameNodes(document, true); + nodes.forEach((el) => { + const cur = (el.textContent || '').trim(); + if (el.dataset.npOrig == null && cur && cur !== cfg.displayName) { + el.dataset.npOrig = cur; + } + if (cur !== cfg.displayName || el.dataset.npPatched !== '1') { + el.textContent = cfg.displayName; + el.dataset.npOverlay = '1'; + el.dataset.npPatched = '1'; + n += 1; + } + }); + return n; + } + + function startOverlayWatcher() { + if (window.__npOverlayWatcher) return; + window.__npOverlayWatcher = true; + + const run = () => { + if (!getOverlayConfig().enabled) return; + applyTicketOverlay(); + }; + + run(); + document.addEventListener('DOMContentLoaded', run); + window.addEventListener('load', run); + window.addEventListener('pageshow', run); + + const mo = new MutationObserver(() => { + if (!getOverlayConfig().enabled) return; + clearTimeout(window.__npOverlayTimer); + window.__npOverlayTimer = setTimeout(run, 80); + }); + mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); + + let lastUrl = location.href; + setInterval(() => { + if (location.href !== lastUrl) { + lastUrl = location.href; + setTimeout(run, 100); + } + }, 500); + } + + startOverlayWatcher(); + + async function checkLoggedIn() { + if (isLoggedInFromDom()) return true; + try { + const r = await httpGet('/member_mypage.html'); + return htmlLooksLoggedIn(r.text); + } catch (e) { + return isLoggedInFromDom(); + } + } + + async function loadProfile() { + await httpGet('/member_mypage.html'); + const r = await httpGet('/member_regist.html?request=edit'); + if (!htmlLooksLoggedIn(r.text)) { + if (isLoggedInFromDom()) { + throw new Error('已登录但读取资料失败,请刷新页面后重试'); + } + throw new Error('未登录:请用 Safari 打开 parks2 并完成登录(不要用无痕模式)'); + } + const p = parseProfile(r.text); + if (!p.tel) throw new Error('未读取到手机号,无法安全提交'); + return p; + } + + function normalizeBirthday(raw) { + const s = String(raw || '').trim(); + if (!s) return ''; + const m1 = s.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (m1) { + return `${m1[1]}-${String(parseInt(m1[2], 10)).padStart(2, '0')}-${String(parseInt(m1[3], 10)).padStart(2, '0')}`; + } + const digits = s.replace(/\D/g, ''); + if (digits.length === 8) { + return `${digits.slice(0, 4)}-${digits.slice(4, 6)}-${digits.slice(6, 8)}`; + } + return ''; + } + + function bdayParts(bday) { + const norm = normalizeBirthday(bday) || '1990-01-01'; + const m = norm.match(/^(\d{4})-(\d{1,2})-(\d{1,2})/); + if (!m) return { y: '1990', mo: '1', d: '1' }; + return { + y: m[1], + mo: String(parseInt(m[2], 10)), + d: String(parseInt(m[3], 10)), + }; + } + + async function updateMemberName(profile, changes, password) { + const ln = changes.last_name || profile.last_name; + const fn = changes.first_name || profile.first_name; + const lk = changes.last_name_kana != null ? changes.last_name_kana : profile.last_name_kana; + const fk = changes.first_name_kana != null ? changes.first_name_kana : profile.first_name_kana; + const nick = changes.nickname != null ? changes.nickname : (profile.nickname || ln); + const bday = normalizeBirthday(changes.birthday || profile.birthday) || profile.birthday || '1990-01-01'; + const { y, mo, d } = bdayParts(bday); + const editRef = ORIGIN + '/member_regist.html?request=edit'; + const zip7 = String(profile.zip || '').replace(/-/g, ''); + const addr1 = profile.addr1 || '東京都'; + const addr2 = profile.addr2 || ''; + const addrStreet = profile.addr_street || ''; + const addr3 = profile.addr3 || ''; + const sex = profile.gender || 'M'; + if (!zip7 || !addr2 || !addrStreet) { + throw new Error('当前资料缺邮编/市区町村/番地(官网已改为必填)。请先在「会員情報変更」填完整地址,再回来改名字。'); + } + + const confirm = Object.assign({}, profile.formFields || {}, { + request: 'confirm', + PC_MAIL_OLD: profile.email, + FOREIGN_LOGIN_PROVIDER_KIND: '', + MOBILE_MAIL_OLD: '', + mode: '1', + CART_MEMBER_REGIST: '', + MAIL_FLG_OLD: '1', + 'SOCIAL_PLUS:SOCIAL_PLUS_ID': '', + 'SOCIAL_PLUS:PROVIDER': '', + NICKNAME: nick, + 'jp.co.interfactory.framework.trim.NICKNAME': '', + PC_MAIL: profile.email, + 'jp.co.interfactory.framework.trim.PC_MAIL': '', + PASSWORD: password, + PASSWORD2: password, + SEX: sex, + BIRTH_YEAR: y, + 'jp.co.interfactory.framework.trim.BIRTH_YEAR': '', + BIRTH_MONTH: mo, + 'jp.co.interfactory.framework.trim.BIRTH_MONTH': '', + BIRTH_DAY: d, + 'jp.co.interfactory.framework.trim.BIRTH_DAY': '', + ZIP: zip7, + 'jp.co.interfactory.framework.trim.ZIP': '', + ADDR1: addr1, + ADDR2: addr2, + 'jp.co.interfactory.framework.trim.ADDR2': '', + 'MEMBER.FREE_ITEM16': addrStreet, + 'jp.co.interfactory.framework.trim.MEMBER.FREE_ITEM16': '', + ADDR3: addr3, + 'jp.co.interfactory.framework.trim.ADDR3': '', + TEL: profile.tel, + 'jp.co.interfactory.framework.trim.TEL': '', + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + 'jp.co.interfactory.framework.trim.L_KANA': '', + F_KANA: fk, + 'jp.co.interfactory.framework.trim.F_KANA': '', + PC_MAIL_TYPE: '1', + MOBILE_MAIL_TYPE: '1', + }); + if (!addr3) confirm['MEMBER.FREE_ITEM19'] = '1'; + else delete confirm['MEMBER.FREE_ITEM19']; + + const r1 = await httpPost('/member_regist.html', confirm, editRef); + if (r1.text.includes('sms_authentication') || r1.url.includes('sms_authentication')) { + throw new Error('触发了 SMS 验证(请勿改手机号)'); + } + const confirmParsed = parseFormChunk(r1.text, 'confirmForm'); + const hidden = parseHiddenFields(confirmParsed.chunk); + const token = hidden.token || parseToken(r1.text); + if (!token) { + throw new Error(extractParksError(r1.text) || 'confirm 失败,请检查密码是否正确'); + } + + const execute = Object.assign({}, hidden, { + request: 'execute', + token, + MAIL_FLG: hidden.MAIL_FLG || '1', + SEX: sex, + BIRTH_YEAR: y, + BIRTH_MONTH: mo, + BIRTH_DAY: d, + BIRTH: y + '/' + mo + '/' + d, + ZIP: zip7 || hidden.ZIP || '', + ADDR1: addr1 || hidden.ADDR1 || '', + ADDR2: addr2 || hidden.ADDR2 || '', + 'MEMBER.FREE_ITEM16': addrStreet || hidden['MEMBER.FREE_ITEM16'] || '', + ADDR3: addr3 || hidden.ADDR3 || '', + TEL: profile.tel, + L_NAME: ln, + F_NAME: fn, + L_KANA: lk, + F_KANA: fk, + NICKNAME: nick, + PC_MAIL: profile.email, + PASSWORD: password, + PASSWORD2: password, + }); + if (addr3) delete execute['MEMBER.FREE_ITEM19']; + else execute['MEMBER.FREE_ITEM19'] = '1'; + + const action = confirmParsed.action || '/member_regist_confirm.html'; + const r2 = await httpPost(action, execute, ORIGIN + '/member_regist.html'); + if (r2.text.includes('sms_authentication') || r2.url.includes('sms_authentication')) { + throw new Error('execute 触发 SMS'); + } + if (!looksExecuteSuccess(r2)) { + const afterEdit = await httpGet('/member_regist.html?request=edit', ORIGIN + '/member_mypage.html'); + const after = parseProfile(afterEdit.text); + if (after.last_name === ln && after.first_name === fn) { + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + throw new Error(extractParksError(r2.text) || 'execute 未返回成功页'); + } + return { + last_name: ln, + first_name: fn, + last_name_kana: lk, + first_name_kana: fk, + birthday: `${y}-${String(mo).padStart(2, '0')}-${String(d).padStart(2, '0')}`, + }; + } + + async function verifyTicketNames() { + const r = await httpGet('/admission_ticket.html'); + const orders = [...r.text.matchAll(/admission_use_ticket\.html\?order_no=(\d+)/g)].map((m) => m[1]); + const tickets = []; + for (const ono of orders) { + const t = await httpGet('/admission_use_ticket.html?order_no=' + ono, ORIGIN + '/admission_ticket.html'); + const m = t.text.match( + /block-mypage-coupon-list-item-code-value">([^<]+)<\/dd>\s*
    (EC-\d+)<\/dd>/s + ); + if (m) tickets.push({ order: ono, ec: m[2], name: m[1].trim() }); + } + const hist = await httpGet('/member_history.html'); + const clients = [...hist.text.matchAll(/ご依頼主<\/dt>\s*]*>\s*([^<]+)/g)].map((m) => m[1].trim()); + const prof = await loadProfile(); + const member = `${prof.last_name} ${prof.first_name}`.trim(); + return { member, tickets, clients, kana: `${prof.last_name_kana} ${prof.first_name_kana}`.trim() }; + } + + /* ---------- UI ---------- */ + const css = ` +#npRenameRoot{all:initial;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;} +#npRenameFab{position:fixed;right:14px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483646;width:54px;height:54px;border-radius:27px;border:none;background:linear-gradient(135deg,#e60012,#b8000f);color:#fff;font-size:14px;font-weight:700;box-shadow:0 4px 16px rgba(0,0,0,.35);cursor:pointer;} +#npRenameMask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:2147483647;display:none;} +#npRenamePanel{position:fixed;left:0;right:0;bottom:0;max-height:88vh;overflow:auto;background:#fff;border-radius:16px 16px 0 0;padding:16px 16px calc(20px + env(safe-area-inset-bottom));z-index:2147483647;transform:translateY(110%);transition:transform .25s ease;box-sizing:border-box;} +#npRenamePanel.open{transform:translateY(0);} +#npRenamePanel *{box-sizing:border-box;font-family:inherit;} +.np-title{font-size:17px;font-weight:700;margin:0 0 4px;color:#111;} +.np-sub{font-size:12px;color:#666;margin:0 0 12px;line-height:1.5;} +.np-warn{font-size:11px;color:#b45309;background:#fffbeb;border:1px solid #fcd34d;border-radius:8px;padding:8px 10px;margin-bottom:12px;line-height:1.45;} +.np-row{margin-bottom:10px;} +.np-row label{display:block;font-size:12px;color:#444;margin-bottom:4px;} +.np-row input{width:100%;height:42px;border:1px solid #ddd;border-radius:8px;padding:0 12px;font-size:16px;} +.np-row input:focus{outline:none;border-color:#e60012;} +.np-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;} +.np-btns{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:12px;} +.np-btn{height:44px;border:none;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;} +.np-btn-primary{background:#e60012;color:#fff;} +.np-btn-secondary{background:#f3f4f6;color:#111;} +.np-btn-full{grid-column:1/-1;} +.np-log{margin-top:12px;font-size:12px;line-height:1.55;color:#333;background:#f9fafb;border-radius:8px;padding:10px;white-space:pre-wrap;max-height:160px;overflow:auto;} +.np-close{position:absolute;right:12px;top:12px;border:none;background:#eee;width:32px;height:32px;border-radius:16px;font-size:18px;cursor:pointer;} +.np-switch-box{background:linear-gradient(135deg,#ecfdf5,#f0fdf4);border:1px solid #6ee7b7;border-radius:12px;padding:12px;margin-bottom:12px;} +.np-switch-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;} +.np-switch-title{font-size:14px;font-weight:700;color:#065f46;} +.np-switch-hint{font-size:11px;color:#047857;line-height:1.45;margin:0 0 8px;} +.np-switch{position:relative;width:52px;height:30px;flex-shrink:0;} +.np-switch input{opacity:0;width:0;height:0;} +.np-switch-slider{position:absolute;inset:0;background:#cbd5e1;border-radius:15px;transition:.2s;cursor:pointer;} +.np-switch-slider:before{content:"";position:absolute;width:24px;height:24px;left:3px;top:3px;background:#fff;border-radius:50%;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.2);} +.np-switch input:checked+.np-switch-slider{background:#059669;} +.np-switch input:checked+.np-switch-slider:before{transform:translateX(22px);} +#npOverlayBadge{position:fixed;left:10px;bottom:calc(18px + env(safe-area-inset-bottom));z-index:2147483645;background:#059669;color:#fff;font-size:11px;padding:6px 10px;border-radius:8px;display:none;max-width:42vw;line-height:1.3;box-shadow:0 2px 8px rgba(0,0,0,.25);} +`; + + const root = document.createElement('div'); + root.id = 'npRenameRoot'; + root.innerHTML = ` + + +
    +
    + +

    NAMCO Parks 改个人信息

    +

    需已登录 parks2。改的是会员资料/会員情報変更中的姓名与生日,无 SMS(手机号不变)。
    面板随页面自动打开,无需任何操作;关闭后可用右下角按钮 / 长按顶部 2 秒 / 三击顶部重新打开

    +
    ⚠ 「提交修改」改服务器会员资料(姓名/生日)。官网编辑页生日虽显示只读,接口可改。「券面强制显示」仅本机浏览器覆盖画面。
    +
    +
    + 券面强制显示 + +
    +

    开启后替换/插入券面姓名。iPhone 使用済み券有时官方不显示姓名,开此开关并填写姓名即可补上;刷新后仍有效。

    +
    + + +
    + +
    +
    + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + + + +
    +
    请先登录 NAMCO,再点「读取当前」。
    +
    +
    `; + document.documentElement.appendChild(root); + npDebug('UI 元素已注入 @ ' + location.hostname + location.pathname); + + const fab = $('#npRenameFab', root); + const mask = $('#npRenameMask', root); + const panel = $('#npRenamePanel', root); + const logEl = $('#npLog', root); + const overlayBadge = $('#npOverlayBadge', root); + + function log(msg) { + logEl.textContent = msg; + } + + function refreshOverlayBadge() { + const cfg = getOverlayConfig(); + if (cfg.enabled && cfg.displayName) { + overlayBadge.style.display = 'block'; + overlayBadge.textContent = '券面强制显示:' + cfg.displayName; + } else { + overlayBadge.style.display = 'none'; + } + } + + function refreshPluginUiVisibility() { + fab.style.display = ''; // v1.6.1: 修改按钮常驻显示,不受任何设置影响 + refreshOverlayBadge(); + } + + function syncOverlayFromForm() { + const name = buildDisplayName( + $('#npL', root).value.trim(), + $('#npF', root).value.trim(), + $('#npFull', root).value.trim() || $('#npOverlayName', root).value.trim() + ); + if (name) $('#npOverlayName', root).value = name; + return name; + } + + function saveOverlayFromUI() { + const enabled = $('#npOverlayOn', root).checked; + const displayName = ($('#npOverlayName', root).value || syncOverlayFromForm()).trim(); + setOverlayConfig({ enabled, displayName }); + refreshPluginUiVisibility(); + if (enabled && displayName) { + findTicketNameNodes(document, true).forEach((el) => { + el.dataset.npOverlay = '1'; + }); + const n = applyTicketOverlay(true); + return { enabled, displayName, patched: n }; + } + return { enabled, displayName, patched: 0 }; + } + + function loadOverlayToUI() { + const cfg = getOverlayConfig(); + $('#npOverlayOn', root).checked = !!cfg.enabled; + if (cfg.displayName) $('#npOverlayName', root).value = cfg.displayName; + refreshPluginUiVisibility(); + } + + function openPanel(noMask) { + if (!noMask) mask.style.display = 'block'; + panel.classList.add('open'); + const draft = store.get(LS_KEY, {}); + if (draft.full) $('#npFull', root).value = draft.full; + if (draft.l) $('#npL', root).value = draft.l; + if (draft.f) $('#npF', root).value = draft.f; + if (draft.lk) $('#npLk', root).value = draft.lk; + if (draft.fk) $('#npFk', root).value = draft.fk; + if (draft.birthday) $('#npBirthday', root).value = draft.birthday; + loadOverlayToUI(); + if (isLoggedInFromDom()) { + log('✅ 当前页已登录\n• 手机没名字:开「券面强制显示」+ 填姓名\n• 必须在「詳細」页(有 EC 号那页),不是列表页'); + } else { + log('⚠ 未检测到登录(改服务器资料才需要)\n• 手机券面没名字:直接开「券面强制显示」填姓名即可'); + } + } + + function closePanel() { + panel.classList.remove('open'); + mask.style.display = 'none'; + store.set(LS_KEY, { + full: $('#npFull', root).value, + l: $('#npL', root).value, + f: $('#npF', root).value, + lk: $('#npLk', root).value, + fk: $('#npFk', root).value, + birthday: $('#npBirthday', root).value, + }); + saveOverlayFromUI(); + } + + fab.addEventListener('click', openPanel); + mask.addEventListener('click', closePanel); + $('#npRenameClose', root).addEventListener('click', closePanel); + + /** 触发:长按页面顶部 2 秒 / 快速三击顶部 */ + (function setupTopTriggers() { + const HOLD_MS = 2000; + const TOP_ZONE = 100; + const TRIPLE_MS = 500; + const TRIPLE_SPREAD = 40; + let holdTimer = null; + let startY = 0; + let touchStartTime = 0; + let tripleTimes = []; + + function clearHold() { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; + } + } + + function openFromTop() { + if (!panel.classList.contains('open')) openPanel(); + } + + function beginHold(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + clearHold(); + startY = clientY; + touchStartTime = Date.now(); + holdTimer = setTimeout(() => { + holdTimer = null; + openFromTop(); + }, HOLD_MS); + } + + function moveHold(clientY) { + if (!holdTimer) return; + if (Math.abs(clientY - startY) > 20 || clientY > TOP_ZONE + 20) { + clearHold(); + } + } + + /** 长按结束时的兜底:若计时器被系统打断(如长按文字弹放大镜/链接菜单)但按住时长已够,仍然打开 */ + function endHold(clientY) { + if (holdTimer) { + clearHold(); + return; + } + if ( + clientY <= TOP_ZONE && + Date.now() - touchStartTime >= HOLD_MS && + !panel.classList.contains('open') + ) { + openFromTop(); + } + } + + /** 快速三击顶部(鼠标事件在 iOS 上由点击合成,一次点击只记一次) */ + function recordTopTap(clientY) { + if (panel.classList.contains('open')) return; + if (clientY > TOP_ZONE) return; + const now = Date.now(); + tripleTimes = tripleTimes.filter((t) => now - t.time <= TRIPLE_MS); + tripleTimes.push({ time: now, y: clientY }); + if (tripleTimes.length < 3) return; + const ys = tripleTimes.map((t) => t.y); + const spread = Math.max(...ys) - Math.min(...ys); + tripleTimes = []; + if (spread <= TRIPLE_SPREAD) openFromTop(); + } + + // 捕获阶段监听,避免页面自身 touch 处理拦截事件 + document.addEventListener( + 'touchstart', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + beginHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchmove', + (e) => { + const t = e.touches && e.touches[0]; + if (!t) return; + moveHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener( + 'touchend', + (e) => { + const t = e.changedTouches && e.changedTouches[0]; + clearHold(); + if (t) endHold(t.clientY); + }, + { capture: true, passive: true } + ); + + document.addEventListener('touchcancel', clearHold, { capture: true, passive: true }); + + document.addEventListener('mousedown', (e) => beginHold(e.clientY)); + document.addEventListener('mousemove', (e) => moveHold(e.clientY)); + document.addEventListener('mouseup', (e) => { + clearHold(); + recordTopTap(e.clientY); + }); + document.addEventListener('mouseleave', clearHold); + })(); + + $('#npOverlayOn', root).addEventListener('change', () => { + const r = saveOverlayFromUI(); + if (r.enabled && !r.displayName) { + log('请先填写「券面显示姓名」'); + $('#npOverlayOn', root).checked = false; + setOverlayConfig({ enabled: false, displayName: '' }); + refreshPluginUiVisibility(); + return; + } + log(r.enabled ? `✅ 券面强制显示已开启:${r.displayName}\n刷新/店员 F5 后会自动再覆盖。` : '券面强制显示已关闭'); + }); + + $('#npOverlayName', root).addEventListener('input', () => { + if ($('#npOverlayOn', root).checked) saveOverlayFromUI(); + }); + + $('#npSyncOverlay', root).addEventListener('click', () => { + const name = syncOverlayFromForm(); + if (!name) { + log('请先在上方填写完整姓名或姓/名'); + return; + } + const r = saveOverlayFromUI(); + log(`券面显示名:${name}${r.enabled ? '(已生效)' : '(请打开开关)'}`); + }); + + loadOverlayToUI(); + refreshPluginUiVisibility(); + if (getOverlayConfig().enabled) applyTicketOverlay(true); + + // v1.6.2:页面加载即自动打开设置面板,无需任何唤起动作;券面页不弹(避免挡住给店员看的券面) + if (!isTicketPage()) { + openPanel(true); + npDebug('UI 初始化完成,面板已自动打开'); + } else { + npDebug('券面页:面板不自动弹(右下角按钮可用)'); + } + + $('#npSplit', root).addEventListener('click', () => { + const { l, f } = splitFullName($('#npFull', root).value); + $('#npL', root).value = l; + $('#npF', root).value = f; + log(`已拆分:姓「${l}」名「${f}」`); + }); + + $('#npLoad', root).addEventListener('click', async () => { + log('读取中…'); + try { + const ok = await checkLoggedIn(); + if (!ok) throw new Error('未登录,请打开网站先登录'); + const p = await loadProfile(); + log( + `当前会员\n氏名:${p.last_name} ${p.first_name}\nカナ:${p.last_name_kana} ${p.first_name_kana}\n生日:${p.birthday}\n手机:${p.tel}\n邮箱:${p.email}` + ); + $('#npL', root).value = p.last_name || ''; + $('#npF', root).value = p.first_name || ''; + if (!$('#npLk', root).value) $('#npLk', root).value = p.last_name_kana || ''; + if (!$('#npFk', root).value) $('#npFk', root).value = p.first_name_kana || ''; + $('#npBirthday', root).value = normalizeBirthday(p.birthday); + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npSubmit', root).addEventListener('click', async () => { + const l = $('#npL', root).value.trim(); + const f = $('#npF', root).value.trim(); + const bdayRaw = $('#npBirthday', root).value.trim(); + const pwd = $('#npPwd', root).value; + if (!l || !f) { + log('请填写姓和名'); + return; + } + if (bdayRaw && !normalizeBirthday(bdayRaw)) { + log('生日格式无效,请用 YYYY-MM-DD'); + return; + } + if (!pwd) { + log('请填写账号密码'); + return; + } + log('提交中…请勿关页面'); + try { + const profile = await loadProfile(); + const changes = { + last_name: l, + first_name: f, + nickname: l, + }; + const lk = $('#npLk', root).value.trim(); + const fk = $('#npFk', root).value.trim(); + if (lk) changes.last_name_kana = lk; + if (fk) changes.first_name_kana = fk; + const bday = normalizeBirthday(bdayRaw); + if (bday) changes.birthday = bday; + await updateMemberName(profile, changes, pwd); + const after = await loadProfile(); + log( + `✅ 会员资料已更新\n` + + `新氏名:${after.last_name} ${after.first_name}\n` + + `カナ:${after.last_name_kana} ${after.first_name_kana}\n` + + `生日:${after.birthday}\n` + + `建议开启「券面强制显示」并验证券面。` + ); + const dn = `${after.last_name} ${after.first_name}`.trim(); + if (dn) { + $('#npOverlayName', root).value = dn; + if (!$('#npOverlayOn', root).checked) { + $('#npOverlayOn', root).checked = true; + } + saveOverlayFromUI(); + } + } catch (e) { + log('❌ ' + e.message); + } + }); + + $('#npVerify', root).addEventListener('click', async () => { + log('验证中…'); + try { + const v = await verifyTicketNames(); + const prof = await loadProfile(); + let msg = `会员资料:${v.member}\n片假名:${v.kana || '(空)'}\n生日:${prof.birthday || '(空)'}\n`; + if (v.clients.length) msg += `订单ご依頼主:${v.clients[0]}\n`; + if (!v.tickets.length) { + msg += '当前无入場チケット。'; + } else { + v.tickets.forEach((t) => { + const ok = t.name === v.member; + msg += `\n券面 [${t.ec}]:${t.name} ${ok ? '✅与会员一致' : '❌仍为订单快照'}`; + }); + } + log(msg); + } catch (e) { + log('❌ ' + e.message); + } + }); +})(); diff --git a/fixed-site-replacer-main/shibuya-accounts.js b/fixed-site-replacer-main/shibuya-accounts.js new file mode 100644 index 0000000..ca4cc5c --- /dev/null +++ b/fixed-site-replacer-main/shibuya-accounts.js @@ -0,0 +1,772 @@ +const SHIBUYA_ACCOUNTS = [ + { + "email": "StevanJr978922@outlook.com", + "pass": "pizdus432063", + "time": "11:00" + }, + { + "email": "RudyVellos367864@outlook.com", + "pass": "wsufjc44609", + "time": "11:00" + }, + { + "email": "ThedoreSerigne7392@outlook.com", + "pass": "bkylcq965403", + "time": "11:00" + }, + { + "email": "NapoleonRaycroft340315@outlook.com", + "pass": "djjfxd93947", + "time": "11:00" + }, + { + "email": "ChantelHughs3322@outlook.com", + "pass": "dgtnx935728", + "time": "11:00" + }, + { + "email": "CallumSupino1403@outlook.com", + "pass": "hkmcs79780", + "time": "11:00" + }, + { + "email": "DimitrovCaffentzis211@outlook.com", + "pass": "nU1HRa54G5", + "time": "11:00" + }, + { + "email": "HarukoRogner209161@outlook.com", + "pass": "orexo79555", + "time": "11:00" + }, + { + "email": "PargaElfreda08@outlook.com", + "pass": "l7AYEFXL", + "time": "11:00" + }, + { + "email": "FlandersMagierski40@outlook.com", + "pass": "U2gwNLLsnI", + "time": "11:00" + }, + { + "email": "GoeckeSill626@outlook.com", + "pass": "jppTBc1QvUnM", + "time": "11:00" + }, + { + "email": "MaryjoLape1868@outlook.com", + "pass": "gbqqe25960", + "time": "11:00" + }, + { + "email": "CobyMaynes4830@outlook.com", + "pass": "uiivb613524", + "time": "12:00" + }, + { + "email": "RobertMorago465229@outlook.com", + "pass": "qhojr443254", + "time": "12:00" + }, + { + "email": "KaiyaTerracina5179@outlook.com", + "pass": "ghqrcj61272", + "time": "12:00" + }, + { + "email": "AmberlyApostol080636@outlook.com", + "pass": "lwhlte42578", + "time": "12:00" + }, + { + "email": "GageLargay4628@outlook.com", + "pass": "xyzweo07494", + "time": "12:00" + }, + { + "email": "BunaMite089562@outlook.com", + "pass": "vsswr783373", + "time": "12:00" + }, + { + "email": "ChelsieArnal3807@outlook.com", + "pass": "mgddlc46586", + "time": "12:00" + }, + { + "email": "OrinPenhall42916@outlook.com", + "pass": "hdzkr879741", + "time": "12:00" + }, + { + "email": "AidaDesatnik16108@outlook.com", + "pass": "lokwz298811", + "time": "12:00" + }, + { + "email": "VetaCatha75617@outlook.com", + "pass": "qytef78040", + "time": "12:00" + }, + { + "email": "PearlieHaydu1162@outlook.com", + "pass": "mkqou538907", + "time": "12:00" + }, + { + "email": "DixiePagnano96880@outlook.com", + "pass": "ketaa424800", + "time": "12:00" + }, + { + "email": "MirandoKari676@outlook.com", + "pass": "PF8qtY50qhD", + "time": "12:00" + }, + { + "email": "NerisDistefano9400@outlook.com", + "pass": "VvrZp9P32c8", + "time": "12:00" + }, + { + "email": "MatheyJayson561@outlook.com", + "pass": "T76KTbofMm", + "time": "12:00" + }, + { + "email": "RubyNicoletti014495@outlook.com", + "pass": "kfsyn677192", + "time": "12:00" + }, + { + "email": "ErnestClum61480@outlook.com", + "pass": "ihubim12522", + "time": "12:00" + }, + { + "email": "TommyOhme209549@outlook.com", + "pass": "gwimje193362", + "time": "12:00" + }, + { + "email": "NoblePioske658841@outlook.com", + "pass": "ldgby13398", + "time": "12:00" + }, + { + "email": "DequanStingo157264@outlook.com", + "pass": "rnfuyt418163", + "time": "12:00" + }, + { + "email": "PawlowiczIbraham5715@outlook.com", + "pass": "u7lGk69c", + "time": "12:00" + }, + { + "email": "LudwigHalliwell00587@outlook.com", + "pass": "wuguwl046177", + "time": "13:00" + }, + { + "email": "MarenYung20785@outlook.com", + "pass": "wfsmj574209", + "time": "13:00" + }, + { + "email": "FitzgeraldRockholt98295@outlook.com", + "pass": "vfosa42619", + "time": "13:00" + }, + { + "email": "LavoniaOnori430491@outlook.com", + "pass": "tynja52093", + "time": "13:00" + }, + { + "email": "LaurynBenns55300@outlook.com", + "pass": "ycoor865974", + "time": "13:00" + }, + { + "email": "AdinMantica4225@outlook.com", + "pass": "ggikew34255", + "time": "13:00" + }, + { + "email": "SpencerMicca75194@outlook.com", + "pass": "ovpexo583348", + "time": "13:00" + }, + { + "email": "MarilynKoes4862@outlook.com", + "pass": "malvd97553", + "time": "13:00" + }, + { + "email": "MyrtisGoglia44012@outlook.com", + "pass": "qceaxo318981", + "time": "13:00" + }, + { + "email": "AmaGooch4221@outlook.com", + "pass": "cfbukf996534", + "time": "13:00" + }, + { + "email": "LacyBaracco4835@outlook.com", + "pass": "hkwbdw20237", + "time": "13:00" + }, + { + "email": "DemondThomer335742@outlook.com", + "pass": "wjunch91495", + "time": "13:00" + }, + { + "email": "AnitraSaracina6117@outlook.com", + "pass": "pcycmh53076", + "time": "13:00" + }, + { + "email": "MiahVanderbush085922@outlook.com", + "pass": "qxvhk68960", + "time": "13:00" + }, + { + "email": "ScudieriGlantz140@outlook.com", + "pass": "3sWtOwpD", + "time": "13:00" + }, + { + "email": "LominackGayle15@outlook.com", + "pass": "l4vSL9NI", + "time": "13:00" + }, + { + "email": "MonetMarbury2351@outlook.com", + "pass": "vH1M3i3s58E", + "time": "13:00" + }, + { + "email": "LuarcaIdelle11@outlook.com", + "pass": "BRzqEo8ng", + "time": "13:00" + }, + { + "email": "YolandaInsinga6164@outlook.com", + "pass": "kuwzrx28362", + "time": "14:00" + }, + { + "email": "RosamondIanniello8358@outlook.com", + "pass": "kgihj399612", + "time": "14:00" + }, + { + "email": "NormandEquihua1109@outlook.com", + "pass": "phige47306", + "time": "14:00" + }, + { + "email": "MalikKoukos129235@outlook.com", + "pass": "emiyp493973", + "time": "14:00" + }, + { + "email": "ShaquitaAboytes4351@outlook.com", + "pass": "lsxnv876948", + "time": "14:00" + }, + { + "email": "OleneFreiley3534@outlook.com", + "pass": "jqzzdd964055", + "time": "14:00" + }, + { + "email": "NaomaCocroft838671@outlook.com", + "pass": "ztcczh085978", + "time": "14:00" + }, + { + "email": "UrielPreas06225@outlook.com", + "pass": "zusftv68860", + "time": "14:00" + }, + { + "email": "NathanialLuchene26658@outlook.com", + "pass": "clifo168333", + "time": "14:00" + }, + { + "email": "LeomaShadeed8810@outlook.com", + "pass": "ebzjb622751", + "time": "14:00" + }, + { + "email": "EugenioGoldblatt2459@outlook.com", + "pass": "ajglip701106", + "time": "14:00" + }, + { + "email": "RecknerFiner23@outlook.com", + "pass": "YWC4fXYbQ8", + "time": "14:00" + }, + { + "email": "LehmerDearmore8491@outlook.com", + "pass": "aPwHdrm6Z", + "time": "14:00" + }, + { + "email": "FriisWithey7965@outlook.com", + "pass": "NSJjsJa07u", + "time": "14:00" + }, + { + "email": "BielinskiBrenton014@outlook.com", + "pass": "pFK1HwRq7", + "time": "14:00" + }, + { + "email": "BruckSprvill3308@outlook.com", + "pass": "RXN7TmzVp2N", + "time": "14:00" + }, + { + "email": "SearchfieldBrumbelow4116@outlook.com", + "pass": "c8l7eG06FEU", + "time": "14:00" + }, + { + "email": "EchoShakal891534@outlook.com", + "pass": "emzlx918955", + "time": "14:00" + }, + { + "email": "AlvisPhalin192479@outlook.com", + "pass": "alzvt79672", + "time": "15:00" + }, + { + "email": "OswaldGaletka9536@outlook.com", + "pass": "ukcbrg313096", + "time": "15:00" + }, + { + "email": "CarenMaki607247@outlook.com", + "pass": "zmuwgc703557", + "time": "15:00" + }, + { + "email": "CornieBlesse95411@outlook.com", + "pass": "zhnps44417", + "time": "15:00" + }, + { + "email": "SpurgeonNaeve8030@outlook.com", + "pass": "psvtt35984", + "time": "15:00" + }, + { + "email": "AntoniaDagdag2213@outlook.com", + "pass": "dwwvi704649", + "time": "15:00" + }, + { + "email": "AmaliaLeiterman563681@outlook.com", + "pass": "pvlyub84163", + "time": "15:00" + }, + { + "email": "LynwoodKuenne13902@outlook.com", + "pass": "wkpgul552549", + "time": "15:00" + }, + { + "email": "SelmaFramer61290@outlook.com", + "pass": "wisucq052328", + "time": "15:00" + }, + { + "email": "EllynFoshay97036@outlook.com", + "pass": "qnkhhd77753", + "time": "15:00" + }, + { + "email": "IldaVidic699203@outlook.com", + "pass": "srauap68390", + "time": "15:00" + }, + { + "email": "OthelAdelsberg79522@outlook.com", + "pass": "hxtuk858284", + "time": "15:00" + }, + { + "email": "JameJuntunen55@outlook.com", + "pass": "qtaTeVc8", + "time": "15:00" + }, + { + "email": "GollihueFischer64@outlook.com", + "pass": "i9lh4JdM60", + "time": "15:00" + }, + { + "email": "SicklesFerne114@outlook.com", + "pass": "yQldUQ20V9X", + "time": "15:00" + }, + { + "email": "FayLafevers041933@outlook.com", + "pass": "ckegy68592", + "time": "15:00" + }, + { + "email": "LoriGidcumb029418@outlook.com", + "pass": "lytoy55942", + "time": "16:00" + }, + { + "email": "AnnDelamarter806252@outlook.com", + "pass": "vicplt40738", + "time": "16:00" + }, + { + "email": "MarquezFeuchter1874@outlook.com", + "pass": "uixjk69638", + "time": "16:00" + }, + { + "email": "AileneKuwata277811@outlook.com", + "pass": "ejcfkm56148", + "time": "16:00" + }, + { + "email": "JobTrippett345830@outlook.com", + "pass": "cmqtj39103", + "time": "16:00" + }, + { + "email": "CelineDerr3525@outlook.com", + "pass": "ybgvij45965", + "time": "16:00" + }, + { + "email": "AnnabellaTreisch3070@outlook.com", + "pass": "egsbys378815", + "time": "16:00" + }, + { + "email": "ErikOkolie007503@outlook.com", + "pass": "jdobih10988", + "time": "16:00" + }, + { + "email": "IsaiahViglione31408@outlook.com", + "pass": "ioxdra00857", + "time": "16:00" + }, + { + "email": "BraydenShoda17303@outlook.com", + "pass": "oetiq87395", + "time": "16:00" + }, + { + "email": "CarissaTrimmings8794@outlook.com", + "pass": "hmdun14740", + "time": "16:00" + }, + { + "email": "MalindaHolme5566@outlook.com", + "pass": "mweich585575", + "time": "16:00" + }, + { + "email": "JammieSidun92407@outlook.com", + "pass": "fadyat13010", + "time": "16:00" + }, + { + "email": "BuckwaldArmson0861@outlook.com", + "pass": "KmOpLt3xF0Yh", + "time": "16:00" + }, + { + "email": "MoronBaruffi3375@outlook.com", + "pass": "1rK2H8OZdEq", + "time": "16:00" + }, + { + "email": "JonathonUrben068336@outlook.com", + "pass": "hwooin19315", + "time": "16:00" + }, + { + "email": "GillyardYeamans0032@outlook.com", + "pass": "7mQmLDwdb4gU", + "time": "16:00" + }, + { + "email": "GinnyDerda7515@outlook.com", + "pass": "hjgiqs94172", + "time": "16:00" + }, + { + "email": "LakeshiaLaurey2701@outlook.com", + "pass": "iqjxd77418", + "time": "16:00" + }, + { + "email": "DominiqueEsfahani8866@outlook.com", + "pass": "pchymr183635", + "time": "16:00" + }, + { + "email": "HideoDinitto58505@outlook.com", + "pass": "rlqju208364", + "time": "16:00" + }, + { + "email": "KaedenGalos914292@outlook.com", + "pass": "jsoupe75853", + "time": "17:00" + }, + { + "email": "CindyEhrhardt3848@outlook.com", + "pass": "rrwmoq658617", + "time": "17:00" + }, + { + "email": "ChanelleWeisser93240@outlook.com", + "pass": "vlpjrv86293", + "time": "17:00" + }, + { + "email": "AudrianaSanfelice5396@outlook.com", + "pass": "hzumas555723", + "time": "17:00" + }, + { + "email": "CatalinaCorace91255@outlook.com", + "pass": "ljzttr16503", + "time": "17:00" + }, + { + "email": "JalenVanover1020@outlook.com", + "pass": "mqmye34935", + "time": "17:00" + }, + { + "email": "SaigeHenrichsen8593@outlook.com", + "pass": "ttzhl12151", + "time": "17:00" + }, + { + "email": "JanaeDifelice402549@outlook.com", + "pass": "chzseb51764", + "time": "17:00" + }, + { + "email": "NewmanLomasney02574@outlook.com", + "pass": "vkayy81843", + "time": "17:00" + }, + { + "email": "MaebelleRasler49736@outlook.com", + "pass": "lgwil93083", + "time": "17:00" + }, + { + "email": "HamiltonScungio842335@outlook.com", + "pass": "bgeoxp845501", + "time": "17:00" + }, + { + "email": "CristopherCeparano1380@outlook.com", + "pass": "cgqmz71389", + "time": "17:00" + }, + { + "email": "CorwinMarkus231678@outlook.com", + "pass": "xqfhnt933191", + "time": "17:00" + }, + { + "email": "HalperinSoltes28@outlook.com", + "pass": "S5f0TPUce", + "time": "17:00" + }, + { + "email": "DanutaDelker1598@outlook.com", + "pass": "X2Rr29Zwe5", + "time": "17:00" + }, + { + "email": "KayleighBentson1115@outlook.com", + "pass": "IUqYnVDh0", + "time": "17:00" + }, + { + "email": "AshbyBurkus333287@outlook.com", + "pass": "cclxqw47477", + "time": "17:00" + }, + { + "email": "ElmyraBurridge3817@outlook.com", + "pass": "impyaf20647", + "time": "18:00" + }, + { + "email": "CorlissCarelock32411@outlook.com", + "pass": "onkvg958499", + "time": "18:00" + }, + { + "email": "AlphonsineBelanger81517@outlook.com", + "pass": "xiitd338826", + "time": "18:00" + }, + { + "email": "MargaretteMyricks28312@outlook.com", + "pass": "islok47384", + "time": "18:00" + }, + { + "email": "IsamSalery8936@outlook.com", + "pass": "vktau572934", + "time": "18:00" + }, + { + "email": "TimmyHurwitz75493@outlook.com", + "pass": "xroqkk49247", + "time": "18:00" + }, + { + "email": "KnoblockRabern0754@outlook.com", + "pass": "sb2DyFi5", + "time": "18:00" + }, + { + "email": "FlorNicoletti9588@outlook.com", + "pass": "cfvdq798849", + "time": "18:00" + }, + { + "email": "ArdeliaSeton46231@outlook.com", + "pass": "dfpsct53782", + "time": "18:00" + }, + { + "email": "QianaOthon396644@outlook.com", + "pass": "gozbm742224", + "time": "18:00" + }, + { + "email": "WoolstenhulmeSevaaetasi0425@outlook.com", + "pass": "pPhX22cZn", + "time": "18:00" + }, + { + "email": "CeceliaMeirick476878@outlook.com", + "pass": "nccdso98121", + "time": "18:00" + }, + { + "email": "AbbeyAltieri95596@outlook.com", + "pass": "hfwwky61796", + "time": "19:00" + }, + { + "email": "KinsleyDen8853@outlook.com", + "pass": "rdlybu83882", + "time": "19:00" + }, + { + "email": "BushJaros3246@outlook.com", + "pass": "tooyc34116", + "time": "19:00" + }, + { + "email": "DixonSalminen0004@outlook.com", + "pass": "geizhx120028", + "time": "19:00" + }, + { + "email": "AugustinQuebodeaux687077@outlook.com", + "pass": "ijtto250620", + "time": "19:00" + }, + { + "email": "CherrylMelaas2686@outlook.com", + "pass": "cbtjth394749", + "time": "19:00" + }, + { + "email": "GilmoreKatehis91590@outlook.com", + "pass": "jvyiva73808", + "time": "19:00" + }, + { + "email": "AbeFolkner13996@outlook.com", + "pass": "bumrpg243093", + "time": "19:00" + }, + { + "email": "KatinaHani49508@outlook.com", + "pass": "syyuvf231200", + "time": "19:00" + }, + { + "email": "VanderLifland3731@outlook.com", + "pass": "jmbfe792159", + "time": "19:00" + }, + { + "email": "AlvanLesinski52544@outlook.com", + "pass": "xcsxz12876", + "time": "19:00" + }, + { + "email": "KevenForgey006943@outlook.com", + "pass": "slmgk69100", + "time": "19:00" + }, + { + "email": "HodgdonLaman90@outlook.com", + "pass": "DVIu7zTh2t", + "time": "19:00" + }, + { + "email": "HuggerBareilles439@outlook.com", + "pass": "26eK2vl7Z", + "time": "19:00" + }, + { + "email": "RupnickBluto777@outlook.com", + "pass": "XukB7ZKSAEUp", + "time": "19:00" + }, + { + "email": "McarthurHertel041@outlook.com", + "pass": "IH83tjZZ", + "time": "19:00" + }, + { + "email": "GerryNeuber5044@outlook.com", + "pass": "zddbt776626", + "time": "19:00" + }, + { + "email": "AaravDimento5767@outlook.com", + "pass": "qxrubz628969", + "time": "19:00" + }, + { + "email": "RitaCalalang43363@outlook.com", + "pass": "xlhsgp805784", + "time": "19:00" + } +]; \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..498c362 --- /dev/null +++ b/index.html @@ -0,0 +1,349 @@ + + + + + +固定网站显示替换 - 脚本下载 + + + +

    📦 下载脚本(最新版)

    + +

    在 iPhone 上点 code-v0.7.0.user.js(最新版)即开始下载,然后用 Userscripts 应用导入。
    需要旧版本时点对应版本文件。

    +

    🧪 注入测试(排查用)

    +
      +
    • test-inject.user.js (任意网站左上角显示蓝色标记,验证 Userscripts 是否正常注入)
    • +
    +
    +

    📌 bookmarklet 书签版(不依赖扩展)

    +

    不想装扩展?点下方按钮把新脚本复制到剪贴板,然后粘贴到书签 URL 即可:
    打开任意网页 → 分享 → 添加书签 → 书本按钮 → 编辑 → 把地址全部删掉 → 长按粘贴 → 完成。
    之后在目标网站点这个书签,文字立即替换。

    + +

    💡 复制成功后,把内容粘贴到任意书签的「地址」栏(以 javascript: 开头)。
    规则配置与油猴版共用,首次点击弹出设置面板时填写即可。

    +
    +

    🔗 目标网站

    +

    + https://parks2.bandainamco-am.co.jp/ +

    +

    👆 点击在新页签打开网站;长按链接可弹出菜单「拷贝」,复制网址。

    +
    + + + + + + + +
    + + +
    + + +
    +
    SECONDS
    +
    +
    + + + 5 + +
    +
    +
    時空忍術発動中…
    +
    + +
    + +