init
This commit is contained in:
103
Scripts/Add8TFDlcToCart.js
Normal file
103
Scripts/Add8TFDlcToCart.js
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Surge Script - 微软购物车批量添加
|
||||
* 特性:动态生成 riskSessionId
|
||||
*/
|
||||
|
||||
// ==== 配置项 ==== //
|
||||
const PRODUCT_IDS = $persistentStore.read("productId"); // 格式:id1&id2
|
||||
const MUID = $persistentStore.read("cart-x-authorization-muid");
|
||||
const MS_CV = $persistentStore.read("cart-ms-cv");
|
||||
|
||||
// ==== 工具函数 ==== //
|
||||
const generateRiskSessionId = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => (c === "x" ? (Math.random()*16|0).toString(16) : (Math.random()*4|8).toString(16)));
|
||||
|
||||
// ==== 执行逻辑 ==== //
|
||||
const productIdArray = PRODUCT_IDS?.split("&") || [];
|
||||
const results = { success: [], failure: [] };
|
||||
let currentIndex = 0;
|
||||
|
||||
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,
|
||||
"sec-fetch-site": "cross-site",
|
||||
"x-validation-field-1": "9pgbhbppjf2b",
|
||||
"ms-cv": MS_CV,
|
||||
"accept-language": "ha-Latn-NG,ha;q=0.9",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"sec-fetch-mode": "cors",
|
||||
"origin": "https://www.microsoft.com",
|
||||
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/133.0.3065.54 Version/18.0 Mobile/15E148 Safari/604.1",
|
||||
"referer": "https://www.microsoft.com/",
|
||||
"x-ms-vector-id": "",
|
||||
"sec-fetch-dest": "empty"
|
||||
};
|
||||
|
||||
function sendRequest() {
|
||||
if (currentIndex >= productIdArray.length) {
|
||||
const successCount = results.success.length;
|
||||
const failureList = results.failure.join("\n");
|
||||
$notification.post(
|
||||
"🎮 操作完成",
|
||||
`成功: ${successCount}个`,
|
||||
failureList || "所有请求均成功"
|
||||
);
|
||||
$done();
|
||||
return;
|
||||
}
|
||||
|
||||
const productId = productIdArray[currentIndex];
|
||||
const riskSessionId = generateRiskSessionId();
|
||||
const timeoutId = setTimeout(() => {
|
||||
recordResult(productId, "超时");
|
||||
proceed();
|
||||
}, 30000);
|
||||
|
||||
$httpClient.put({
|
||||
url: API_URL,
|
||||
headers: HEADERS,
|
||||
body: JSON.stringify({
|
||||
locale: "en-ng",
|
||||
market: "NG",
|
||||
catalogClientType: "storeWeb",
|
||||
friendlyName: "cart-NG",
|
||||
riskSessionId: riskSessionId,
|
||||
clientContext: { client: "UniversalWebStore.Cart", deviceType: "Pc" },
|
||||
itemsToAdd: { items: [{ productId, skuId: "0010", quantity: 1 }] }
|
||||
})
|
||||
}, (error, response) => {
|
||||
clearTimeout(timeoutId);
|
||||
error ? recordResult(productId, error)
|
||||
: response.status === 200 ? handleSuccess(productId)
|
||||
: recordResult(productId, `HTTP ${response.status}`);
|
||||
proceed();
|
||||
});
|
||||
}
|
||||
|
||||
// ==== 辅助函数 ==== //
|
||||
function handleSuccess(id) {
|
||||
results.success.push(id);
|
||||
console.log(`✅ ${id}`);
|
||||
}
|
||||
|
||||
function recordResult(id, reason) {
|
||||
results.failure.push(`${id}: ${reason}`);
|
||||
console.log(`❌ ${id} - ${reason}`);
|
||||
}
|
||||
|
||||
function proceed() {
|
||||
currentIndex++;
|
||||
setTimeout(sendRequest, 100); // 请求间隔0.0秒
|
||||
}
|
||||
|
||||
// ==== 启动检查 ==== //
|
||||
if (!MUID || !PRODUCT_IDS) {
|
||||
console.log("⚠️ 配置错误 - 缺少必要参数 MUID 或 PRODUCT_IDS");
|
||||
$notification.post("配置错误", "缺少必要参数", "");
|
||||
$done();
|
||||
} else {
|
||||
console.log("🚀 开始执行请求");
|
||||
sendRequest();
|
||||
}
|
||||
56
Scripts/ApprovalCartId.js
Normal file
56
Scripts/ApprovalCartId.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Surge 脚本:从 buynow 链接中提取 cartId 并存入 ApprovalCartId
|
||||
* 存储格式:id1&id2&id3...
|
||||
*/
|
||||
|
||||
const key = "ApprovalCartId";
|
||||
|
||||
// 1. 获取已有 ApprovalCartId 的值
|
||||
const existingRaw = $persistentStore.read(key);
|
||||
|
||||
// 2. 获取当前请求 URL
|
||||
const url = $request.url;
|
||||
|
||||
// 3. 正则表达式提取 cartId
|
||||
// 说明:[?&] 匹配开始的 ? 或中间的 &,cartId= 匹配参数名,([^&]+) 捕获直到下一个 & 或字符串结束的内容
|
||||
const matches = url.match(/[?&]cartId=([^&]+)/);
|
||||
|
||||
if (matches && matches[1]) {
|
||||
const newCartId = matches[1];
|
||||
|
||||
// 将已有的字符串分割为数组 (过滤掉空字符串,防止 split 产生 bug)
|
||||
let existingIdArray = existingRaw ? existingRaw.split("&").filter(Boolean) : [];
|
||||
|
||||
// 4. 判重逻辑
|
||||
if (!existingIdArray.includes(newCartId)) {
|
||||
|
||||
// 追加新 ID 到数组
|
||||
existingIdArray.push(newCartId);
|
||||
|
||||
// 重新组合成字符串
|
||||
const finalString = existingIdArray.join("&");
|
||||
|
||||
// 5. 写入 Persistent Store
|
||||
$persistentStore.write(finalString, key);
|
||||
|
||||
// 控制台日志
|
||||
console.log(`✅ [CartId提取] 已追加: ${newCartId}`);
|
||||
console.log(`📄 当前列表: ${finalString}`);
|
||||
|
||||
// 发送通知
|
||||
$notification.post(
|
||||
"✅ CartId 抓取成功",
|
||||
`已存入第 ${existingIdArray.length} 个 ID`,
|
||||
newCartId
|
||||
);
|
||||
} else {
|
||||
console.log(`⚠️ [CartId提取] 跳过,已存在: ${newCartId}`);
|
||||
// 如果需要重复时也弹窗,取消下面这行的注释
|
||||
// $notification.post("⚠️ 跳过重复 ID", "该 CartId 已在列表中", newCartId);
|
||||
}
|
||||
} else {
|
||||
console.log("⚠️ URL 中未找到 cartId 参数");
|
||||
}
|
||||
|
||||
// 结束脚本,继续请求
|
||||
$done({});
|
||||
22
Scripts/AutoClearApprovalCartId.js
Normal file
22
Scripts/AutoClearApprovalCartId.js
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Surge 脚本:Clear_ApprovalCartId.js
|
||||
* 作用:监测到 Family Safety 的 Complete 请求时,自动清空 ApprovalCartId
|
||||
*/
|
||||
|
||||
const STORE_KEY = "ApprovalCartId";
|
||||
|
||||
(function() {
|
||||
// 只有 POST 方法才触发清理
|
||||
if ($request.method === "POST") {
|
||||
const oldValue = $persistentStore.read(STORE_KEY);
|
||||
|
||||
if (oldValue) {
|
||||
$persistentStore.write("", STORE_KEY);
|
||||
|
||||
console.log("🧹 [Batch Approve] 购买流程结束,已自动清空 ApprovalCartId 缓存。");
|
||||
|
||||
$notification.post("Microsoft Family", "购买流程结束", "ApprovalCartId 缓存已清空");
|
||||
}
|
||||
}
|
||||
$done({});
|
||||
})();
|
||||
83
Scripts/BuyRequestReplace.js
Normal file
83
Scripts/BuyRequestReplace.js
Normal file
@@ -0,0 +1,83 @@
|
||||
// Surge脚本:拦截 RequestParentalApproval 并替换 CartId/Auth
|
||||
// 增加了通知功能:成功或失败都会弹窗提示
|
||||
|
||||
// 1. 判断 HTTP 请求方法是否为 POST
|
||||
if ($request.method !== "POST") {
|
||||
$done({});
|
||||
} else {
|
||||
// 定义状态标记,用于最后发通知
|
||||
let status = { cartId: false, auth: false };
|
||||
let errorMsg = [];
|
||||
|
||||
// 2. 读取持久化存储
|
||||
let newCartId = $persistentStore.read("cartId");
|
||||
let newAuth = $persistentStore.read("authorization");
|
||||
|
||||
if (!newCartId) {
|
||||
console.log("警告:Store 中缺少 cartId");
|
||||
errorMsg.push("缺少 cartId");
|
||||
}
|
||||
if (!newAuth) {
|
||||
console.log("警告:Store 中缺少 authorization");
|
||||
errorMsg.push("缺少 Auth");
|
||||
}
|
||||
|
||||
// 3. 解析并修改 Request Body (CartId)
|
||||
let bodyStr = $request.body || "";
|
||||
let bodyObj;
|
||||
try {
|
||||
bodyObj = JSON.parse(bodyStr);
|
||||
} catch (e) {
|
||||
console.log("JSON 解析失败:" + e);
|
||||
errorMsg.push("JSON 解析错误");
|
||||
bodyObj = null;
|
||||
}
|
||||
|
||||
// 4. 执行替换:CartId
|
||||
if (bodyObj && newCartId) {
|
||||
bodyObj.cartId = newCartId;
|
||||
bodyStr = JSON.stringify(bodyObj);
|
||||
status.cartId = true; // 标记成功
|
||||
}
|
||||
|
||||
// 5. 执行替换:Authorization
|
||||
let headers = $request.headers;
|
||||
if (newAuth) {
|
||||
let authHeaderKey = Object.keys(headers).find(k => k.toLowerCase() === "authorization");
|
||||
if (authHeaderKey) {
|
||||
headers[authHeaderKey] = newAuth;
|
||||
} else {
|
||||
headers["Authorization"] = newAuth;
|
||||
}
|
||||
status.auth = true; // 标记成功
|
||||
}
|
||||
|
||||
// 6. 修正 Content-Length
|
||||
if (bodyStr !== $request.body) {
|
||||
let lenKey = Object.keys(headers).find(k => k.toLowerCase() === "content-length");
|
||||
if (lenKey) delete headers[lenKey];
|
||||
}
|
||||
|
||||
// 7. 发送通知逻辑
|
||||
if (status.cartId && status.auth) {
|
||||
// 情况A:完美,两个都替换了
|
||||
$notification.post("✅ 替换成功", "Xbox 购买参数", "CartId 和 Authorization 均已更新");
|
||||
} else if (status.cartId || status.auth) {
|
||||
// 情况B:部分成功 (比如只有Auth没有CartId)
|
||||
let details = [];
|
||||
if (status.cartId) details.push("CartId OK");
|
||||
if (status.auth) details.push("Auth OK");
|
||||
$notification.post("⚠️ 部分替换成功", "Xbox 购买参数", `仅完成: ${details.join(", ")} (请检查参数)`);
|
||||
} else {
|
||||
// 情况C:完全失败
|
||||
let reason = errorMsg.length > 0 ? errorMsg.join(" & ") : "未知原因";
|
||||
$notification.post("❌ 替换失败", "Xbox 购买参数", reason);
|
||||
}
|
||||
|
||||
// 8. 返回结果
|
||||
if (newAuth) {
|
||||
$done({ body: bodyStr, headers: headers });
|
||||
} else {
|
||||
$done({ body: bodyStr });
|
||||
}
|
||||
}
|
||||
89
Scripts/CartParameter.js
Normal file
89
Scripts/CartParameter.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Surge 脚本:捕获特定 URL 请求的参数
|
||||
* 包含: x-authorization-muid, ms-cv, x-ms-vector-id, x-ms-reference-id
|
||||
* 新增: x-ms-tracking-id, x-ms-correlation-id
|
||||
* 并存储到持久化存储($persistentStore)中
|
||||
* 只针对 PUT 请求
|
||||
*/
|
||||
|
||||
// 修改正则表达式,捕获新的请求 URL
|
||||
const pattern = /^https:\/\/cart\.production\.store-web\.dynamics\.com\/v1\.0\/cart\/loadCart\?/;
|
||||
const url = $request.url;
|
||||
|
||||
// 判断是否是 PUT 请求并且 URL 匹配
|
||||
if ($request.method === "PUT" && pattern.test(url)) {
|
||||
try {
|
||||
// 从请求头中获取所需参数
|
||||
const xAuthorizationMuid = $request.headers['x-authorization-muid'];
|
||||
const msCv = $request.headers['ms-cv'];
|
||||
const xMsVectorId = $request.headers['x-ms-vector-id'];
|
||||
const xMsReferenceId = $request.headers['x-ms-reference-id'];
|
||||
// 新增参数获取
|
||||
const xMsTrackingId = $request.headers['x-ms-tracking-id'];
|
||||
const xMsCorrelationId = $request.headers['x-ms-correlation-id'];
|
||||
|
||||
// 更新存储逻辑(仅在新值有效时更新)
|
||||
let hasUpdate = false;
|
||||
|
||||
// 1. x-authorization-muid
|
||||
if (xAuthorizationMuid && xAuthorizationMuid !== $persistentStore.read("cart-x-authorization-muid")) {
|
||||
$persistentStore.write(xAuthorizationMuid, "cart-x-authorization-muid");
|
||||
console.log(`Stored x-authorization-muid: ${xAuthorizationMuid}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 2. ms-cv
|
||||
if (msCv && msCv !== $persistentStore.read("cart-ms-cv")) {
|
||||
$persistentStore.write(msCv, "cart-ms-cv");
|
||||
console.log(`Stored ms-cv: ${msCv}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 3. x-ms-vector-id
|
||||
if (xMsVectorId && xMsVectorId !== $persistentStore.read("cart-x-ms-vector-id")) {
|
||||
$persistentStore.write(xMsVectorId, "cart-x-ms-vector-id");
|
||||
console.log(`Stored x-ms-vector-id: ${xMsVectorId}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 4. x-ms-reference-id
|
||||
if (xMsReferenceId && xMsReferenceId !== $persistentStore.read("cart-x-ms-reference-id")) {
|
||||
$persistentStore.write(xMsReferenceId, "cart-x-ms-reference-id");
|
||||
console.log(`Stored x-ms-reference-id: ${xMsReferenceId}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 5. [新增] x-ms-tracking-id
|
||||
if (xMsTrackingId && xMsTrackingId !== $persistentStore.read("cart-x-ms-tracking-id")) {
|
||||
$persistentStore.write(xMsTrackingId, "cart-x-ms-tracking-id");
|
||||
console.log(`Stored x-ms-tracking-id: ${xMsTrackingId}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 6. [新增] x-ms-correlation-id
|
||||
if (xMsCorrelationId && xMsCorrelationId !== $persistentStore.read("cart-x-ms-correlation-id")) {
|
||||
$persistentStore.write(xMsCorrelationId, "cart-x-ms-correlation-id");
|
||||
console.log(`Stored x-ms-correlation-id: ${xMsCorrelationId}`);
|
||||
hasUpdate = true;
|
||||
}
|
||||
|
||||
// 仅在成功捕获新值时发送通知(如果需要开启通知,取消下方注释)
|
||||
// if (hasUpdate) {
|
||||
// $notification.post(
|
||||
// "Surge 信息存储",
|
||||
// "已捕获并存储所有加购请求参数",
|
||||
// "包含 tracking-id 和 correlation-id"
|
||||
// );
|
||||
// }
|
||||
|
||||
} catch (error) {
|
||||
console.log(`Error capturing parameters: ${error}`);
|
||||
// $notification.post(
|
||||
// "Surge 脚本错误",
|
||||
// "无法捕获所需购物车参数",
|
||||
// `${error}`
|
||||
// );
|
||||
}
|
||||
}
|
||||
|
||||
$done({});
|
||||
48
Scripts/ClearApprovalCartId.js
Normal file
48
Scripts/ClearApprovalCartId.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Surge 脚本:清空 ApprovalCartId
|
||||
* 功能:1. 清空数据 2. 系统弹窗 3. 网页显示纯净结果
|
||||
* 触发地址:http://clear_list.com
|
||||
*/
|
||||
|
||||
const key = "ApprovalCartId";
|
||||
|
||||
// 1. 执行清空
|
||||
$persistentStore.write("", key);
|
||||
|
||||
// 2. 控制台日志
|
||||
console.log("✅ 操作成功 - 已清空 ApprovalCartId");
|
||||
|
||||
// 3. 发送系统通知 (手机顶部依然会弹窗)
|
||||
$notification.post("🗑️ 清单已清空", "操作成功", "ApprovalCartId 已重置为空对象");
|
||||
|
||||
// 4. 生成网页 HTML (去掉了提示文字)
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Clear List</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f4f4f4; }
|
||||
.card { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); text-align: center; }
|
||||
h1 { color: #28a745; margin: 0 0 10px; }
|
||||
p { color: #555; margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>✅ 操作成功</h1>
|
||||
<p>ApprovalCartId 已被清空</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// 5. 返回给浏览器
|
||||
$done({
|
||||
response: {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/html;charset=utf-8" },
|
||||
body: html
|
||||
}
|
||||
});
|
||||
48
Scripts/ClearXboxProductList_Web.js
Normal file
48
Scripts/ClearXboxProductList_Web.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Surge 脚本:清空 XboxProductList
|
||||
* 功能:1. 清空数据 2. 系统弹窗 3. 网页显示纯净结果
|
||||
* 触发地址:http://clear_list.com
|
||||
*/
|
||||
|
||||
const key = "XboxProductList";
|
||||
|
||||
// 1. 执行清空
|
||||
$persistentStore.write("{}", key);
|
||||
|
||||
// 2. 控制台日志
|
||||
console.log("✅ 操作成功 - 已清空 XboxProductList");
|
||||
|
||||
// 3. 发送系统通知 (手机顶部依然会弹窗)
|
||||
$notification.post("🗑️ 清单已清空", "操作成功", "XboxProductList 已重置为空对象");
|
||||
|
||||
// 4. 生成网页 HTML (去掉了提示文字)
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Clear List</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f4f4f4; }
|
||||
.card { background: white; padding: 30px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); text-align: center; }
|
||||
h1 { color: #28a745; margin: 0 0 10px; }
|
||||
p { color: #555; margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>✅ 操作成功</h1>
|
||||
<p>XboxProductList 已被清空</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// 5. 返回给浏览器
|
||||
$done({
|
||||
response: {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/html;charset=utf-8" },
|
||||
body: html
|
||||
}
|
||||
});
|
||||
156
Scripts/NewAddToCart.js
Normal file
156
Scripts/NewAddToCart.js
Normal file
@@ -0,0 +1,156 @@
|
||||
const MARKET = "NG";
|
||||
const LOCALE = "en-ng";
|
||||
const FRIENDLY_NAME = `cart-${MARKET}`;
|
||||
const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" };
|
||||
|
||||
const LIST_RAW = $persistentStore.read("XboxProductList") || "{}";
|
||||
const MUID = $persistentStore.read("cart-x-authorization-muid");
|
||||
const MS_CV = $persistentStore.read("cart-ms-cv");
|
||||
|
||||
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 };
|
||||
};
|
||||
|
||||
let parsed; try { parsed = JSON.parse(LIST_RAW); } catch { parsed = {}; }
|
||||
|
||||
const productList = 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 results = { success: [], failure: [] };
|
||||
const successKeys = [];
|
||||
let currentIndex = 0;
|
||||
|
||||
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,
|
||||
"sec-fetch-site": "cross-site",
|
||||
"x-validation-field-1": "9pgbhbppjf2b",
|
||||
"ms-cv": MS_CV,
|
||||
"accept-language": "en-US,en;q=0.9",
|
||||
"accept-encoding": "gzip, deflate, br",
|
||||
"sec-fetch-mode": "cors",
|
||||
"origin": "https://www.microsoft.com",
|
||||
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) EdgiOS/133.0.3065.54 Version/18.0 Mobile/15E148 Safari/604.1",
|
||||
"referer": "https://www.microsoft.com/",
|
||||
"x-ms-vector-id": "",
|
||||
"sec-fetch-dest": "empty"
|
||||
};
|
||||
|
||||
function finalizeAndClean() {
|
||||
const successCount = results.success.length;
|
||||
const failureList = results.failure.join("\n");
|
||||
let remainingCount = 0;
|
||||
|
||||
try {
|
||||
let storeObj; try { storeObj = JSON.parse($persistentStore.read("XboxProductList") || "{}"); } 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), "XboxProductList");
|
||||
} catch (e) {
|
||||
console.log("清理存储异常:" + e);
|
||||
}
|
||||
|
||||
const subtitle = `成功: ${successCount} 个 / 共 ${productList.length} 个`;
|
||||
const body = (failureList || "所有请求均成功") + `\n剩余待处理 product:${remainingCount} 个`;
|
||||
$notification.post("🎮 操作完成", subtitle, body);
|
||||
$done();
|
||||
}
|
||||
|
||||
function sendRequest() {
|
||||
if (currentIndex >= productList.length) return finalizeAndClean();
|
||||
|
||||
const { key, productId, skuId, availabilityId } = productList[currentIndex];
|
||||
const riskSessionId = generateRiskSessionId();
|
||||
const idTriple = `${productId}/${skuId}/${availabilityId}`;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
recordResult(idTriple, "超时");
|
||||
proceed();
|
||||
}, 30000);
|
||||
|
||||
const bodyObj = {
|
||||
locale: LOCALE,
|
||||
market: MARKET,
|
||||
catalogClientType: "storeWeb",
|
||||
friendlyName: FRIENDLY_NAME,
|
||||
riskSessionId,
|
||||
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) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (error || response.status !== 200) {
|
||||
recordResult(idTriple, `HTTP ${response ? response.status : "Error"}`);
|
||||
} else {
|
||||
handleSuccess(idTriple, key);
|
||||
}
|
||||
proceed();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleSuccess(id, key) {
|
||||
results.success.push(id);
|
||||
if (key) successKeys.push(key);
|
||||
console.log(`✅ ${id} (${key})`);
|
||||
}
|
||||
|
||||
function recordResult(id, reason) {
|
||||
results.failure.push(`${id}: ${reason}`);
|
||||
console.log(`❌ ${id} - ${reason}`);
|
||||
}
|
||||
|
||||
function proceed() {
|
||||
currentIndex++;
|
||||
setTimeout(sendRequest, 10);
|
||||
}
|
||||
|
||||
if (!MUID || !MS_CV) {
|
||||
console.log("缺少必要参数 MUID 或 MS_CV");
|
||||
$notification.post("配置错误", "缺少必要参数 MUID 或 MS_CV", "");
|
||||
$done();
|
||||
} else if (productList.length === 0) {
|
||||
console.log("XboxProductList 为空或无有效条目");
|
||||
$notification.post("无任务", "XboxProductList 为空或无有效条目", "");
|
||||
$done();
|
||||
} else {
|
||||
console.log(`开始执行请求(共 ${productList.length} 个)`);
|
||||
sendRequest();
|
||||
}
|
||||
102
Scripts/NewAddToCart_Web.js
Normal file
102
Scripts/NewAddToCart_Web.js
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Xbox Cart Web Runner
|
||||
* 远程路径: https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/NewAddToCart_Web.js
|
||||
*/
|
||||
|
||||
const MARKET = "NG";
|
||||
const LOCALE = "en-ng";
|
||||
const FRIENDLY_NAME = `cart-${MARKET}`;
|
||||
const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" };
|
||||
|
||||
// 读取配置
|
||||
const LIST_RAW = $persistentStore.read("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;
|
||||
|
||||
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(`<div style="color:${color}; border-bottom:1px solid #eee; padding:5px;">${icon} ${message} <small>${detail}</small></div>`);
|
||||
}
|
||||
|
||||
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 };
|
||||
};
|
||||
|
||||
let parsed; try { parsed = JSON.parse(LIST_RAW); } catch { parsed = {}; }
|
||||
const productList = 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;
|
||||
let remainingCount = 0;
|
||||
|
||||
try {
|
||||
let storeObj; try { storeObj = JSON.parse($persistentStore.read("XboxProductList") || "{}"); } 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), "XboxProductList");
|
||||
log("info", "清理完成", `剩余: ${remainingCount}`);
|
||||
} catch (e) { log("error", "清理异常", e); }
|
||||
|
||||
// 【新增】发送系统通知
|
||||
$notification.post(
|
||||
"🛒 Xbox 加购完成",
|
||||
`成功: ${successCount} / 失败: ${failureCount}`,
|
||||
`剩余库存: ${remainingCount}`
|
||||
);
|
||||
|
||||
const html = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Xbox Cart</title></head><body style="font-family:sans-serif;padding:20px;"><h3>执行结果: 成功 ${successCount} / 剩余 ${remainingCount}</h3><div style="background:#f9f9f9;padding:10px;">${logBuffer.join("")}</div></body></html>`;
|
||||
|
||||
$done({ response: { status: 200, headers: { "Content-Type": "text/html;charset=utf-8" }, body: html } });
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
if (!MUID || !MS_CV) {
|
||||
log("error", "缺少 MUID/CV");
|
||||
// 即使配置错误也发个通知提醒你
|
||||
$notification.post("❌ Xbox 脚本错误", "缺少必要参数", "请检查 MUID 或 MS_CV");
|
||||
finalizeAndClean();
|
||||
}
|
||||
else if (productList.length === 0) {
|
||||
log("info", "列表为空");
|
||||
$notification.post("⚠️ Xbox 脚本", "无需执行", "列表为空");
|
||||
finalizeAndClean();
|
||||
}
|
||||
else {
|
||||
log("info", "开始任务", `数量: ${productList.length}`);
|
||||
sendRequest();
|
||||
}
|
||||
171
Scripts/SyncXboxCloud.js
Normal file
171
Scripts/SyncXboxCloud.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* SyncXboxCloud.js
|
||||
* 两步流程:先读取,写入成功后再删除
|
||||
* 顶部时间戳锁防止 Surge 规则重复触发脚本
|
||||
*/
|
||||
|
||||
const readUrl = 'https://cc.dragonisheep.com/surge?token=xbox123';
|
||||
const clearUrl = 'https://cc.dragonisheep.com/surge?token=xbox123&action=clear';
|
||||
const storeKey = 'XboxProductList';
|
||||
const lockKey = 'SyncXboxLock';
|
||||
|
||||
// ★ 防重入:5 秒内若已执行过,直接放行不处理
|
||||
// Surge 规则会同时拦截 read 和 clear 两个请求,第二次触发在此被阻断
|
||||
const lockVal = $persistentStore.read(lockKey);
|
||||
if (lockVal && Date.now() - parseInt(lockVal, 10) < 5000) {
|
||||
$done({});
|
||||
}
|
||||
$persistentStore.write(String(Date.now()), lockKey);
|
||||
|
||||
function escapeHTML(str) {
|
||||
return String(str || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function renderUI(title, message, type = "success") {
|
||||
const colorMap = {
|
||||
success: "#107C10",
|
||||
warning: "#d83b01",
|
||||
error: "#c50f1f",
|
||||
info: "#0078d4"
|
||||
};
|
||||
const color = colorMap[type] || "#0078d4";
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>${escapeHTML(title)}</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0; min-height: 100vh; display: flex; align-items: center;
|
||||
justify-content: center; background: #0f1115; color: #fff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
.card {
|
||||
width: 100%; max-width: 560px; background: #171a21; border-radius: 16px;
|
||||
border: 1px solid #252a35; border-top: 5px solid ${color};
|
||||
padding: 24px 22px 20px; box-sizing: border-box;
|
||||
}
|
||||
h1 { margin: 0 0 14px; font-size: 22px; color: ${color}; }
|
||||
.msg { color: #cfd6e4; font-size: 14px; line-height: 1.7; word-break: break-word; }
|
||||
.msg p { margin: 0 0 10px; }
|
||||
.msg ul { margin: 10px 0 0; padding-left: 20px; text-align: left; }
|
||||
.msg li { margin: 6px 0; }
|
||||
.sub { color: #8e99ab; font-size: 12px; margin-top: 14px; }
|
||||
button {
|
||||
margin-top: 18px; border: none; background: ${color};
|
||||
color: white; padding: 10px 22px; border-radius: 999px; font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>${escapeHTML(title)}</h1>
|
||||
<div class="msg">${message}</div>
|
||||
<div class="sub">SyncXbox Cloud Queue</div>
|
||||
<button onclick="history.back()">确定</button>
|
||||
</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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 第一步:读取当前组
|
||||
$httpClient.get(readUrl, (error, response, data) => {
|
||||
if (error) {
|
||||
$notification.post("❌ 同步失败", "无法连接服务器", String(error));
|
||||
return renderUI("❌ 连接失败", `<p>无法连接至服务器。</p><p>${escapeHTML(String(error))}</p>`, "error");
|
||||
}
|
||||
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse((data || '').trim() || '{}');
|
||||
} catch (e) {
|
||||
$notification.post("❌ 同步失败", "返回 JSON 无法解析", "");
|
||||
return renderUI("❌ 解析错误", `<p>服务器返回内容不是合法 JSON。</p><p>${escapeHTML(String(e.message || e))}</p>`, "error");
|
||||
}
|
||||
|
||||
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
||||
$notification.post("❌ 同步失败", "返回结构异常", "");
|
||||
return renderUI("❌ 数据异常", `<p>服务器返回的数据结构不正确。</p>`, "error");
|
||||
}
|
||||
|
||||
if (!payload.ok) {
|
||||
$notification.post("❌ 同步失败", "服务器返回 ok=false", "");
|
||||
return renderUI("❌ 同步失败", `<p>服务器返回失败状态。</p>`, "error");
|
||||
}
|
||||
|
||||
const currentGroup = payload.currentGroup || {};
|
||||
const keys = Object.keys(currentGroup);
|
||||
|
||||
if (!keys.length) {
|
||||
$notification.post("📭 当前无待同步组", "云端队列为空", "");
|
||||
return renderUI("📭 没有可同步内容", `<p>当前没有待同步的 Product 分组。</p>`, "warning");
|
||||
}
|
||||
|
||||
// 第二步:写入本地,成功后才删云端
|
||||
const writeOK = $persistentStore.write(JSON.stringify(currentGroup), storeKey);
|
||||
if (!writeOK) {
|
||||
$notification.post("❌ 同步失败", "写入 Surge 存储失败", "");
|
||||
// 写入失败,不发 clear,云端数据保留,下次可以重试
|
||||
return renderUI("❌ 写入失败", `<p>无法写入 Surge 本地存储,云端数据未删除,下次访问可重试。</p>`, "error");
|
||||
}
|
||||
|
||||
// 第三步:本地写入成功,删除云端当前组
|
||||
$httpClient.get(clearUrl, (clearError, clearResponse, clearData) => {
|
||||
if (clearError) {
|
||||
$notification.post("⚠️ 本地已写入", `第 ${payload.currentGroupIndex} 组已保存`, "但云端清理失败,下次访问仍是这一组");
|
||||
return renderUI(
|
||||
"⚠️ 当前组已写入,但云端未清理",
|
||||
`<p>已成功写入第 <b>${escapeHTML(String(payload.currentGroupIndex))}</b> 组,共 <b>${keys.length}</b> 个商品。</p>
|
||||
<p>但清理云端当前组时失败,下次访问时仍会是这一组。</p>`,
|
||||
"warning"
|
||||
);
|
||||
}
|
||||
|
||||
let clearPayload = {};
|
||||
try { clearPayload = JSON.parse((clearData || '').trim() || '{}'); } catch (_) {}
|
||||
|
||||
const remainingGroups = typeof clearPayload.remainingGroups === 'number'
|
||||
? clearPayload.remainingGroups
|
||||
: (typeof payload.remainingAfterCurrent === 'number' ? payload.remainingAfterCurrent : 0);
|
||||
const nextGroupIndex = clearPayload.nextGroupIndex ?? null;
|
||||
const nextGroupCount = clearPayload.nextGroupCount ?? 0;
|
||||
const list = keys.map(k => `<li>${escapeHTML(k)}</li>`).join('');
|
||||
|
||||
let message =
|
||||
`<p>本次只同步了第 <b>${escapeHTML(String(payload.currentGroupIndex))}</b> 组,共 <b>${keys.length}</b> 个商品。</p>
|
||||
<p>当前这一组已经从云端队列删除。</p>
|
||||
<p>现在还剩 <b>${remainingGroups}</b> 组待处理。</p>`;
|
||||
|
||||
if (remainingGroups > 0 && nextGroupIndex !== null) {
|
||||
message += `<p>下次访问网页时,将同步第 <b>${escapeHTML(String(nextGroupIndex))}</b> 组(共 <b>${escapeHTML(String(nextGroupCount))}</b> 个商品)。</p>`;
|
||||
} else {
|
||||
message += `<p>所有分组已经处理完毕。</p>`;
|
||||
}
|
||||
|
||||
message += `<ul>${list}</ul>`;
|
||||
|
||||
$notification.post("✅ 当前组同步成功", `第 ${payload.currentGroupIndex} 组已同步`, `剩余 ${remainingGroups} 组`);
|
||||
return renderUI("✅ 当前组同步完成", message, "success");
|
||||
});
|
||||
});
|
||||
113
Scripts/XboxProductList.js
Normal file
113
Scripts/XboxProductList.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// Surge: script-response-body
|
||||
// 递归提取 GET 响应中 actionType == "Cart" 的 {ProductId,SkuId,AvailabilityId}
|
||||
// 以 product1/product2… 顺序写入 XboxProductList;ProductId重复则跳过
|
||||
// 通知:🆕 新增 / 🔁 已存在(不含 URL),副标题仅显示「当前共有X个商品📦」
|
||||
|
||||
(function () {
|
||||
const STORE_KEY = 'XboxProductList';
|
||||
|
||||
try {
|
||||
if ($request?.method?.toUpperCase() !== 'GET') return $done({});
|
||||
if (!$response?.body || typeof $response.body !== 'string') return $done({ body: $response?.body });
|
||||
|
||||
// 解析 JSON;失败则尝试从文本抠出 JSON 片段
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse($response.body);
|
||||
} catch {
|
||||
const m = $response.body.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
|
||||
if (m) { try { data = JSON.parse(m[1]); } catch {} }
|
||||
if (!data) return $done({ body: $response.body });
|
||||
}
|
||||
|
||||
// 读取已有 store
|
||||
let store = {};
|
||||
try {
|
||||
const raw = $persistentStore.read(STORE_KEY);
|
||||
if (raw) store = JSON.parse(raw) || {};
|
||||
} catch { store = {}; }
|
||||
|
||||
// 现有条目(仅 productN)按数字排序
|
||||
const entries = Object.keys(store)
|
||||
.filter(k => /^product\d+$/.test(k))
|
||||
.sort((a, b) => parseInt(a.slice(7)) - parseInt(b.slice(7)))
|
||||
.map(k => store[k]);
|
||||
|
||||
// 【修改点1】简化判定逻辑:只要 ProductId 一致,即判定为相同商品
|
||||
const same = (a, b) =>
|
||||
a && b &&
|
||||
String(a.ProductId||'') === String(b.ProductId||'');
|
||||
|
||||
// 递归收集 Cart(去重当前响应内)
|
||||
const found = [];
|
||||
const seen = new Set();
|
||||
const isObj = v => v && typeof v === 'object';
|
||||
const visit = (node) => {
|
||||
if (!isObj(node)) return;
|
||||
if (typeof node.actionType === 'string' && node.actionType.toLowerCase() === 'cart') {
|
||||
const args = node.actionArguments;
|
||||
if (isObj(args)) {
|
||||
const r = {
|
||||
ProductId: String(args.ProductId ?? '').trim(),
|
||||
SkuId: String(args.SkuId ?? '').trim(),
|
||||
AvailabilityId: String(args.AvailabilityId ?? '').trim()
|
||||
};
|
||||
if (r.ProductId && r.SkuId && r.AvailabilityId) {
|
||||
// 【修改点2】当前响应内部的去重 Set 也只记录 ProductId
|
||||
const key = r.ProductId;
|
||||
if (!seen.has(key)) { seen.add(key); found.push(r); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(node)) node.forEach(visit);
|
||||
else Object.keys(node).forEach(k => visit(node[k]));
|
||||
};
|
||||
visit(data);
|
||||
|
||||
if (found.length === 0) return $done({ body: $response.body });
|
||||
|
||||
// 拆分新增/已存在
|
||||
const toAdd = [];
|
||||
const existed = [];
|
||||
for (const r of found) (entries.some(e => same(e, r)) ? existed : toAdd).push(r);
|
||||
|
||||
let title = '';
|
||||
let detail = null;
|
||||
|
||||
if (toAdd.length > 0) {
|
||||
// 追加为下一个 productN
|
||||
let maxIndex = 0;
|
||||
const keys = Object.keys(store).filter(k => /^product\d+$/.test(k));
|
||||
if (keys.length) maxIndex = Math.max(...keys.map(k => parseInt(k.slice(7), 10) || 0));
|
||||
|
||||
for (const r of toAdd) {
|
||||
const key = `product${++maxIndex}`;
|
||||
store[key] = { ProductId: r.ProductId, SkuId: r.SkuId, AvailabilityId: r.AvailabilityId };
|
||||
entries.push(store[key]);
|
||||
}
|
||||
$persistentStore.write(JSON.stringify(store), STORE_KEY);
|
||||
|
||||
title = '🆕 XboxProductList 已新增';
|
||||
detail = toAdd[0];
|
||||
} else {
|
||||
title = '🔁 XboxProductList 已存在,跳过';
|
||||
detail = existed[0];
|
||||
}
|
||||
|
||||
// 统计当前商品总数(仅 productN 键)
|
||||
const productCount = Object.keys(store).filter(k => /^product\d+$/.test(k)).length;
|
||||
|
||||
if (detail) {
|
||||
$notification.post(
|
||||
title,
|
||||
`当前共有 ${productCount} 个商品📦`,
|
||||
`信息如下:${JSON.stringify(detail)}`
|
||||
);
|
||||
}
|
||||
|
||||
return $done({ body: $response.body });
|
||||
} catch (err) {
|
||||
$notification.post('❌ Xbox 抓取脚本异常', '', String(err));
|
||||
return $done({ body: $response?.body });
|
||||
}
|
||||
})();
|
||||
44
Scripts/authorization&cartId.js
Normal file
44
Scripts/authorization&cartId.js
Normal file
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Surge 脚本:捕获特定 URL 请求的 Authorization 和 cartId(仅限 PUT 请求)
|
||||
* 并存储到持久化存储($persistentStore),只有在成功捕获到新值时才更新
|
||||
*/
|
||||
|
||||
const pattern = /^https:\/\/cart\.production\.store-web\.dynamics\.com\/v1\.0\/Cart\/eligibilityCheck\?/;
|
||||
const url = $request.url;
|
||||
|
||||
// 仅处理 PUT 请求
|
||||
if ($request.method === "PUT" && pattern.test(url)) {
|
||||
try {
|
||||
// 获取 Authorization 请求头的值
|
||||
const authorization = $request.headers['authorization'];
|
||||
// 解析 URL 以提取 cartId 参数
|
||||
const urlObj = new URL(url);
|
||||
const cartId = urlObj.searchParams.get('cartId');
|
||||
|
||||
// 只有在成功捕获到新值时,才更新 $persistentStore 中的值
|
||||
if (authorization && authorization !== $persistentStore.read("authorization")) {
|
||||
$persistentStore.write(authorization, "authorization");
|
||||
console.log(`Stored authorization: ${authorization}`);
|
||||
}
|
||||
|
||||
if (cartId && cartId !== $persistentStore.read("cartId")) {
|
||||
$persistentStore.write(cartId, "cartId");
|
||||
console.log(`Stored cartId: ${cartId}`);
|
||||
}
|
||||
|
||||
// 发送通知,成功捕获并存储 Authorization 和 CartId
|
||||
$notification.post(
|
||||
"Surge 信息存储",
|
||||
"已捕获并存储 authorization 和 cartId"
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(`Error capturing authorization & cartId: ${error}`);
|
||||
$notification.post(
|
||||
"Surge 脚本错误",
|
||||
"无法捕获 authorization 和 cartId",
|
||||
`${error}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$done({});
|
||||
141
Scripts/batch_purchase_send.js
Normal file
141
Scripts/batch_purchase_send.js
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* Surge 脚本:微软家庭组批量购买 (防风控增强版)
|
||||
* * 特性 1: 随机抖动延迟 (1.5s ~ 3.5s 之间随机),模拟真人。
|
||||
* * 特性 2: 智能熔断,遇到 429/403 立即停止,保护账号。
|
||||
*/
|
||||
|
||||
const STORE_KEY = "ApprovalCartId";
|
||||
const BASE_DELAY = 1500; // 基础间隔
|
||||
const JITTER_MAX = 2000; // 最大随机附加间隔 (0~2000ms)
|
||||
|
||||
(async () => {
|
||||
// 1. 严格限制仅允许 POST
|
||||
if ($request.method !== "POST") {
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 读取 Store
|
||||
const rawIds = $persistentStore.read(STORE_KEY);
|
||||
if (!rawIds) {
|
||||
console.log("ℹ️ [旁路] 未读取到 ApprovalCartId,放行。");
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIds = rawIds.split("&").filter(Boolean);
|
||||
if (targetIds.length === 0) {
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 启动通知
|
||||
$notification.post(
|
||||
"🛡️ 防风控批量启动",
|
||||
`队列: ${targetIds.length} 个请求`,
|
||||
`启用随机延迟与熔断保护机制...`
|
||||
);
|
||||
|
||||
let originalBodyTemplate;
|
||||
try {
|
||||
originalBodyTemplate = JSON.parse($request.body);
|
||||
} catch (e) {
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
const baseHeaders = { ...$request.headers };
|
||||
delete baseHeaders["Content-Length"];
|
||||
delete baseHeaders["content-length"];
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
let isBanned = false; // 标记是否被风控
|
||||
|
||||
// ==========================================
|
||||
// 串行循环
|
||||
// ==========================================
|
||||
for (let i = 0; i < targetIds.length; i++) {
|
||||
const id = targetIds[i];
|
||||
|
||||
// 构造请求
|
||||
let currentBody = JSON.parse(JSON.stringify(originalBodyTemplate));
|
||||
currentBody.cartId = id;
|
||||
|
||||
const options = {
|
||||
url: $request.url,
|
||||
method: "POST",
|
||||
headers: baseHeaders,
|
||||
body: JSON.stringify(currentBody)
|
||||
};
|
||||
|
||||
console.log(`🔄 [${i + 1}/${targetIds.length}] 处理 ${id.substring(0,6)}...`);
|
||||
|
||||
const result = await sendRequest(options);
|
||||
|
||||
// --- 结果判定与熔断逻辑 ---
|
||||
if (result && result.status >= 200 && result.status < 300) {
|
||||
console.log(` ✅ 成功`);
|
||||
successCount++;
|
||||
} else if (result.status === 429 || result.status === 403) {
|
||||
// 429: Too Many Requests (请求太快)
|
||||
// 403: Forbidden (可能鉴权失败或被封禁)
|
||||
console.log(` ⛔️ 触发风控 (Code: ${result.status})! 立即停止后续任务!`);
|
||||
$notification.post("⛔️ 任务熔断停止", `检测到微软风控 (${result.status})`, "已停止后续请求以保护账号");
|
||||
isBanned = true;
|
||||
failCount++;
|
||||
break; // 👈 核心:立即跳出循环,不再发包
|
||||
} else {
|
||||
console.log(` ❌ 失败 (Code: ${result ? result.status : 'unknown'})`);
|
||||
failCount++;
|
||||
}
|
||||
|
||||
// --- 随机抖动延迟 ---
|
||||
// 只有不是最后一个,且没有被Ban,才等待
|
||||
if (i < targetIds.length - 1 && !isBanned) {
|
||||
// 生成 1500ms 到 3500ms 之间的随机时间
|
||||
const randomTime = BASE_DELAY + Math.floor(Math.random() * JITTER_MAX);
|
||||
console.log(` ⏳ 随机等待 ${(randomTime/1000).toFixed(2)}s...`);
|
||||
await sleep(randomTime);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 清空 Store
|
||||
$persistentStore.write(null, STORE_KEY);
|
||||
|
||||
// 5. 最终处理
|
||||
if (!isBanned) {
|
||||
const msg = `成功 ${successCount} | 失败 ${failCount}`;
|
||||
$notification.post("✅ 批量完成", msg, "原始请求已拦截,请刷新页面");
|
||||
}
|
||||
|
||||
// 拦截并返回伪造成功
|
||||
$done({
|
||||
response: {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
status: "Complete",
|
||||
message: "Processed by Surge (Anti-Ban Mode)"
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function sendRequest(opts) {
|
||||
return new Promise((resolve) => {
|
||||
$httpClient.post(opts, (err, resp, data) => {
|
||||
if (err) {
|
||||
console.log(`❌ 网络错误: ${err}`);
|
||||
resolve({ status: 0, error: err });
|
||||
} else {
|
||||
resolve({ status: resp.status, body: data });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
58
Scripts/ms_family_block.js
Normal file
58
Scripts/ms_family_block.js
Normal file
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 脚本名称: Microsoft Family ProductId Block
|
||||
* 功能: 拦截 Microsoft Family 中指定的 ProductId 购买请求
|
||||
*/
|
||||
|
||||
// 定义需要拦截的目标 ID 列表
|
||||
const targetIds = [
|
||||
"9PNTSH5SKCL5", "9nfmccp0pm67", "9npbvj8lwsvn", "9pcgszz8zpq2",
|
||||
"9P54FF0VQD7R", "9NCJZN3LBD3P", "9P9CLTVLLHD6", "9NHXDFLDBN6G"
|
||||
];
|
||||
|
||||
function main() {
|
||||
// 1. 检查是否为 POST 请求 (对应 Fiddler 的 oSession.HTTPMethodIs("POST"))
|
||||
if ($request.method !== "POST") {
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取请求体 (对应 oSession.GetRequestBodyAsString())
|
||||
// 注意:需要在模块配置中开启 requires-body=1
|
||||
const body = $request.body;
|
||||
|
||||
if (!body) {
|
||||
$done({});
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 检查是否包含目标 ID (对应 IndexOf + StringComparison.OrdinalIgnoreCase)
|
||||
// 为了实现忽略大小写匹配,我们将 body 和 targetId 都转换为大写进行比较
|
||||
const upperBody = body.toUpperCase();
|
||||
|
||||
let isMatch = false;
|
||||
for (let id of targetIds) {
|
||||
if (upperBody.includes(id.toUpperCase())) {
|
||||
isMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 如果匹配,则拦截 (对应 oSession.oRequest.FailSession(403...))
|
||||
if (isMatch) {
|
||||
console.log(`[MS-Block] 检测到拦截目标,已阻断请求。`);
|
||||
$done({
|
||||
response: {
|
||||
status: 403,
|
||||
headers: {
|
||||
"Content-Type": "text/plain; charset=utf-8"
|
||||
},
|
||||
body: "Blocked by Surge script (Microsoft Family ProductId Rule)"
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 未匹配,放行
|
||||
$done({});
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user