From a2c99aae4a3910b2e8e0a12b9724d934ec7fb3eb Mon Sep 17 00:00:00 2001 From: XXhaos Date: Tue, 2 Jun 2026 19:24:27 +0900 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20Scripts/AddMsGames.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Scripts/AddMsGames.js | 567 ++++++++++++++++++++++++------------------ 1 file changed, 319 insertions(+), 248 deletions(-) diff --git a/Scripts/AddMsGames.js b/Scripts/AddMsGames.js index a4ae61c..759b010 100644 --- a/Scripts/AddMsGames.js +++ b/Scripts/AddMsGames.js @@ -9,65 +9,52 @@ * - 访问 https://addmsgames.com/?region=US → 直接执行美区加购 * - 访问 https://addmsgames.com/?region=NG → 直接执行尼区加购 * - 访问 https://addmsgames.com/?region=AR → 直接执行阿区加购 - * - * PersistentStore Key 说明(三区共用,与 NewAddToCart_Web.js 完全一致): - * - MUID: cart-x-authorization-muid - * - CV: cart-ms-cv - * - 列表: XboxProductList - * - * 流程: - * 1. GET 读取远程当前组(服务端加锁) - * 2. 执行加购 - * 3. POST /surge/commit 提交结果 - * 4. 若远程无数据,回退到本地对应区域的 XboxProductList-{REGION} */ -// ========================= 区域配置 ========================= const REGION_CONFIGS = { US: { - label: "美区", - flag: "🇺🇸", - color: "#4a90e2", - MARKET: "US", - LOCALE: "en-us", - FRIENDLY_NAME:"cart-US", - MUID_KEY: "cart-x-authorization-muid", - CV_KEY: "cart-ms-cv", - LOCAL_KEY: "XboxProductList", - CURRENCY: "USD", + label: "美区", + flag: "🇺🇸", + color: "#4a90e2", + MARKET: "US", + LOCALE: "en-us", + FRIENDLY_NAME: "cart-US", + MUID_KEY: "cart-x-authorization-muid", + CV_KEY: "cart-ms-cv", + LOCAL_KEY: "XboxProductList", + CURRENCY: "USD", }, NG: { - label: "尼区", - flag: "🇳🇬", - color: "#52b043", - MARKET: "NG", - LOCALE: "en-ng", - FRIENDLY_NAME:"cart-NG", - MUID_KEY: "cart-x-authorization-muid", - CV_KEY: "cart-ms-cv", - LOCAL_KEY: "XboxProductList", - CURRENCY: "NGN", + label: "尼区", + flag: "🇳🇬", + color: "#52b043", + MARKET: "NG", + LOCALE: "en-ng", + FRIENDLY_NAME: "cart-NG", + MUID_KEY: "cart-x-authorization-muid", + CV_KEY: "cart-ms-cv", + LOCAL_KEY: "XboxProductList", + CURRENCY: "NGN", }, AR: { - label: "阿区", - flag: "🇦🇷", - color: "#e8a838", - MARKET: "AR", - LOCALE: "es-ar", - FRIENDLY_NAME:"cart-AR", - MUID_KEY: "cart-x-authorization-muid", - CV_KEY: "cart-ms-cv", - LOCAL_KEY: "XboxProductList", - CURRENCY: "ARS", + label: "阿区", + flag: "🇦🇷", + color: "#e8a838", + MARKET: "AR", + LOCALE: "es-ar", + FRIENDLY_NAME: "cart-AR", + MUID_KEY: "cart-x-authorization-muid", + CV_KEY: "cart-ms-cv", + LOCAL_KEY: "XboxProductList", + CURRENCY: "ARS", }, }; -const REMOTE_READ_URL = 'https://xbox-bot.biubiubiu-lalala.workers.dev/surge?token=xbox123'; -const REMOTE_COMMIT_URL = 'https://xbox-bot.biubiubiu-lalala.workers.dev/surge/commit?token=xbox123'; -const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" }; -const API_URL = "https://cart.production.store-web.dynamics.com/cart/v1.0/cart/loadCart?cartType=consumer&appId=StoreWeb"; +const REMOTE_READ_URL = "https://xbox-bot.biubiubiu-lalala.workers.dev/surge?token=xbox123"; +const REMOTE_COMMIT_URL = "https://xbox-bot.biubiubiu-lalala.workers.dev/surge/commit?token=xbox123"; +const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" }; +const API_URL = "https://cart.production.store-web.dynamics.com/cart/v1.0/cart/loadCart?cartType=consumer&appId=StoreWeb"; -// ========================= 解析 region 参数 ========================= function getRegionParam() { try { const url = $request.url || ""; @@ -77,7 +64,6 @@ function getRegionParam() { return null; } -// ========================= 区域选择页面 ========================= function serveSelector() { const html = ` @@ -87,195 +73,84 @@ function serveSelector() { Xbox · 区域加购 -
Xbox
@@ -340,7 +215,6 @@ function go(el, name) { }); } -// ========================= 加购逻辑 ========================= function runCart(regionCode) { const cfg = REGION_CONFIGS[regionCode]; if (!cfg) { @@ -350,10 +224,9 @@ function runCart(regionCode) { } const { label, flag, color, MARKET, LOCALE, FRIENDLY_NAME, MUID_KEY, CV_KEY, LOCAL_KEY, CURRENCY } = cfg; - const MUID = $persistentStore.read(MUID_KEY); + const MUID = $persistentStore.read(MUID_KEY); const MS_CV = $persistentStore.read(CV_KEY); - const HEADERS = { "content-type": "application/json", "accept": "*/*", @@ -363,17 +236,17 @@ function runCart(regionCode) { "user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1" }; - let logBuffer = []; - const results = { success: [], failure: [] }; + let logBuffer = []; + const results = { success: [], failure: [] }; const successKeys = []; - let currentIndex = 0; - let productList = []; - let sourceLabel = ""; - let useRemote = false; + let currentIndex = 0; + let productList = []; + let sourceLabel = ""; + let useRemote = false; function log(type, message, detail = "") { - const icon = type === "success" ? "✅" : (type === "error" ? "❌" : "ℹ️"); - const clr = type === "success" ? "#52b043" : (type === "error" ? "#e05050" : "#777"); + const icon = type === "success" ? "✅" : (type === "error" ? "❌" : "ℹ️"); + const clr = type === "success" ? "#52b043" : (type === "error" ? "#e05050" : "#777"); console.log(`${icon} [${regionCode}] ${message} ${detail}`); logBuffer.push(`
  • ${icon} ${message} ${detail}
  • `); } @@ -382,30 +255,167 @@ function runCart(regionCode) { "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => (c === "x" ? (Math.random() * 16 | 0) : ((Math.random() * 4 | 8) | 0)).toString(16)); - const toNum = k => { const m = /^product(\d+)$/.exec(k); return m ? parseInt(m[1], 10) : Number.MAX_SAFE_INTEGER; }; + const toNum = k => { + const m = /^product(\d+)$/.exec(k); + return m ? parseInt(m[1], 10) : Number.MAX_SAFE_INTEGER; + }; + const normEntry = v => { if (!v || typeof v !== "object") return null; - const productId = String(v.ProductId ?? v.productId ?? "").trim(); - const skuId = String(v.SkuId ?? v.skuId ?? "").trim(); + const productId = String(v.ProductId ?? v.productId ?? "").trim(); + const skuId = String(v.SkuId ?? v.skuId ?? "").trim(); const availabilityId = String(v.AvailabilityId ?? v.availabilityId ?? "").trim(); if (!productId || !skuId || !availabilityId) return null; return { productId, skuId, availabilityId }; }; function parseProductList(raw) { - let parsed; try { parsed = JSON.parse(raw || "{}"); } catch { parsed = {}; } + let parsed; + try { parsed = JSON.parse(raw || "{}"); } catch { parsed = {}; } return Object.keys(parsed) .sort((a, b) => toNum(a) - toNum(b)) - .map(k => { const n = normEntry(parsed[k]); return n ? { key: k, ...n } : null; }) + .map(k => { + const n = normEntry(parsed[k]); + return n ? { key: k, ...n } : null; + }) .filter(Boolean); } + const normId = v => String(v ?? "").trim().toUpperCase(); + const asArr = v => Array.isArray(v) ? v : []; + + function parseJsonBody(raw) { + if (raw && typeof raw === "object") return { ok: true, value: raw }; + try { + return { ok: true, value: JSON.parse(String(raw || "{}")) }; + } catch (e) { + return { ok: false, error: String(e) }; + } + } + + function getStatusCode(response) { + return Number(response && (response.status || response.statusCode)) || 0; + } + + function collectLineItems(cart) { + const out = []; + const addItems = items => { + for (const item of asArr(items)) { + if (item && typeof item === "object") out.push(item); + } + }; + + addItems(cart && cart.lineItems); + addItems(cart && cart.bundleLineItems); + + for (const bundle of asArr(cart && cart.bundleLineItems)) { + addItems(bundle && bundle.lineItems); + addItems(bundle && bundle.items); + } + + return out; + } + + function collectBusinessErrors(payload) { + const events = payload && payload.events; + if (!events || typeof events !== "object") return []; + + const found = []; + const visit = (section, value) => { + if (Array.isArray(value)) { + for (const event of value) visit(section, event); + return; + } + if (!value || typeof value !== "object") return; + + const data = value.data || {}; + const status = Number(data.httpStatusCode || value.httpStatusCode || 0); + const type = String(value.type || value.severity || "").toLowerCase(); + + if (type === "error" || status >= 400) { + found.push({ section, event: value }); + return; + } + + for (const key of Object.keys(value)) { + if (Array.isArray(value[key])) visit(`${section}.${key}`, value[key]); + } + }; + + for (const key of Object.keys(events)) visit(key, events[key]); + return found; + } + + function formatBusinessError(item) { + const event = item.event || {}; + const data = event.data || {}; + const parts = []; + + if (item.section) parts.push(item.section); + if (event.provider) parts.push(`provider=${event.provider}`); + if (event.code) parts.push(`code=${event.code}`); + if (data.reason) parts.push(`reason=${data.reason}`); + if (Array.isArray(data.subReasons) && data.subReasons.length) { + parts.push(`sub=${data.subReasons.filter(Boolean).join(" / ")}`); + } + if (data.httpStatusCode) parts.push(`http=${data.httpStatusCode}`); + + return parts.join(", ") || "unknown business error"; + } + + function validateAddResult(rawBody, target) { + const parsed = parseJsonBody(rawBody); + if (!parsed.ok) { + return { ok: false, reason: `响应不是合法 JSON: ${parsed.error}` }; + } + + const payload = parsed.value || {}; + const cart = payload.cart || {}; + const market = cart.market ? String(cart.market).toUpperCase() : ""; + const language = cart.language ? String(cart.language).toLowerCase() : ""; + const contextProblems = []; + + if (market && market !== MARKET) contextProblems.push(`market=${cart.market}`); + if (language && language !== LOCALE.toLowerCase()) contextProblems.push(`language=${cart.language}`); + + const errors = collectBusinessErrors(payload); + const lineItems = collectLineItems(cart); + const matchedLine = lineItems.find(item => + normId(item.productId) === normId(target.productId) && + normId(item.skuId) === normId(target.skuId) && + normId(item.availabilityId) === normId(target.availabilityId) + ); + + if (errors.length > 0) { + const detail = errors.map(formatBusinessError).join(" | "); + const context = contextProblems.length ? ` | context: ${contextProblems.join(", ")}` : ""; + return { ok: false, reason: `${detail}${context} | lineItems=${lineItems.length}` }; + } + + if (contextProblems.length > 0) { + return { ok: false, reason: `购物车上下文不匹配: ${contextProblems.join(", ")} | lineItems=${lineItems.length}` }; + } + + if (!matchedLine) { + return { + ok: false, + reason: `HTTP 200 但未在 cart.lineItems 找到目标商品 | lineItems=${lineItems.length} | cartId=${cart.id || ""}` + }; + } + + const title = matchedLine.title ? ` | ${matchedLine.title}` : ""; + const qty = matchedLine.quantity ? ` | qty=${matchedLine.quantity}` : ""; + const amount = matchedLine.totalAmount != null ? ` | ${CURRENCY} ${Number(matchedLine.totalAmount).toFixed(2)}` : ""; + return { ok: true, detail: `${cart.language || ""}/${cart.market || ""}${title}${qty}${amount}` }; + } + function buildResultPage(failedNames) { const sc = results.success.length; const fc = results.failure.length; const failedHtml = failedNames.length ? `
    加购失败的游戏:
      ${failedNames.map(n => `
    • ${n}
    • `).join("")}
    ` : ""; + return ` @@ -441,7 +451,7 @@ function runCart(regionCode) {
    ${sc}
    成功
    ${fc}
    失败
    -
    ${sc+fc}
    合计
    +
    ${sc + fc}
    合计
    ${failedHtml}
    来源: ${sourceLabel}
    @@ -460,7 +470,11 @@ ${failedHtml} $done({ response: { status: 200, - headers: { "Content-Type": "text/html;charset=utf-8", "Cache-Control": "no-store, no-cache, must-revalidate", "Pragma": "no-cache" }, + headers: { + "Content-Type": "text/html;charset=utf-8", + "Cache-Control": "no-store, no-cache, must-revalidate", + "Pragma": "no-cache" + }, body: buildResultPage(failedNames) } }); @@ -471,10 +485,16 @@ ${failedHtml} let fi = 1; for (const item of productList) { if (results.failure.includes(item.productId)) { - failedProducts[`product${fi++}`] = { ProductId: item.productId, SkuId: item.skuId, AvailabilityId: item.availabilityId }; + failedProducts[`product${fi++}`] = { + ProductId: item.productId, + SkuId: item.skuId, + AvailabilityId: item.availabilityId + }; } } + log("info", fc === 0 ? "全部成功,提交 commit(弹出当前组)" : `${fc} 个失败,提交 commit(保留失败部分)`); + $httpClient.post({ url: REMOTE_COMMIT_URL, headers: { "Content-Type": "application/json" }, @@ -486,49 +506,80 @@ ${failedHtml} }); } else { try { - let store; try { store = JSON.parse($persistentStore.read(LOCAL_KEY) || "{}"); } catch { store = {}; } + let store; + try { store = JSON.parse($persistentStore.read(LOCAL_KEY) || "{}"); } catch { store = {}; } + for (const k of successKeys) { if (k && Object.prototype.hasOwnProperty.call(store, k)) delete store[k]; } + const rem = Object.keys(store).filter(k => normEntry(store[k]) !== null).length; $persistentStore.write(JSON.stringify(store), LOCAL_KEY); log("info", "本地清理完成", `剩余: ${rem}`); - } catch (e) { log("error", "清理异常", String(e)); } + } catch (e) { + log("error", "清理异常", String(e)); + } finish(); } } function sendRequest() { if (currentIndex >= productList.length) return finalizeAndClean(); + const { key, productId, skuId, availabilityId } = productList[currentIndex]; + $httpClient.put({ url: API_URL, headers: HEADERS, body: JSON.stringify({ - locale: LOCALE, market: MARKET, + locale: LOCALE, + market: MARKET, catalogClientType: "storeWeb", friendlyName: FRIENDLY_NAME, riskSessionId: riskId(), clientContext: CLIENT_CONTEXT, - itemsToAdd: { items: [{ productId, skuId, availabilityId, campaignId: "xboxcomct", quantity: 1 }] } + itemsToAdd: { + items: [ + { + productId, + skuId, + availabilityId, + campaignId: "xboxcomct", + quantity: 1 + } + ] + } }) - }, (error, response) => { - if (error || response.status !== 200) { + }, (error, response, data) => { + const statusCode = getStatusCode(response); + + if (error || statusCode !== 200) { results.failure.push(productId); - log("error", "失败", productId); + log("error", "失败", `${productId} | HTTP ${statusCode || "ERR"} ${error ? String(error) : ""}`); } else { - results.success.push(productId); - if (key) successKeys.push(key); - log("success", "成功", productId); + const verdict = validateAddResult( + data != null ? data : (response && response.body), + { productId, skuId, availabilityId } + ); + + if (verdict.ok) { + results.success.push(productId); + if (key) successKeys.push(key); + log("success", "成功", `${productId}${verdict.detail ? " | " + verdict.detail : ""}`); + } else { + results.failure.push(productId); + log("error", "失败", `${productId} | ${verdict.reason}`); + } } + currentIndex++; setTimeout(sendRequest, 50); }); } function doneWithPage(title, message, type = "warn") { - const color = type === "error" ? "#e05050" : type === "warn" ? "#e8a838" : "#52b043"; - const icon = type === "error" ? "❌" : type === "warn" ? "⚠️" : "✅"; + const pageColor = type === "error" ? "#e05050" : type === "warn" ? "#e8a838" : "#52b043"; + const icon = type === "error" ? "❌" : type === "warn" ? "⚠️" : "✅"; const html = ` @@ -539,13 +590,13 @@ ${failedHtml} @import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700&family=Noto+Sans+SC:wght@400;500&display=swap'); *,*::before,*::after{box-sizing:border-box;margin:0;padding:0} body{background:#0b0b0b;color:#ddd;font-family:'Noto Sans SC',sans-serif;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px 18px;-webkit-font-smoothing:antialiased} - .card{width:100%;max-width:420px;background:#141414;border:1px solid #222;border-top:3px solid ${color};border-radius:4px;padding:24px 20px 20px} + .card{width:100%;max-width:420px;background:#141414;border:1px solid #222;border-top:3px solid ${pageColor};border-radius:4px;padding:24px 20px 20px} .icon{font-size:32px;margin-bottom:12px} .title{font-family:'Barlow Condensed',sans-serif;font-size:20px;font-weight:700;color:#fff;letter-spacing:1px;margin-bottom:8px} .msg{font-size:13px;color:#888;line-height:1.7} .footer{margin-top:20px;display:flex;align-items:center;justify-content:space-between} .sub{font-size:11px;color:#2a2a2a;letter-spacing:1.5px;text-transform:uppercase} - a{display:inline-block;padding:8px 18px;border:1px solid ${color};color:${color};border-radius:3px;font-family:'Barlow Condensed',sans-serif;font-size:13px;font-weight:600;letter-spacing:1px;text-decoration:none} + a{display:inline-block;padding:8px 18px;border:1px solid ${pageColor};color:${pageColor};border-radius:3px;font-family:'Barlow Condensed',sans-serif;font-size:13px;font-weight:600;letter-spacing:1px;text-decoration:none} @@ -560,19 +611,35 @@ ${failedHtml}
    `; - $done({ response: { status: 200, headers: { "Content-Type": "text/html;charset=utf-8", "Cache-Control": "no-store, no-cache, must-revalidate", "Pragma": "no-cache" }, body: html } }); + + $done({ + response: { + status: 200, + headers: { + "Content-Type": "text/html;charset=utf-8", + "Cache-Control": "no-store, no-cache, must-revalidate", + "Pragma": "no-cache" + }, + body: html + } + }); } function startTask() { if (!MUID || !MS_CV) { - $notification.post(`❌ Xbox ${label} 错误`, `缺少 MUID 或 CV`, `请写入 ${MUID_KEY} / ${CV_KEY}`); + $notification.post(`❌ Xbox ${label} 错误`, "缺少 MUID 或 CV", `请写入 ${MUID_KEY} / ${CV_KEY}`); doneWithPage("缺少必要参数", `未找到 MUID 或 MS-CV,请确认已正确写入:

    ${MUID_KEY}
    ${CV_KEY}`, "error"); return; } + if (productList.length === 0) { $notification.post(`⚠️ Xbox ${label}`, "列表为空,无需执行", `来源: ${sourceLabel}`); if (useRemote) { - $httpClient.post({ url: REMOTE_COMMIT_URL, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ remaining: {} }) }, () => { + $httpClient.post({ + url: REMOTE_COMMIT_URL, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ remaining: {} }) + }, () => { doneWithPage("暂无商品", `远程队列与本地列表均为空,无需加购。

    来源: ${sourceLabel}`, "warn"); }); } else { @@ -580,39 +647,43 @@ ${failedHtml} } return; } + log("info", `开始 ${flag}${label} 加购`, `数量: ${productList.length},来源: ${sourceLabel}`); sendRequest(); } - // 主流程 $httpClient.get(REMOTE_READ_URL, (err, _res, data) => { - let remoteGroup = null, groupIndex = null; + let remoteGroup = null; + let groupIndex = null; + if (!err && data) { try { const p = JSON.parse((data || "").trim() || "{}"); if (p.ok && p.currentGroup && Object.keys(p.currentGroup).length > 0) { remoteGroup = p.currentGroup; - groupIndex = p.currentGroupIndex; + groupIndex = p.currentGroupIndex; } } catch (_) {} } + if (remoteGroup) { - useRemote = true; + useRemote = true; sourceLabel = `远程第 ${groupIndex} 组`; - log("info", `使用远程 Product`, `${flag}${label} · 第 ${groupIndex} 组,共 ${Object.keys(remoteGroup).length} 个`); + log("info", "使用远程 Product", `${flag}${label} · 第 ${groupIndex} 组,共 ${Object.keys(remoteGroup).length} 个`); productList = parseProductList(JSON.stringify(remoteGroup)); } else { - useRemote = false; + useRemote = false; sourceLabel = "本地"; productList = parseProductList($persistentStore.read(LOCAL_KEY) || "{}"); log("info", err ? `远程连接失败,使用本地 [${label}]` : `远程队列为空,使用本地 [${label}]`, err ? String(err) : ""); } + startTask(); }); } -// ========================= 入口 ========================= const region = getRegionParam(); + if (!region) { serveSelector(); } else {