init
This commit is contained in:
129
Surge-main/Discard/AddToCart.js
Normal file
129
Surge-main/Discard/AddToCart.js
Normal file
@@ -0,0 +1,129 @@
|
||||
// ==== 配置项 ==== //
|
||||
const PRODUCT_IDS = $persistentStore.read("TempProductId"); // 格式: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");
|
||||
|
||||
// 如果全部请求成功,则清空 TempProductId
|
||||
if (results.failure.length === 0) {
|
||||
$persistentStore.write("", "TempProductId");
|
||||
}
|
||||
|
||||
$notification.post(
|
||||
"🎮 操作完成",
|
||||
`成功: ${successCount}个`,
|
||||
failureList || "所有请求均成功,TempProductId已清空"
|
||||
);
|
||||
$done();
|
||||
return;
|
||||
}
|
||||
|
||||
const productId = productIdArray[currentIndex];
|
||||
const riskSessionId = generateRiskSessionId();
|
||||
const timeoutId = setTimeout(() => {
|
||||
recordResult(productId, "超时");
|
||||
proceed();
|
||||
}, 30000);
|
||||
|
||||
// 发送skuId为0001的请求
|
||||
$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: "0001", quantity: 1 }] }
|
||||
})
|
||||
}, (error1, response1) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (error1 || response1.status !== 200) {
|
||||
recordResult(productId, `HTTP ${response1 ? response1.status : "Error1"}`);
|
||||
proceed();
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送skuId为0010的请求
|
||||
$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 }] }
|
||||
})
|
||||
}, (error2, response2) => {
|
||||
if (error2 || response2.status !== 200) {
|
||||
recordResult(productId, `HTTP ${response2 ? response2.status : "Error2"}`);
|
||||
} else {
|
||||
// 两次请求都成功
|
||||
handleSuccess(productId);
|
||||
}
|
||||
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, 10); // 请求间隔0.01秒
|
||||
}
|
||||
|
||||
// ==== 启动检查 ==== //
|
||||
if (!MUID || !PRODUCT_IDS) {
|
||||
console.log("⚠️ 配置错误 - 缺少必要参数 MUID 或 PRODUCT_IDS");
|
||||
$notification.post("配置错误", "缺少必要参数", "");
|
||||
$done();
|
||||
} else {
|
||||
console.log("🚀 开始执行请求");
|
||||
sendRequest();
|
||||
}
|
||||
24
Surge-main/Discard/CoreHaloLinkCollector.sgmodule
Normal file
24
Surge-main/Discard/CoreHaloLinkCollector.sgmodule
Normal file
@@ -0,0 +1,24 @@
|
||||
#!name=CoreHalo Link Collector
|
||||
#!desc=Capture Xianyu/Goofish links, expose at https://corehalo.dump/
|
||||
#!author=Ah Long
|
||||
#!category=XBOX
|
||||
#!version=1.4
|
||||
#!homepage=https://github.com/XXhaos/Surge
|
||||
|
||||
[Host]
|
||||
# 将 corehalo.dump 解析到 Surge 虚拟 IP
|
||||
corehalo.dump = 198.18.0.1
|
||||
|
||||
[Script]
|
||||
# 1. 捕获脚本 (保持原样,匹配闲鱼域名)
|
||||
corehalo-capture = type=http-request, pattern=https?://h5\.m\.goofish\.com/.*reminderUrl=.+, requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/XXhaos/Surge/main/corehalo_capture.js
|
||||
|
||||
# 2. 导出脚本 (修改点)
|
||||
# - 正则改为匹配 https://corehalo.dump/ (兼容 http)
|
||||
# - 去掉了 fetch 路径,匹配根路径
|
||||
corehalo-dump = type=http-request, pattern=^https?://corehalo\.dump/?$, requires-body=0, timeout=5, script-path=https://raw.githubusercontent.com/XXhaos/Surge/main/corehalo_dump.js
|
||||
|
||||
[MITM]
|
||||
# 必须包含 corehalo.dump 才能解密 HTTPS 流量
|
||||
# 同时也需要包含闲鱼域名(h5.m.goofish.com)以确保捕获功能正常
|
||||
hostname = %APPEND% corehalo.dump, h5.m.goofish.com
|
||||
41
Surge-main/Discard/GetProductId.js
Normal file
41
Surge-main/Discard/GetProductId.js
Normal file
@@ -0,0 +1,41 @@
|
||||
// Surge 脚本:从特定URL中提取并更新 TempProductId
|
||||
|
||||
const key = "TempProductId";
|
||||
|
||||
// 获取已有 TempProductId 的值
|
||||
const existingIds = $persistentStore.read(key);
|
||||
|
||||
// 从URL提取新的产品ID (仅匹配特定的 xbox 链接)
|
||||
const url = $request.url;
|
||||
|
||||
// 改进正则表达式,匹配 store/ 后面的游戏ID(支持大小写)
|
||||
const matches = url.match(/^https:\/\/www\.xbox\.com\/[eE][nN]-[uU][sS]\/games\/store\/[^\/]+\/([^\/?]+)/);
|
||||
|
||||
if (matches && matches[1]) {
|
||||
const newProductId = matches[1];
|
||||
|
||||
// 将已有的 TempProductId 分割为数组
|
||||
const existingIdArray = existingIds ? existingIds.split("&") : [];
|
||||
|
||||
if (!existingIdArray.includes(newProductId)) {
|
||||
// 如果已有内容不为空,则先加入 '&' 再追加新ID
|
||||
const finalProductId = existingIdArray.length > 0
|
||||
? `${existingIdArray.join("&")}&${newProductId}`
|
||||
: newProductId;
|
||||
|
||||
// 更新 TempProductId 的值
|
||||
$persistentStore.write(finalProductId, key);
|
||||
|
||||
// 控制台输出操作
|
||||
console.log(`✅ 已更新 TempProductId: ${finalProductId}`);
|
||||
|
||||
// 发送通知表示操作完成
|
||||
$notification.post("✅ 操作成功", "已更新 TempProductId", finalProductId);
|
||||
} else {
|
||||
console.log(`⚠️ TempProductId 未更新,已存在: ${newProductId}`);
|
||||
$notification.post("⚠️ 操作跳过", "TempProductId 已存在", newProductId);
|
||||
}
|
||||
}
|
||||
|
||||
// 结束脚本
|
||||
$done();
|
||||
51
Surge-main/Discard/corehalo_capture.js
Normal file
51
Surge-main/Discard/corehalo_capture.js
Normal file
@@ -0,0 +1,51 @@
|
||||
// corehalo_capture.js
|
||||
//
|
||||
// 触发:请求 URL 匹配 h5.m.goofish.com/...reminderUrl=...
|
||||
// 逻辑:
|
||||
// 1. 从 $request.url 提取 reminderUrl= 后的编码串
|
||||
// 2. decodeURIComponent -> 真正目标链接 realLink
|
||||
// 3. 保存到 persistentStore("corehalo_links")
|
||||
// 4. 发通知,提示捕获到了什么
|
||||
|
||||
let reqUrl = $request && $request.url ? $request.url : "";
|
||||
|
||||
// 提取 reminderUrl= 后面的编码内容
|
||||
let m = reqUrl.match(/reminderUrl=([^&]+)/);
|
||||
|
||||
if (m && m[1]) {
|
||||
// decode 出真实外链
|
||||
let realLink = decodeURIComponent(m[1]);
|
||||
|
||||
// 从持久化存储读现有列表
|
||||
let raw = $persistentStore.read("corehalo_links") || "[]";
|
||||
let list;
|
||||
try {
|
||||
list = JSON.parse(raw);
|
||||
if (!Array.isArray(list)) {
|
||||
list = [];
|
||||
}
|
||||
} catch (e) {
|
||||
list = [];
|
||||
}
|
||||
|
||||
// 去重后推入
|
||||
let added = false;
|
||||
if (!list.includes(realLink)) {
|
||||
list.push(realLink);
|
||||
$persistentStore.write(JSON.stringify(list), "corehalo_links");
|
||||
added = true;
|
||||
}
|
||||
|
||||
// 给你发一条通知,告诉你本次抓到的情况
|
||||
// 标题:CoreHalo Capture
|
||||
// 副标题:Added / Duplicate
|
||||
// 正文:具体链接
|
||||
$notification.post(
|
||||
"CoreHalo Capture",
|
||||
added ? "Added to list" : "Already in list",
|
||||
realLink
|
||||
);
|
||||
}
|
||||
|
||||
// 放行原始请求
|
||||
$done({});
|
||||
84
Surge-main/Discard/corehalo_dump.js
Normal file
84
Surge-main/Discard/corehalo_dump.js
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* CoreHalo Web Dump
|
||||
* 作用:访问 http://corehalo.dump/fetch 时,以网页形式显示捕获的链接并清空存储
|
||||
*/
|
||||
|
||||
// 1. 读取数据
|
||||
let raw = $persistentStore.read("corehalo_links") || "[]";
|
||||
let list;
|
||||
try {
|
||||
list = JSON.parse(raw);
|
||||
if (!Array.isArray(list)) list = [];
|
||||
} catch (e) {
|
||||
list = [];
|
||||
}
|
||||
|
||||
const count = list.length;
|
||||
|
||||
// 2. 发送通知 (保持原有的系统通知功能)
|
||||
if (count > 0) {
|
||||
$notification.post("CoreHalo Dump", `捕获 ${count} 条链接`, "已在浏览器显示并清空");
|
||||
} else {
|
||||
$notification.post("CoreHalo Dump", "没有新链接", "列表为空");
|
||||
}
|
||||
|
||||
// 3. 准备 HTML 内容
|
||||
// 生成列表项 HTML
|
||||
const listHtml = list.map((link, index) => `
|
||||
<div class="item">
|
||||
<span class="index">${index + 1}.</span>
|
||||
<a href="${link}" target="_blank">${link}</a>
|
||||
</div>
|
||||
`).join("");
|
||||
|
||||
// 页面样式
|
||||
const css = `
|
||||
<style>
|
||||
body { font-family: -apple-system, sans-serif; background-color: #f4f4f4; padding: 20px; display: flex; justify-content: center; }
|
||||
.card { background: white; width: 100%; max-width: 600px; padding: 20px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.05); }
|
||||
h1 { margin-top: 0; font-size: 20px; color: #333; border-bottom: 1px solid #eee; padding-bottom: 10px; }
|
||||
.count-tag { background: #007aff; color: white; padding: 2px 8px; border-radius: 10px; font-size: 14px; margin-left: 8px; vertical-align: middle; }
|
||||
.empty-state { text-align: center; color: #999; padding: 40px 0; }
|
||||
.list-container { max-height: 70vh; overflow-y: auto; }
|
||||
.item { padding: 12px 0; border-bottom: 1px solid #f0f0f0; display: flex; align-items: flex-start; word-break: break-all; }
|
||||
.item:last-child { border-bottom: none; }
|
||||
.index { color: #999; font-size: 12px; margin-right: 10px; min-width: 20px; margin-top: 2px; }
|
||||
a { color: #333; text-decoration: none; font-size: 13px; line-height: 1.4; transition: color 0.2s; }
|
||||
a:hover { color: #007aff; }
|
||||
.footer { margin-top: 15px; font-size: 12px; color: #aaa; text-align: center; }
|
||||
</style>
|
||||
`;
|
||||
|
||||
const bodyContent = count > 0
|
||||
? `<div class="list-container">${listHtml}</div>`
|
||||
: `<div class="empty-state">📭 暂无捕获的链接</div>`;
|
||||
|
||||
const html = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>CoreHalo Links</title>
|
||||
${css}
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>捕获列表 <span class="count-tag">${count}</span></h1>
|
||||
${bodyContent}
|
||||
<div class="footer">列表已自动清空</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// 4. 清空存储 (提取后即焚)
|
||||
$persistentStore.write("[]", "corehalo_links");
|
||||
|
||||
// 5. 返回网页响应
|
||||
$done({
|
||||
response: {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "text/html;charset=utf-8" },
|
||||
body: html
|
||||
}
|
||||
});
|
||||
12
Surge-main/Modules/BuyRequestReplace.sgmodule
Normal file
12
Surge-main/Modules/BuyRequestReplace.sgmodule
Normal file
@@ -0,0 +1,12 @@
|
||||
#!name=Xbox Buy Request Replace
|
||||
#!desc=替换购买请求参数 (RequestParentalApproval)
|
||||
#!category=XBOX
|
||||
#!author=Ah Long
|
||||
|
||||
[Script]
|
||||
# 替换购买请求参数
|
||||
# 触发条件:RequestParentalApproval 接口
|
||||
BuyRequestReplace = type=http-request, pattern=^https://buynow\.production\.store-web\.dynamics\.com/v1\.0/Cart/RequestParentalApproval, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/BuyRequestReplace.js, requires-body=1, max-size=0, binary-body-mode=0, script-update-interval=0
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% buynow.production.store-web.dynamics.com
|
||||
11
Surge-main/Modules/MS-Family-Block.sgmodule
Normal file
11
Surge-main/Modules/MS-Family-Block.sgmodule
Normal file
@@ -0,0 +1,11 @@
|
||||
#!name=Microsoft Family Block
|
||||
#!desc=拦截 Microsoft Family 中指定的 ProductId 购买请求 (9PNTSH5SKCL5等)
|
||||
#!category=XBOX
|
||||
#!author=Ah Long
|
||||
|
||||
[Script]
|
||||
# logic: 检查 POST body 是否包含黑名单中的 ID
|
||||
MS_Family_Block = type=http-request, pattern=^https://account\.microsoft\.com/family/api/buy/requests/complete(\?.*)?$, requires-body=1, max-size=0, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/ms_family_block.js, script-update-interval=-1, timeout=10, debug=0, script-text=var targetIds=["9PNTSH5SKCL5","9nfmccp0pm67","9npbvj8lwsvn","9pcgszz8zpq2","9P54FF0VQD7R","9NCJZN3LBD3P","9P9CLTVLLHD6","9NHXDFLDBN6G"];var body=$request.body;if($request.method==="POST"&&body){var upperBody=body.toUpperCase();for(var i=0;i<targetIds.length;i++){if(upperBody.indexOf(targetIds[i].toUpperCase())!==-1){console.log("[MS-Block] Intercepted blocked ProductId: "+targetIds[i]);$done({response:{status:403,headers:{"Content-Type":"text/plain"},body:"Blocked by Surge Module"}});break}}$done({})}else{$done({})}
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% account.microsoft.com
|
||||
171
Surge-main/Modules/Q-Search-Plus.sgmodule
Normal file
171
Surge-main/Modules/Q-Search-Plus.sgmodule
Normal file
@@ -0,0 +1,171 @@
|
||||
#!name=Q-Search Plus
|
||||
#!desc=Bing搜索重定向
|
||||
#引用自墨鱼@ddgksf2013,转换为Surge格式并加入自用重写,修改日期2024-07-23
|
||||
|
||||
|
||||
##############################################
|
||||
# - Safari 内输入以下格式命令快速指定搜索引擎
|
||||
# - 【命令+空格+关键词】或者【关键词+空格+命令】
|
||||
# - 注:请先进入设置更改 Safari 默认搜索为 Bing
|
||||
# - 更新时间:2024-01-31
|
||||
# - 墨鱼自用全能搜索V2.0(135)
|
||||
# - 墨鱼手记
|
||||
# - 如需引用请注明出处-> https://t.me/ddgksf2021 谢谢合作!
|
||||
# - https://github.com/ddgksf2013/Rewrite/raw/master/Html/Q-Search.conf
|
||||
##############################################
|
||||
|
||||
|
||||
[URL Rewrite]
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>自用
|
||||
# TMDB
|
||||
^https:\/\/.*bing.com\/search\?q=tmdb\+([^&]+).+ https://www.themoviedb.org/search?query=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+tmdb.+ https://www.themoviedb.org/search?query=$1 302
|
||||
# ng (切换至尼日利亚区)
|
||||
^https:\/\/.*bing.com\/search\?q=ng&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143561&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=ng&urlDesc= 302
|
||||
# pp xxx (perplexity)
|
||||
^https:\/\/.*bing.com\/search\?q=pp\+([^&]+).+ https://www.perplexity.ai/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+pp.+ https://www.perplexity.ai/search?q=$1 302
|
||||
#tf (TestFlight)
|
||||
^https:\/\/.*bing.com\/search\?q=tf(\+|%20)([^&]+).+ https://www.google.com/search?as_q=$2&as_sitesearch=testflight.apple.com 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>翻译
|
||||
# yd xxx (有道词典)
|
||||
^https:\/\/.*bing.com\/search\?q=yd\+([^&]+).+ http://dict.youdao.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+yd.+ http://dict.youdao.com/search?q=$1 302
|
||||
# trc xxx (Google 译至中)
|
||||
^https:\/\/.*bing.com\/search\?q=trc\+([^&]+).+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=zh-CN&text=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+trc.+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=zh-CN&text=$1 302
|
||||
# tre xxx (Google 译至英)
|
||||
^https:\/\/.*bing.com\/search\?q=tre\+([^&]+).+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=en&text=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+tre.+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=en&text=$1 302
|
||||
# trj xxx (Google 译至日)
|
||||
^https:\/\/.*bing.com\/search\?q=trj\+([^&]+).+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=ja&text=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+trj.+ https://translate.google.com/#view=home&op=translate&sl=auto&tl=ja&text=$1 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>社区
|
||||
# tt xxx (头条)
|
||||
^https:\/\/.*bing.com\/search\?q=tt\+([^&]+).+ https://so.toutiao.com/search?keyword=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+tt.+ https://so.toutiao.com/search?keyword=$1 302
|
||||
# db xxx (豆瓣)
|
||||
^https:\/\/.*bing.com\/search\?q=db\+([^&]+).+ https://m.douban.com/search?query=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+db.+ https://m.douban.com/search?query=$1 302
|
||||
# zh xxx (知乎)
|
||||
^https:\/\/.*bing.com\/search\?q=zh\+([^&]+).+ http://www.zhihu.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+zh.+ http://www.zhihu.com/search?q=$1 302
|
||||
# wb xxx (微博)
|
||||
^https:\/\/.*bing.com\/search\?q=wb\+([^&]+).+ https://s.weibo.com/weibo/$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+wb.+ https://s.weibo.com/weibo/$1 302
|
||||
# wx xxx (微信)
|
||||
^https:\/\/.*bing.com\/search\?q=wx\+([^&]+).+ https://weixin.sogou.com/weixinwap?query=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+wx.+ https://weixin.sogou.com/weixinwap?query=$1 302
|
||||
# up xxx (Unsplash)
|
||||
^https:\/\/.*bing.com\/search\?q=up\+([^&]+).+ https://unsplash.com/s/photos/$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+up.+ https://unsplash.com/s/photos/$1 302
|
||||
# sspai xxx (少数派站内搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=sspai\+([^&]+).+ https://sspai.com/search/post/$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+sspai.+ https://sspai.com/search/post/$1 302
|
||||
# ssp xxx (Google 搜索少数派)
|
||||
^https:\/\/.*bing.com\/search\?q=ssp\+([^&]+).+ https://www.google.com/search?as_q=$1&as_sitesearch=sspai.com 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+ssp.+ https://www.google.com/search?as_q=$1&as_sitesearch=sspai.com 302
|
||||
# tw xxx (Twitter)
|
||||
^https:\/\/.*bing.com\/search\?q=tw\+([^&]+).+ https://twitter.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+tw.+ https://twitter.com/search?q=$1 302
|
||||
# gh xxx (GitHub)
|
||||
^https:\/\/.*bing.com\/search\?q=gh\+([^&]+).+ https://github.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+gh.+ https://github.com/search?q=$1 302
|
||||
# gu xxx (GitHub User)
|
||||
^https:\/\/.*bing.com\/search\?q=gu\+([^&]+).+ https://github.com/search?q=$1&type=users 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+gu.+ https://github.com/search?q=$1&type=users 302
|
||||
# gc xxx (GitHub Code)
|
||||
^https:\/\/.*bing.com\/search\?q=gc\+([^&]+).+ https://github.com/search?o=desc&q=$1&s=indexed&type=Code 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+gc.+ https://github.com/search?o=desc&q=$1&s=indexed&type=Code 302
|
||||
# so xxx (Stack Overflow)
|
||||
^https:\/\/.*bing.com\/search\?q=so\+([^&]+).+ https://stackoverflow.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+so.+ https://stackoverflow.com/search?q=$1 302
|
||||
# se xxx (StackExchange)
|
||||
^https:\/\/.*bing.com\/search\?q=se\+([^&]+).+ https://stackexchange.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+se.+ https://stackexchange.com/search?q=$1 302
|
||||
# wa xxx (WolframAlpha)
|
||||
^https:\/\/.*bing.com\/search\?q=wa\+([^&]+).+ https://www.wolframalpha.com/input/?i=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+wa.+ https://www.wolframalpha.com/input/?i=$1 302
|
||||
# rd xxx (Reddit)
|
||||
^https:\/\/.*bing.com\/search\?q=rd\+([^&]+).+ https://www.reddit.com/search?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+rd.+ https://www.reddit.com/search?q=$1 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>购物
|
||||
# zdm xxx (什么值得买)
|
||||
^https:\/\/.*bing.com\/search\?q=zdm\+([^&]+).+ https://search.m.smzdm.com/?v=b&s=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+zdm.+ https://search.m.smzdm.com/?v=b&s=$1 302
|
||||
# jd xxx (京东)
|
||||
^https:\/\/.*bing.com\/search\?q=jd\+([^&]+).+ openapp.jdmobile://virtual?params={"des":"productList","keyWord":"$1","from":"search","category":"jump"} 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+jd.+ openapp.jdmobile://virtual?params={"des":"productList","keyWord":"$1","from":"search","category":"jump"} 302
|
||||
# tb xxx (淘宝)
|
||||
^https:\/\/.*bing.com\/search\?q=tb\+([^&]+).+ taobao://s.taobao.com?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\q=([^+]+)\+tb.+ taobao://s.taobao.com?q=$1 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>视频
|
||||
# yt xxx (YouTube)
|
||||
^https:\/\/.*bing.com\/search\?q=yt\+([^&]+).+ https://www.youtube.com/results?search_query=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+yt.+ https://www.youtube.com/results?search_query=$1 302
|
||||
# bli xxx (哔哩哔哩)
|
||||
^https:\/\/.*bing.com\/search\?q=bli\+([^&]+).+ https://m.bilibili.com/search?keyword=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+bli.+ https://m.bilibili.com/search?keyword=$1 302
|
||||
# gd xxx (Google 搜索 Google Drive 资源)
|
||||
^https:\/\/.*bing.com\/search\?q=gd\+([^&]+).+ https://www.google.com/search?q=%22Google+Drive%22+$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+gd.+ https://www.google.com/search?q=%22Google+Drive%22+$1 302
|
||||
# tgd xxx (t.me/gdurl 搜索 Google Drive 资源)
|
||||
^https:\/\/.*bing.com\/search\?q=tgd\+([^&]+).+ https://t.me/s/gdurl?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+tgd.+ https://t.me/s/gdurl?q=$1 302
|
||||
# ph xxx (PornHub)
|
||||
^https:\/\/.*bing.com\/search\?q=ph\+([^&]+).+ https://cn.pornhub.com/video/search?search=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+ph.+ https://cn.pornhub.com/video/search?search=$1 302
|
||||
# af xxx (Acfun)
|
||||
^https:\/\/.*bing.com\/search\?q=af\+([^&]+).+ https://www.acfun.cn/search?keyword=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+af.+ https://www.acfun.cn/search?keyword=$1 302
|
||||
# ys xxx (搜片)
|
||||
^https:\/\/.*bing.com\/search\?q=ys\+([^&]+).+ https://soupian.icu/search?key=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+ys.+ https://soupian.icu/search?key=$1 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>苹果商店切换
|
||||
# cn (切换至中国区)
|
||||
^https:\/\/.*bing.com\/search\?q=cn&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143465&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=cn&urlDesc= 302
|
||||
# hk (切换至香港区)
|
||||
^https:\/\/.*bing.com\/search\?q=hk&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143463&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=hk&urlDesc= 302
|
||||
# tw (切换至台湾区)
|
||||
^https:\/\/.*bing.com\/search\?q=tw&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143470&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=tw&urlDesc= 302
|
||||
# us (切换至美国区)
|
||||
^https:\/\/.*bing.com\/search\?q=us&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143441&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=us&urlDesc= 302
|
||||
# jp (切换至日本区)
|
||||
^https:\/\/.*bing.com\/search\?q=jp&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143462&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=jp&urlDesc= 302
|
||||
# kr (切换至韩国区)
|
||||
^https:\/\/.*bing.com\/search\?q=kr&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143466&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=kr&urlDesc= 302
|
||||
# tr (切换至土耳其区)
|
||||
^https:\/\/.*bing.com\/search\?q=tr&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143480&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=tr&urlDesc= 302
|
||||
# sg (切换至新加坡区)
|
||||
^https:\/\/.*bing.com\/search\?q=sg&.+ https://itunes.apple.com/WebObjects/MZStore.woa/wa/resetAndRedirect?dsf=143464&mt=8&url=/WebObjects/MZStore.woa/wa/viewSoftware?mt=8&id=1108187390&cc=tr&urlDesc= 302
|
||||
|
||||
#>>>>>>>>>>>>>>>>>>>>>>>搜索
|
||||
# bd xxx (百度搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=bd\+([^&]+).+ https://www.baidu.com/s?wd=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=((.(?!bd))+)\+bd.+ https://www.baidu.com/s?wd=$1 302
|
||||
# wk xxx (维基搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=wk\+([^&]+).+ https://zh.wikipedia.org/wiki/$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+wk.+ https://zh.wikipedia.org/wiki/$1 302
|
||||
# wz xxx (无追搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=wz\+([^&]+).+ https://www.wuzhuiso.com/s?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+wz.+ https://www.wuzhuiso.com/s?q=$1 302
|
||||
# yh xxx (油猴搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=yh\+([^&]+).+ https://greasyfork.org/zh-CN/scripts?q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+yh.+ https://greasyfork.org/zh-CN/scripts?q=$1 302
|
||||
# gi xxx (Google 图片)
|
||||
^https:\/\/.*bing.com\/search\?q=gi\+([^&]+).+ https://www.google.com/search?&tbm=isch&q=$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+gi.+ https://www.google.com/search?&tbm=isch&q=$1 302
|
||||
# ios xxx (苹果应用搜索)
|
||||
^https:\/\/.*bing.com\/search\?q=ios\+([^&]+).+ https://www.qimai.cn/search/index/search/$1 302
|
||||
^https:\/\/.*bing.com\/search\?q=([^+]+)\+ios.+ https://www.qimai.cn/search/index/search/$1 302
|
||||
# xxx (无指令默认为 Google)
|
||||
^https:\/\/.*bing.com\/search\?q=([^&]+).+ https://www.google.com/search?q=$1 302
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% *.bing.com
|
||||
16
Surge-main/Modules/XboxCartWeb.sgmodule
Normal file
16
Surge-main/Modules/XboxCartWeb.sgmodule
Normal file
@@ -0,0 +1,16 @@
|
||||
#!name=Xbox Cart Web
|
||||
#!desc=访问 https://addtocart.com 触发远程脚本
|
||||
#!author=Ah Long
|
||||
#!category=XBOX
|
||||
|
||||
[Host]
|
||||
# 核心:将 addtocart.com 解析到 Surge 内部 IP
|
||||
# 这样手机发出的请求会被 Surge 直接截获,不会去公网找服务器
|
||||
addtocart.com = 198.18.0.1
|
||||
|
||||
[Script]
|
||||
# 正则匹配:支持 http 和 https,域名为 addtocart.com
|
||||
xbox_web_runner = type=http-request, pattern=^https?://addtocart\.com/?.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/NewAddToCart_Web.js, timeout=60
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% addtocart.com
|
||||
26
Surge-main/Modules/XboxParamCapturer.sgmodule
Normal file
26
Surge-main/Modules/XboxParamCapturer.sgmodule
Normal file
@@ -0,0 +1,26 @@
|
||||
#!name=Xbox Param Capturer
|
||||
#!desc=抓取 Xbox 参数 (全区/防缓存/忽略大小写)
|
||||
#!author=Ah Long
|
||||
#!category=XBOX
|
||||
|
||||
[Script]
|
||||
# 1. 抓 Product 参数 (Response)
|
||||
# 修复:优化正则写法,忽略大小写
|
||||
XboxProductList = type=http-response, pattern=(?i)^https://emerald\.xboxservices\.com/xboxcomfd/productActions/.*locale=en-us, requires-body=1, max-size=0, binary-body-mode=0, timeout=60, script-update-interval=0, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/XboxProductList.js
|
||||
|
||||
# 2. 获取 Authorization & CartId
|
||||
# 修复:加上 (?i) 忽略 Cart/cart 大小写,去掉强制结尾的 \?
|
||||
Xbox_Auth_CartId = type=http-request, pattern=(?i)^https://cart\.production\.store-web\.dynamics\.com/v1\.0/cart/eligibilityCheck, requires-body=0, script-update-interval=0, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/authorization%26cartId.js
|
||||
|
||||
# 3. 获取 CartParameter
|
||||
# 修复:加上 (?i) 忽略大小写
|
||||
Xbox_Mscv_MUID = type=http-request, pattern=(?i)^https://cart\.production\.store-web\.dynamics\.com/v1\.0/cart/loadCart, requires-body=0, script-update-interval=0, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/CartParameter.js
|
||||
|
||||
[Header Rewrite]
|
||||
# 【防缓存】强制删除 emerald 域名的协商缓存头
|
||||
# 修复:简化正则,确保匹配命中
|
||||
^https://emerald\.xboxservices\.com/xboxcomfd/productActions/ header-del If-Modified-Since
|
||||
^https://emerald\.xboxservices\.com/xboxcomfd/productActions/ header-del If-None-Match
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% emerald.xboxservices.com, *.dynamics.com
|
||||
26
Surge-main/Modules/XboxWebController.sgmodule
Normal file
26
Surge-main/Modules/XboxWebController.sgmodule
Normal file
@@ -0,0 +1,26 @@
|
||||
#!name=Xbox Web Controller
|
||||
#!desc=通过访问虚拟域名触发脚本:\n1. 访问 https://addtocart.com -> 触发添加/购买脚本\n2. 访问 https://clearlist.com -> 清空本地 ProductList\n3. 访问 https://clearapprovalcartid.com -> 清空 ApproveCartId\n4. 访问 https://syncxbox.com -> 从云端同步数据并清理云端队列
|
||||
#!author=Ah Long & XXhaos
|
||||
#!category=XBOX
|
||||
|
||||
[Host]
|
||||
addtocart.com = 198.18.0.1
|
||||
clearlist.com = 198.18.0.1
|
||||
clearapprovalcartid.com = 198.18.0.1
|
||||
syncxbox.com = 198.18.0.1
|
||||
|
||||
[Script]
|
||||
# 1. 触发远程添加/购买脚本
|
||||
xbox_web_runner = type=http-request, pattern=^https?://addtocart\.com/?.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/NewAddToCart_Web.js, timeout=60
|
||||
|
||||
# 2. 清空本地 XboxProductList
|
||||
xbox_clear_web = type=http-request, pattern=^https?://clearlist\.com/?.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/main/Scripts/ClearXboxProductList_Web.js, timeout=10
|
||||
|
||||
# 3. 清空 ApprovalCartId
|
||||
clear_cart_id = type=http-request, pattern=^https?://clearapprovalcartid\.com/?.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/ClearApprovalCartId.js, timeout=10
|
||||
|
||||
# 4. 从云端同步数据 (确保 GitHub 上的文件名和路径 100% 正确)
|
||||
xbox_sync_cloud = type=http-request, pattern=^https?://syncxbox\.com/?.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/SyncXboxCloud.js, timeout=30
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% addtocart.com, clearlist.com, clearapprovalcartid.com, syncxbox.com
|
||||
17
Surge-main/Modules/Xbox_Rewrite.sgmodule
Normal file
17
Surge-main/Modules/Xbox_Rewrite.sgmodule
Normal file
@@ -0,0 +1,17 @@
|
||||
#!name=Xbox Rewrite Collection
|
||||
#!desc=Win 商店链接重定向 & 强制锁美区 (保留阿根廷区)
|
||||
#!category=XBOX
|
||||
#!author=Ah Long
|
||||
|
||||
[URL Rewrite]
|
||||
# Microsoft Store -> 对应的 CoreHalo 游戏页(提取 12 位 ID)
|
||||
(?i)^https?:\/\/(?:www\.)?microsoft\.com\/en-us\/store\/.*?([a-z0-9]{12})(?:[\/\?#]|$) https://www.xbox.com/en-us/games/store/corehalo/$1 302
|
||||
|
||||
# 非 en-us 的 xbox.com 统一跳转到 en-us(排除 es-ar / es-AR / en-us)
|
||||
^https:\/\/www\.xbox\.com\/(?!es-ar|es-AR|en-us)([a-zA-Z]{2}-[a-zA-Z]{2})(\/.*)$ https://www.xbox.com/en-us$2 302
|
||||
|
||||
# app.corehalo.com 的 r 区域参数统一改为 en-us,保留其他 query 参数
|
||||
"^https:\/\/app\.corehalo\.com\/ms\/link\/go\?(.*&)?r=(?!es-ar|es-AR|en-us)([a-zA-Z]{2}-[a-zA-Z]{2})(&.*)?$" "https://app.corehalo.com/ms/link/go?$1r=en-us$3" 302
|
||||
|
||||
[MITM]
|
||||
hostname = %APPEND% www.microsoft.com, www.xbox.com, app.corehalo.com
|
||||
13
Surge-main/Modules/batch_approval.sgmodule
Normal file
13
Surge-main/Modules/batch_approval.sgmodule
Normal file
@@ -0,0 +1,13 @@
|
||||
#!name=Microsoft Family Batch Approve
|
||||
#!desc=自动抓取CartId并替换批量购买流程,购买完成后自动清理
|
||||
#!category=XBOX
|
||||
#!author=Ah Long
|
||||
|
||||
[Script]
|
||||
ApprovalCartId = type=http-request, pattern=^https:\/\/buynow\.production\.store-web\.dynamics\.com\/v1\.0\/cart\/updateCart.*, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/ApprovalCartId.js, requires-body=0
|
||||
|
||||
batch_purchase_send = type=http-request, pattern=^https:\/\/buynow\.production\.store-web\.dynamics\.com\/v1\.0\/Cart\/purchase\?appId=BuyNow, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/batch_purchase_send.js, requires-body=1, max-size=1048576, timeout=60, binary-body-mode=0, script-update-interval=0
|
||||
|
||||
ApprovalCartClean = type=http-request, pattern=^https:\/\/account\.microsoft\.com\/family\/api\/buy\/requests\/complete, script-path=https://raw.githubusercontent.com/XXhaos/Surge/refs/heads/main/Scripts/AutoClearApprovalCartId.js, requires-body=0
|
||||
[MITM]
|
||||
hostname = %APPEND% buynow.production.store-web.dynamics.com, production.store-web.dynamics.com, account.microsoft.com
|
||||
2
Surge-main/README.md
Normal file
2
Surge-main/README.md
Normal file
@@ -0,0 +1,2 @@
|
||||
# Surge
|
||||
自用
|
||||
103
Surge-main/Scripts/Add8TFDlcToCart.js
Normal file
103
Surge-main/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
Surge-main/Scripts/ApprovalCartId.js
Normal file
56
Surge-main/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
Surge-main/Scripts/AutoClearApprovalCartId.js
Normal file
22
Surge-main/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
Surge-main/Scripts/BuyRequestReplace.js
Normal file
83
Surge-main/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
Surge-main/Scripts/CartParameter.js
Normal file
89
Surge-main/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
Surge-main/Scripts/ClearApprovalCartId.js
Normal file
48
Surge-main/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
Surge-main/Scripts/ClearXboxProductList_Web.js
Normal file
48
Surge-main/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
Surge-main/Scripts/NewAddToCart.js
Normal file
156
Surge-main/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
Surge-main/Scripts/NewAddToCart_Web.js
Normal file
102
Surge-main/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
Surge-main/Scripts/SyncXboxCloud.js
Normal file
171
Surge-main/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
Surge-main/Scripts/XboxProductList.js
Normal file
113
Surge-main/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
Surge-main/Scripts/authorization&cartId.js
Normal file
44
Surge-main/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
Surge-main/Scripts/batch_purchase_send.js
Normal file
141
Surge-main/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
Surge-main/Scripts/ms_family_block.js
Normal file
58
Surge-main/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