diff --git a/Scripts/CartHistorySwitcher.js b/Scripts/CartHistorySwitcher.js index 9082fcd..7c6a8e7 100644 --- a/Scripts/CartHistorySwitcher.js +++ b/Scripts/CartHistorySwitcher.js @@ -1,18 +1,25 @@ /** * Xbox Cart History Switcher - * 远程路径: https://raw.githubusercontent.com/dragonisheep/Surge/refs/heads/master/Scripts/CartHistorySwitcher.js * * 功能: - * 1. 访问 https://carthistory.com/ → 展示所有历史 gamertag 卡片列表 - * 2. 点击某条记录 → 访问 https://carthistory.com/?action=apply&index=N - * → 将该条的 cartId / authorization / gamertag 覆盖到 $persistentStore - * 3. 切换后自动跳回列表页,当前激活的卡片会高亮显示 + * 1. 访问 https://carthistory.com/ → 按时间戳最近邻匹配 cart 和 gamertag,展示卡片 + * 2. 点击切换 → 覆盖 $persistentStore 中的 cartId / authorization / gamertag + * + * 匹配策略(核心): + * - 读取 cart_records 和 gamertag_records 两个数组 + * - 对每条 cart,在 gamertag_records 中找时间戳绝对差最小的一条 + * - 若最小差值超过 MATCH_WINDOW_MS,则该 cart 的 gamertag 显示为 "(未知)" + * - 不在存储里记录"已匹配的三元组",每次页面刷新都重新计算 */ -const HISTORY_KEY = "cartId_history"; -const CART_KEY = "cartId"; -const AUTH_KEY = "authorization"; -const GAMERTAG_KEY = "gamertag"; +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 url = $request.url; const parsed = new URL(url); @@ -25,9 +32,9 @@ if (action === "apply" && indexStr !== null) { showList(); } -// ==================== 读取历史 ==================== -function readHistory() { - const raw = $persistentStore.read(HISTORY_KEY); +// ==================== 读取 ==================== +function readRecords(key) { + const raw = $persistentStore.read(key); if (!raw) return []; try { const arr = JSON.parse(raw); @@ -37,14 +44,34 @@ function readHistory() { } } +// ==================== 最近邻匹配 ==================== +function matchCartToGamertag(cart, gamertagRecords) { + if (!gamertagRecords.length) return { gamertag: "(未知)", diff: null }; + + 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; + } + } + + if (bestDiff > MATCH_WINDOW_MS) { + return { gamertag: "(未知)", diff: bestDiff }; + } + return { gamertag: best.gamertag, diff: bestDiff }; +} + // ==================== 工具函数 ==================== -function formatTimestamp(iso) { +function formatTimestamp(ms) { try { - const d = new Date(iso); + 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 iso; + return String(ms); } } @@ -71,15 +98,28 @@ function respondHtml(body) { // ==================== 展示列表 ==================== function showList() { - const history = readHistory(); - const currentCartId = $persistentStore.read(CART_KEY) || ""; - const currentGamertag = $persistentStore.read(GAMERTAG_KEY) || ""; + const cartRecords = readRecords(CART_RECORDS); + const gamertagRecords = readRecords(GAMERTAG_RECORDS); + const currentCartId = $persistentStore.read(CART_KEY) || ""; - // 倒序(最新在前),同时记录在原数组中的 index 用于切换 - const reversed = history.map((e, i) => ({ entry: e, originalIndex: i })).reverse(); + // 为每条 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 cards = reversed.map(({ entry, originalIndex }) => { - const isActive = entry.cartId === currentCartId; + // 倒序(最新在前) + 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;'; @@ -88,29 +128,22 @@ function showList() { : ''; const button = isActive ? '' - : ``; + : ``; return `