diff --git a/Scripts/AddMsGames.js b/Scripts/AddMsGames.js new file mode 100644 index 0000000..d82f591 --- /dev/null +++ b/Scripts/AddMsGames.js @@ -0,0 +1,601 @@ +/** + * Xbox Cart Multi-Region Runner + * 远程路径: https://raw.githubusercontent.com/dragonisheep/Surge/refs/heads/master/Scripts/AddMsGames.js + * + * 完全独立脚本,通过 https://addmsgames.com 触发 + * + * 使用方式: + * - 访问 https://addmsgames.com → 显示区域选择界面 + * - 访问 https://addmsgames.com/?region=US → 直接执行美区加购 + * - 访问 https://addmsgames.com/?region=NG → 直接执行尼区加购 + * - 访问 https://addmsgames.com/?region=AR → 直接执行阿区加购 + * + * PersistentStore Key 说明: + * - 美区 MUID: cart-us-x-authorization-muid + * - 美区 CV: cart-us-ms-cv + * - 美区 列表: XboxProductList-US + * + * - 尼区 MUID: cart-ng-x-authorization-muid + * - 尼区 CV: cart-ng-ms-cv + * - 尼区 列表: XboxProductList-NG + * + * - 阿区 MUID: cart-ar-x-authorization-muid + * - 阿区 CV: cart-ar-ms-cv + * - 阿区 列表: XboxProductList-AR + * + * 流程: + * 1. GET 读取远程当前组(服务端加锁,带 market 参数区分三区) + * 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-us-x-authorization-muid", + CV_KEY: "cart-us-ms-cv", + LOCAL_KEY: "XboxProductList-US", + CURRENCY: "USD", + }, + NG: { + label: "尼区", + flag: "🇳🇬", + color: "#52b043", + MARKET: "NG", + LOCALE: "en-ng", + FRIENDLY_NAME:"cart-NG", + MUID_KEY: "cart-ng-x-authorization-muid", + CV_KEY: "cart-ng-ms-cv", + LOCAL_KEY: "XboxProductList-NG", + CURRENCY: "NGN", + }, + AR: { + label: "阿区", + flag: "🇦🇷", + color: "#e8a838", + MARKET: "AR", + LOCALE: "es-ar", + FRIENDLY_NAME:"cart-AR", + MUID_KEY: "cart-ar-x-authorization-muid", + CV_KEY: "cart-ar-ms-cv", + LOCAL_KEY: "XboxProductList-AR", + CURRENCY: "ARS", + }, +}; + +const REMOTE_BASE_READ = "https://locvps.dragonisheep.com/surge?token=xbox123"; +const REMOTE_BASE_COMMIT = "https://locvps.dragonisheep.com/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 || ""; + const m = url.match(/[?&]region=([A-Za-z]{2})/); + if (m) return m[1].toUpperCase(); + } catch (_) {} + return null; +} + +// ========================= 区域选择页面 ========================= +function serveSelector() { + const html = ` + + + + +Xbox · 区域加购 + + + + +
+
+
Xbox
+
+ +

选择加购区域

+

点击区域后将自动执行加购任务

+ +
+ + 🇺🇸 +
+
美区
+
MARKET: US · en-us
+
+ +
+ + 🇳🇬 +
+
尼区
+
MARKET: NG · en-ng
+
+ +
+ + 🇦🇷 +
+
阿区
+
MARKET: AR · es-ar
+
+ +
+
+ + + +
+
+
正在准备...
+
+ + + +`; + + $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 runCart(regionCode) { + const cfg = REGION_CONFIGS[regionCode]; + if (!cfg) { + $notification.post("❌ AddMsGames 错误", `未知区域: ${regionCode}`, ""); + $done({}); + return; + } + + const { label, flag, color, MARKET, LOCALE, FRIENDLY_NAME, MUID_KEY, CV_KEY, LOCAL_KEY, CURRENCY } = cfg; + const MUID = $persistentStore.read(MUID_KEY); + const MS_CV = $persistentStore.read(CV_KEY); + + const REMOTE_READ_URL = `${REMOTE_BASE_READ}&market=${MARKET}`; + const COMMIT_URL = `${REMOTE_BASE_COMMIT}&market=${MARKET}`; + + const HEADERS = { + "content-type": "application/json", + "accept": "*/*", + "x-authorization-muid": MUID, + "ms-cv": MS_CV, + "origin": "https://www.microsoft.com", + "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: [] }; + const successKeys = []; + 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"); + console.log(`${icon} [${regionCode}] ${message} ${detail}`); + logBuffer.push(`
  • ${icon} ${message} ${detail}
  • `); + } + + const riskId = () => + "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 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 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 = {}; } + return Object.keys(parsed) + .filter(k => /^product\d+$/.test(k)) + .sort((a, b) => toNum(a) - toNum(b)) + .map(k => { const n = normEntry(parsed[k]); return n ? { key: k, ...n } : null; }) + .filter(Boolean); + } + + function buildResultPage(ngnStr, failedNames) { + const sc = results.success.length; + const fc = results.failure.length; + const failedHtml = failedNames.length + ? `
    加购失败的游戏:
    ` + : ""; + const priceHtml = ngnStr + ? `
    游戏总价 ${ngnStr}
    ` + : ""; + return ` + + + + +Xbox · ${flag}${label} 结果 + + + +← 返回区域选择 +
    + ${flag} + ${label} · 加购完成 +
    +
    +
    ${sc}
    成功
    +
    ${fc}
    失败
    +
    ${sc+fc}
    合计
    +
    +${priceHtml} +${failedHtml} +
    来源: ${sourceLabel}
    +
    + +`; + } + + function finalizeAndClean() { + const fc = results.failure.length; + const sc = results.success.length; + + const finish = (ngnStr = "", failedNames = []) => { + const priceNote = ngnStr ? ` | ${ngnStr}` : ""; + const sub = fc === 0 ? `成功: ${sc}${priceNote}` : `成功: ${sc} / 失败: ${fc}${priceNote}`; + $notification.post(`🛒 Xbox ${flag}${label} 加购完成`, sub, `来源: ${sourceLabel}`); + $done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8", "Cache-Control": "no-store, no-cache, must-revalidate", "Pragma": "no-cache" }, + body: buildResultPage(ngnStr, failedNames) + } + }); + }; + + if (useRemote) { + const failedProducts = {}; + 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 }; + } + } + log("info", fc === 0 ? "全部成功,提交 commit(弹出当前组)" : `${fc} 个失败,提交 commit(保留失败部分)`); + $httpClient.post({ + url: COMMIT_URL, + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ remaining: failedProducts }) + }, (_e, _r, commitData) => { + let info = {}; + try { info = JSON.parse(commitData || "{}"); } catch (_) {} + const total = info.groupNGN ?? info.successNGN ?? 0; + const ngnStr = total > 0 ? `${total.toFixed(2)} ${CURRENCY}` : ""; + finish(ngnStr, info.failedNames || []); + }); + } else { + try { + 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 => /^product\d+$/.test(k)).length; + $persistentStore.write(JSON.stringify(store), LOCAL_KEY); + log("info", "本地清理完成", `剩余: ${rem}`); + } 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, + catalogClientType: "storeWeb", + friendlyName: FRIENDLY_NAME, + riskSessionId: riskId(), + clientContext: CLIENT_CONTEXT, + itemsToAdd: { items: [{ productId, skuId, availabilityId, campaignId: "xboxcomct", quantity: 1 }] } + }) + }, (error, response) => { + if (error || response.status !== 200) { + results.failure.push(productId); + log("error", "失败", productId); + } else { + results.success.push(productId); + if (key) successKeys.push(key); + log("success", "成功", productId); + } + currentIndex++; + setTimeout(sendRequest, 50); + }); + } + + function startTask() { + if (!MUID || !MS_CV) { + $notification.post(`❌ Xbox ${label} 错误`, `缺少 MUID 或 CV`, `请写入 ${MUID_KEY} / ${CV_KEY}`); + $done({}); + return; + } + if (productList.length === 0) { + $notification.post(`⚠️ Xbox ${label}`, "列表为空,无需执行", `来源: ${sourceLabel}`); + if (useRemote) { + $httpClient.post({ url: COMMIT_URL, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ remaining: {} }) }, () => $done({})); + } else { + $done({}); + } + return; + } + log("info", `开始 ${flag}${label} 加购`, `数量: ${productList.length},来源: ${sourceLabel}`); + sendRequest(); + } + + // 主流程 + $httpClient.get(REMOTE_READ_URL, (err, _res, data) => { + let remoteGroup = null, 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; + } + } catch (_) {} + } + if (remoteGroup) { + useRemote = true; + sourceLabel = `远程第 ${groupIndex} 组`; + log("info", `使用远程 Product`, `${flag}${label} · 第 ${groupIndex} 组,共 ${Object.keys(remoteGroup).length} 个`); + productList = parseProductList(JSON.stringify(remoteGroup)); + } else { + 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 { + runCart(region); +}