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