From 6852b2fd5fb728e58fb7dd48390273578e063297 Mon Sep 17 00:00:00 2001 From: XXhaos Date: Sat, 18 Apr 2026 13:05:56 +0800 Subject: [PATCH] Update capture_gamertag.js --- Scripts/capture_gamertag.js | 276 ++++++++---------------------------- 1 file changed, 62 insertions(+), 214 deletions(-) diff --git a/Scripts/capture_gamertag.js b/Scripts/capture_gamertag.js index 7c6a8e7..5d3fb54 100644 --- a/Scripts/capture_gamertag.js +++ b/Scripts/capture_gamertag.js @@ -1,235 +1,83 @@ /** - * Xbox Cart History Switcher + * Surge 脚本:捕获 peoplehub 响应中的 gamertag * * 功能: - * 1. 访问 https://carthistory.com/ → 按时间戳最近邻匹配 cart 和 gamertag,展示卡片 - * 2. 点击切换 → 覆盖 $persistentStore 中的 cartId / authorization / gamertag + * - 保持原有:维护最新 gamertag 到 $persistentStore.gamertag + * - 新增:把每次捕获追加到 gamertag_records 数组(相邻相同值去重) * - * 匹配策略(核心): - * - 读取 cart_records 和 gamertag_records 两个数组 - * - 对每条 cart,在 gamertag_records 中找时间戳绝对差最小的一条 - * - 若最小差值超过 MATCH_WINDOW_MS,则该 cart 的 gamertag 显示为 "(未知)" - * - 不在存储里记录"已匹配的三元组",每次页面刷新都重新计算 + * gamertag_records 结构:[{gamertag, ts}, ...] + * - 匹配由网页脚本动态完成,本脚本不做任何配对 + * + * Surge 配置: + * [Script] + * capture_gamertag = type=http-response, pattern=^https:\/\/peoplehub-public\.xboxlive\.com\/people\/gt\(.+\), requires-body=true, script-path=xxx.js + * + * [MITM] + * hostname = %APPEND% peoplehub-public.xboxlive.com */ -const CART_KEY = "cartId"; -const AUTH_KEY = "authorization"; -const GAMERTAG_KEY = "gamertag"; -const CART_RECORDS = "cart_records"; -const GAMERTAG_RECORDS = "gamertag_records"; - -// 匹配窗口:cart 和 gamertag 时间差超过此值就认为无法配对 -const MATCH_WINDOW_MS = 60000; // 60 秒,按需调整 - +const peoplePattern = /^https:\/\/peoplehub-public\.xboxlive\.com\/people\/gt\(.+\)/; const url = $request.url; -const parsed = new URL(url); -const action = parsed.searchParams.get("action"); -const indexStr = parsed.searchParams.get("index"); -if (action === "apply" && indexStr !== null) { - applySwitch(parseInt(indexStr, 10)); -} else { - showList(); -} +const MAX_RECORDS = 20; // gamertag 记录保留最近 20 条 -// ==================== 读取 ==================== -function readRecords(key) { - const raw = $persistentStore.read(key); - if (!raw) return []; - try { - const arr = JSON.parse(raw); - return Array.isArray(arr) ? arr : []; - } catch (e) { - return []; - } -} +if (peoplePattern.test(url)) { + if (!$response.body) { + console.log("peoplehub triggered but no response body, skip"); + } else { + try { + const body = JSON.parse($response.body); + const gamertag = body && body.people && body.people[0] && body.people[0].gamertag; -// ==================== 最近邻匹配 ==================== -function matchCartToGamertag(cart, gamertagRecords) { - if (!gamertagRecords.length) return { gamertag: "(未知)", diff: null }; + if (!gamertag) { + console.log("[gamertag] 响应中未找到 gamertag,跳过"); + } else { + const now = Date.now(); - let best = null; - let bestDiff = Infinity; - for (const g of gamertagRecords) { - const diff = Math.abs(cart.ts - g.ts); - if (diff < bestDiff) { - bestDiff = diff; - best = g; + // 更新 gamertag 主 key(保持原有行为) + if (gamertag !== $persistentStore.read("gamertag")) { + $persistentStore.write(gamertag, "gamertag"); + console.log(`Stored gamertag: ${gamertag}`); + $notification.post( + "Surge 信息存储", + "已捕获 gamertag", + `gamertag: ${gamertag}` + ); + } + + // 追加到 gamertag_records + appendGamertagRecord({ gamertag, ts: now }); + } + } catch (error) { + console.log(`Error (gamertag): ${error}`); + $notification.post("Surge 脚本错误", "gamertag 捕获失败", `${error}`); } } - - if (bestDiff > MATCH_WINDOW_MS) { - return { gamertag: "(未知)", diff: bestDiff }; - } - return { gamertag: best.gamertag, diff: bestDiff }; } -// ==================== 工具函数 ==================== -function formatTimestamp(ms) { - try { - const d = new Date(ms); - const pad = n => String(n).padStart(2, '0'); - return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`; - } catch (e) { - return String(ms); - } -} - -function escapeHtml(s) { - if (s == null) return ''; - return String(s).replace(/[&<>"']/g, c => ({ - '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' - }[c])); -} - -function respondHtml(body) { - $done({ - response: { - status: 200, - headers: { - "Content-Type": "text/html;charset=utf-8", - "Cache-Control": "no-store, no-cache, must-revalidate", - "Pragma": "no-cache" - }, - body: body - } - }); -} - -// ==================== 展示列表 ==================== -function showList() { - const cartRecords = readRecords(CART_RECORDS); - const gamertagRecords = readRecords(GAMERTAG_RECORDS); - const currentCartId = $persistentStore.read(CART_KEY) || ""; - - // 为每条 cart 动态匹配 gamertag,生成展示数据 - const display = cartRecords.map((cart, i) => { - const match = matchCartToGamertag(cart, gamertagRecords); - return { - originalIndex: i, - cartId: cart.cartId, - authorization: cart.authorization, - ts: cart.ts, - gamertag: match.gamertag, - matchDiff: match.diff - }; - }); - - // 倒序(最新在前) - const reversed = [...display].reverse(); - - const cards = reversed.map(d => { - const isActive = d.cartId === currentCartId; - const cardStyle = isActive - ? 'border:2px solid #10b981; background:#f0fdf4;' - : 'border:1px solid #e5e7eb; background:#fff;'; - const activeBadge = isActive - ? '当前激活' - : ''; - const button = isActive - ? '' - : ``; - - return ` -
-
- ${escapeHtml(d.gamertag)}${activeBadge} -
-
- ${escapeHtml(formatTimestamp(d.ts))} -
- ${button} -
`; - }).join(''); - - const emptyHint = cartRecords.length === 0 - ? '
暂无 cart 记录
' - : ''; - - const html = ` - - - - -Cart History - - -
-

🛒 Cart 账号历史

-
共 ${cartRecords.length} 条记录(最新在前)
- ${cards} - ${emptyHint} -
- -`; - - respondHtml(html); -} - -// ==================== 执行切换 ==================== -function applySwitch(index) { - const cartRecords = readRecords(CART_RECORDS); - const gamertagRecords = readRecords(GAMERTAG_RECORDS); - - if (!Number.isInteger(index) || index < 0 || index >= cartRecords.length) { - return showError("无效的记录索引", `index=${index}, 记录数=${cartRecords.length}`); +function appendGamertagRecord(entry) { + let records = []; + const raw = $persistentStore.read("gamertag_records"); + if (raw) { + try { + const parsed = JSON.parse(raw); + if (Array.isArray(parsed)) records = parsed; + } catch (e) { records = []; } } - const cart = cartRecords[index]; - if (!cart || !cart.cartId || !cart.authorization) { - return showError("记录不完整", "该条记录缺少 cartId 或 authorization"); + // 相邻去重:如果最后一条就是同一个 gamertag,只更新它的 ts,不新增 + // 这样反复刷新同账号不会产生大量冗余记录 + if (records.length > 0 && records[records.length - 1].gamertag === entry.gamertag) { + records[records.length - 1].ts = entry.ts; + $persistentStore.write(JSON.stringify(records), "gamertag_records"); + console.log(`[gamertag] 更新末条时间戳: ${entry.gamertag}`); + return; } - const match = matchCartToGamertag(cart, gamertagRecords); - const gamertag = match.gamertag; - - // 覆盖 $persistentStore - $persistentStore.write(cart.cartId, CART_KEY); - $persistentStore.write(cart.authorization, AUTH_KEY); - if (gamertag && gamertag !== "(未知)") { - $persistentStore.write(gamertag, GAMERTAG_KEY); - } - - $notification.post( - "✅ 账号切换成功", - `已切换到 ${gamertag}`, - "" - ); - - const html = ` - - - - -切换成功 - - - -
-
-
切换成功
-
已切换到 ${escapeHtml(gamertag)}
-
1 秒后自动返回列表...
-
- -`; - - respondHtml(html); + records.push(entry); + if (records.length > MAX_RECORDS) records = records.slice(-MAX_RECORDS); + $persistentStore.write(JSON.stringify(records), "gamertag_records"); + console.log(`[gamertag] ✅ 新增记录: ${entry.gamertag}, total=${records.length}`); } -// ==================== 错误页 ==================== -function showError(title, detail) { - const html = ` - -错误 - -
-
-
${escapeHtml(title)}
-
${escapeHtml(detail)}
- 返回列表 -
- -`; - respondHtml(html); -} +$done({});