Files
Surge/Scripts/AddMsGames.js
2026-06-02 19:24:27 +09:00

691 lines
25 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 → 直接执行阿区加购
*/
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",
},
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",
},
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",
},
};
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";
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 = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Xbox · 区域加购</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@400;600;700&family=Noto+Sans+SC:wght@400;500&display=swap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg:#0b0b0b; --surface:#141414; --border:#222; --text:#ddd; --muted:#555;
--green:#107c10; --green-hi:#52b043;
}
html, body {
height: 100%; background: var(--bg); color: var(--text);
font-family: 'Noto Sans SC', sans-serif; -webkit-font-smoothing: antialiased;
}
body {
display:flex; flex-direction:column; align-items:center; justify-content:center;
min-height:100vh; padding:32px 20px;
background-image: radial-gradient(ellipse 80% 50% at 50% -10%, rgba(16,124,16,0.12) 0%, transparent 60%);
}
.xbox-bar { display:flex; align-items:center; gap:10px; margin-bottom:32px; }
.xbox-sphere {
width:36px; height:36px;
background: radial-gradient(circle at 38% 38%, #4caf50, #107c10 60%, #0a5a0a);
border-radius:50%; box-shadow:0 0 18px rgba(82,176,67,0.35); position:relative; flex-shrink:0;
}
.xbox-sphere::before, .xbox-sphere::after { content:''; position:absolute; inset:0; border-radius:50%; }
.xbox-sphere::before {
border-top:1.5px solid rgba(255,255,255,0.25);
border-left:1.5px solid rgba(255,255,255,0.1);
border-right:1.5px solid transparent;
border-bottom:1.5px solid transparent;
transform:rotate(-20deg);
}
.xbox-label {
font-family:'Barlow Condensed', sans-serif; font-size:22px; font-weight:700;
letter-spacing:3px; text-transform:uppercase; color:#fff;
}
.xbox-label span { color: var(--green-hi); }
h1 {
font-family:'Barlow Condensed', sans-serif; font-size:clamp(22px,5.5vw,32px);
font-weight:600; letter-spacing:1px; color:#fff; text-align:center; margin-bottom:6px;
}
.hint { font-size:12px; color:var(--muted); margin-bottom:36px; text-align:center; letter-spacing:.5px; }
.cards { display:flex; flex-direction:column; gap:12px; width:100%; max-width:420px; }
.card {
display:flex; align-items:center; gap:16px; padding:18px 20px;
background:var(--surface); border:1px solid var(--border); border-radius:4px;
cursor:pointer; text-decoration:none; color:var(--text); position:relative; overflow:hidden;
transition:border-color .2s, background .2s, transform .15s; -webkit-tap-highlight-color:transparent;
}
.card::after {
content:''; position:absolute; left:0; top:0; bottom:0; width:3px;
background:var(--c); opacity:0; transition:opacity .2s;
}
.card:hover, .card:active { border-color:var(--c); background:#1b1b1b; transform:translateX(4px); }
.card:hover::after, .card:active::after { opacity:1; }
.card-flag { font-size:30px; line-height:1; flex-shrink:0; }
.card-info { flex:1; }
.card-name {
font-family:'Barlow Condensed', sans-serif; font-size:20px; font-weight:700;
letter-spacing:1px; color:#fff;
}
.card-meta { font-size:11px; color:var(--muted); margin-top:2px; letter-spacing:.8px; }
.card-arrow { font-size:20px; color:var(--border); transition:color .2s, transform .2s; flex-shrink:0; }
.card:hover .card-arrow { color:var(--c); transform:translateX(3px); }
.overlay {
display:none; position:fixed; inset:0; background:rgba(0,0,0,0.88); z-index:999;
flex-direction:column; align-items:center; justify-content:center; gap:18px;
}
.overlay.on { display:flex; }
.spin {
width:44px; height:44px; border:3px solid #222; border-top-color:var(--green-hi);
border-radius:50%; animation:spin .75s linear infinite;
}
.spin-label {
font-family:'Barlow Condensed', sans-serif; font-size:15px; letter-spacing:2px;
color:var(--muted); text-transform:uppercase;
}
@keyframes spin { to { transform: rotate(360deg); } }
footer { margin-top:44px; font-size:11px; color:#2a2a2a; letter-spacing:2px; text-transform:uppercase; }
</style>
</head>
<body>
<div class="xbox-bar">
<div class="xbox-sphere"></div>
<div class="xbox-label">X<span>box</span></div>
</div>
<h1>选择加购区域</h1>
<p class="hint">点击区域后将自动执行加购任务</p>
<div class="cards">
<a class="card" style="--c:#4a90e2" href="https://addmsgames.com/?region=US" onclick="go(this,'美区')">
<span class="card-flag">🇺🇸</span>
<div class="card-info">
<div class="card-name">美区</div>
<div class="card-meta">MARKET: US · en-us</div>
</div>
<span class="card-arrow"></span>
</a>
<a class="card" style="--c:#52b043" href="https://addmsgames.com/?region=NG" onclick="go(this,'尼区')">
<span class="card-flag">🇳🇬</span>
<div class="card-info">
<div class="card-name">尼区</div>
<div class="card-meta">MARKET: NG · en-ng</div>
</div>
<span class="card-arrow"></span>
</a>
<a class="card" style="--c:#e8a838" href="https://addmsgames.com/?region=AR" onclick="go(this,'阿区')">
<span class="card-flag">🇦🇷</span>
<div class="card-info">
<div class="card-name">阿区</div>
<div class="card-meta">MARKET: AR · es-ar</div>
</div>
<span class="card-arrow"></span>
</a>
</div>
<footer>Surge · Xbox Cart Runner</footer>
<div class="overlay" id="ov">
<div class="spin"></div>
<div class="spin-label" id="ovLabel">正在准备...</div>
</div>
<script>
function go(el, name) {
document.getElementById('ovLabel').textContent = '正在执行 ' + name + ' 加购...';
document.getElementById('ov').classList.add('on');
}
</script>
</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 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 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(`<li style="color:${clr};padding:6px 0;border-bottom:1px solid #1e1e1e">${icon} ${message} <small style="opacity:.6">${detail}</small></li>`);
}
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)
.sort((a, b) => toNum(a) - toNum(b))
.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
? `<div class="failed-box"><b>加购失败的游戏:</b><ul>${failedNames.map(n => `<li>${n}</li>`).join("")}</ul></div>`
: "";
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<title>Xbox · ${flag}${label} 结果</title>
<style>
@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;padding:24px 18px;max-width:520px;margin:0 auto;-webkit-font-smoothing:antialiased}
.back{display:inline-flex;align-items:center;gap:6px;margin-bottom:20px;padding:8px 14px;background:#141414;border:1px solid #222;border-radius:3px;color:${color};text-decoration:none;font-size:13px;letter-spacing:.5px}
.header{display:flex;align-items:center;gap:10px;margin-bottom:18px}
.flag{font-size:28px}
.title{font-family:'Barlow Condensed',sans-serif;font-size:22px;font-weight:700;color:#fff;letter-spacing:1px}
.stats{display:flex;gap:10px;margin-bottom:18px}
.stat{flex:1;background:#141414;border:1px solid #222;border-radius:3px;padding:12px;text-align:center}
.stat-num{font-family:'Barlow Condensed',sans-serif;font-size:28px;font-weight:700}
.stat-lbl{font-size:11px;color:#555;margin-top:2px;letter-spacing:.5px}
.s-ok{color:#52b043}.s-err{color:#e05050}
.failed-box{background:#1a0e0e;border:1px solid #3a1a1a;border-radius:3px;padding:12px 16px;margin-bottom:14px;font-size:13px;color:#e05050}
.failed-box ul{margin-top:8px;padding-left:18px;line-height:1.8}
.source{font-size:11px;color:#444;margin-bottom:14px;letter-spacing:.5px}
.logs{background:#111;border-radius:3px;padding:10px 14px}
.logs ul{list-style:none;font-size:12px;line-height:1.6}
</style>
</head>
<body>
<a class="back" href="https://addmsgames.com">← 返回区域选择</a>
<div class="header">
<span class="flag">${flag}</span>
<span class="title">${label} · 加购完成</span>
</div>
<div class="stats">
<div class="stat"><div class="stat-num s-ok">${sc}</div><div class="stat-lbl">成功</div></div>
<div class="stat"><div class="stat-num s-err">${fc}</div><div class="stat-lbl">失败</div></div>
<div class="stat"><div class="stat-num" style="color:#888">${sc + fc}</div><div class="stat-lbl">合计</div></div>
</div>
${failedHtml}
<div class="source">来源: ${sourceLabel}</div>
<div class="logs"><ul>${logBuffer.join("")}</ul></div>
</body>
</html>`;
}
function finalizeAndClean() {
const fc = results.failure.length;
const sc = results.success.length;
const finish = (failedNames = []) => {
const sub = fc === 0 ? `成功: ${sc}` : `成功: ${sc} / 失败: ${fc}`;
$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(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: REMOTE_COMMIT_URL,
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ remaining: failedProducts })
}, (_e, _r, commitData) => {
let info = {};
try { info = JSON.parse(commitData || "{}"); } catch (_) {}
finish(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 => normEntry(store[k]) !== null).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, data) => {
const statusCode = getStatusCode(response);
if (error || statusCode !== 200) {
results.failure.push(productId);
log("error", "失败", `${productId} | HTTP ${statusCode || "ERR"} ${error ? String(error) : ""}`);
} else {
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 pageColor = type === "error" ? "#e05050" : type === "warn" ? "#e8a838" : "#52b043";
const icon = type === "error" ? "❌" : type === "warn" ? "⚠️" : "✅";
const html = `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<title>Xbox · ${flag}${label}</title>
<style>
@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 ${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 ${pageColor};color:${pageColor};border-radius:3px;font-family:'Barlow Condensed',sans-serif;font-size:13px;font-weight:600;letter-spacing:1px;text-decoration:none}
</style>
</head>
<body>
<div class="card">
<div class="icon">${icon}</div>
<div class="title">${title}</div>
<div class="msg">${message}</div>
<div class="footer">
<span class="sub">Xbox · ${flag}${label}</span>
<a href="https://addmsgames.com">← 返回</a>
</div>
</div>
</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}`);
doneWithPage("缺少必要参数", `未找到 MUID 或 MS-CV请确认已正确写入<br><br><code>${MUID_KEY}</code><br><code>${CV_KEY}</code>`, "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: {} })
}, () => {
doneWithPage("暂无商品", `远程队列与本地列表均为空,无需加购。<br><br>来源: ${sourceLabel}`, "warn");
});
} else {
doneWithPage("暂无商品", `远程队列与本地列表均为空,无需加购。<br><br>来源: ${sourceLabel}`, "warn");
}
return;
}
log("info", `开始 ${flag}${label} 加购`, `数量: ${productList.length},来源: ${sourceLabel}`);
sendRequest();
}
$httpClient.get(REMOTE_READ_URL, (err, _res, data) => {
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;
}
} 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);
}