/**
* Xbox Cart Web Runner
* 远程路径: https://raw.githubusercontent.com/dragonisheep/Surge/refs/heads/master/Scripts/NewAddToCart_Web.js
*
* 流程:
* 1. GET 读取远程当前组(服务端加锁)
* 2. 执行加购
* 3. 加购完成后 GET clear(服务端释放锁 + 弹出该组)
* 4. 若远程无数据,回退到本地 XboxProductList
*/
const MARKET = "NG";
const LOCALE = "en-ng";
const FRIENDLY_NAME = `cart-${MARKET}`;
const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" };
const REMOTE_READ_URL = 'https://cc.dragonisheep.com/surge?token=xbox123';
const REMOTE_CLEAR_URL = 'https://cc.dragonisheep.com/surge/clear?token=xbox123';
const REMOTE_UNLOCK_URL = 'https://cc.dragonisheep.com/surge/unlock?token=xbox123';
const LOCAL_KEY = 'XboxProductList';
const MUID = $persistentStore.read("cart-x-authorization-muid");
const MS_CV = $persistentStore.read("cart-ms-cv");
let logBuffer = [];
const results = { success: [], failure: [] };
const successKeys = [];
let currentIndex = 0;
let productList = [];
let sourceLabel = "";
let useRemote = false; // 是否使用了远程数据(决定加购后是否发 clear)
function log(type, message, detail = "") {
const icon = type === "success" ? "✅" : (type === "error" ? "❌" : "ℹ️");
const color = type === "success" ? "green" : (type === "error" ? "red" : "#666");
console.log(`${icon} ${message} ${detail}`);
logBuffer.push(`
${icon} ${message} ${detail}
`);
}
const generateRiskSessionId = () =>
"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 norm = normEntry(parsed[k]); return norm ? { key: k, ...norm } : null; })
.filter(Boolean);
}
const API_URL = "https://cart.production.store-web.dynamics.com/cart/v1.0/cart/loadCart?cartType=consumer&appId=StoreWeb";
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"
};
function finalizeAndClean() {
const successCount = results.success.length;
const failureCount = results.failure.length;
// 清理本地 XboxProductList 中已成功的 key(仅本地模式)
let remainingCount = 0;
if (!useRemote) {
try {
let storeObj; try { storeObj = JSON.parse($persistentStore.read(LOCAL_KEY) || "{}"); } catch { storeObj = {}; }
for (const k of successKeys) {
if (k && Object.prototype.hasOwnProperty.call(storeObj, k)) delete storeObj[k];
}
remainingCount = Object.keys(storeObj).filter(k => /^product\d+$/.test(k)).length;
$persistentStore.write(JSON.stringify(storeObj), LOCAL_KEY);
log("info", "本地清理完成", `剩余: ${remainingCount}`);
} catch (e) { log("error", "清理异常", String(e)); }
}
const doFinish = () => {
$notification.post(
"🛒 Xbox 加购完成",
`成功: ${successCount} / 失败: ${failureCount}`,
`来源: ${sourceLabel}${!useRemote ? ` | 剩余本地: ${remainingCount}` : ''}`
);
const html = `Xbox Cart执行结果: 成功 ${successCount} / 失败 ${failureCount} | 来源: ${sourceLabel}
${logBuffer.join("")}
`;
$done({ response: { status: 200, headers: { "Content-Type": "text/html;charset=utf-8" }, body: html } });
};
if (useRemote) {
if (failureCount === 0) {
// 全部成功:发 clear,释放服务端锁并弹出该组
log("info", "加购全部成功,通知服务端 clear");
$httpClient.post({ url: REMOTE_CLEAR_URL, headers: { 'Content-Type': 'application/json' }, body: '{}' }, () => doFinish());
} else {
// 有失败:释放锁但不弹组,保留服务端数据,下次可重试
log("info", `有 ${failureCount} 个失败,释放锁并保留服务端数据以便重试`);
$httpClient.post({ url: REMOTE_UNLOCK_URL, headers: { 'Content-Type': 'application/json' }, body: '{}' }, () => {
$notification.post("⚠️ 部分失败", `${failureCount} 个加购失败`, "服务端数据已保留,可重新执行");
doFinish();
});
}
} else {
doFinish();
}
}
function startTask() {
if (!MUID || !MS_CV) {
log("error", "缺少 MUID/CV");
$notification.post("❌ Xbox 脚本错误", "缺少必要参数", "请检查 MUID 或 MS_CV");
finalizeAndClean();
return;
}
if (productList.length === 0) {
log("info", "列表为空");
$notification.post("⚠️ Xbox 脚本", "无需执行", `来源: ${sourceLabel} | 列表为空`);
// 远程模式下列表为空也要释放锁
if (useRemote) {
$httpClient.post({ url: REMOTE_CLEAR_URL, headers: { 'Content-Type': 'application/json' }, body: '{}' }, () => $done({}));
} else {
finalizeAndClean();
}
return;
}
log("info", `开始任务 [${sourceLabel}]`, `数量: ${productList.length}`);
sendRequest();
}
function sendRequest() {
if (currentIndex >= productList.length) return finalizeAndClean();
const { key, productId, skuId, availabilityId } = productList[currentIndex];
const bodyObj = {
locale: LOCALE, market: MARKET,
catalogClientType: "storeWeb",
friendlyName: FRIENDLY_NAME,
riskSessionId: generateRiskSessionId(),
clientContext: CLIENT_CONTEXT,
itemsToAdd: { items: [{ productId, skuId, availabilityId, campaignId: "xboxcomct", quantity: 1 }] }
};
$httpClient.put({ url: API_URL, headers: HEADERS, body: JSON.stringify(bodyObj) }, (error, response) => {
const idStr = `${productId}`;
if (error || response.status !== 200) {
results.failure.push(idStr);
log("error", "失败", idStr);
} else {
results.success.push(idStr);
if (key) successKeys.push(key);
log("success", "成功", idStr);
}
currentIndex++;
setTimeout(sendRequest, 50);
});
}
// ========================= 主流程 =========================
// ★ 服务端有锁机制,客户端不再需要时间锁
// 第一步:尝试从远程读取当前组(服务端此时会加锁)
$httpClient.get(REMOTE_READ_URL, (error, response, data) => {
let remoteGroup = null;
let groupIndex = null;
if (!error && data) {
try {
const payload = JSON.parse((data || '').trim() || '{}');
// 服务端有锁且有数据时返回 currentGroup
if (payload.ok && payload.currentGroup) {
const keys = Object.keys(payload.currentGroup);
if (keys.length > 0) {
remoteGroup = payload.currentGroup;
groupIndex = payload.currentGroupIndex;
log("info", "使用远程待同步 Product", `第 ${groupIndex} 组,共 ${keys.length} 个`);
}
}
} catch (_) {}
}
if (remoteGroup) {
useRemote = true;
sourceLabel = `远程第 ${groupIndex} 组`;
productList = parseProductList(JSON.stringify(remoteGroup));
startTask();
} else {
// 远程无数据(队列为空或被锁)或连接失败,回退到本地
useRemote = false;
const localRaw = $persistentStore.read(LOCAL_KEY) || "{}";
sourceLabel = "本地";
productList = parseProductList(localRaw);
if (!error) {
log("info", "远程队列为空,使用本地 Product");
} else {
log("info", "远程连接失败,使用本地 Product", String(error));
}
startTask();
}
});