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 ` -