From 75b01d3e58ed41ed8e669b7b663e3fade478b478 Mon Sep 17 00:00:00 2001 From: hbxnlsy Date: Thu, 2 Apr 2026 21:02:16 +0800 Subject: [PATCH] init --- Discard/AddToCart.js | 129 +++++++++++++ Discard/CoreHaloLinkCollector.sgmodule | 24 +++ Discard/GetProductId.js | 41 +++++ Discard/corehalo_capture.js | 51 ++++++ Discard/corehalo_dump.js | 84 +++++++++ Modules/BuyRequestReplace.sgmodule | 12 ++ Modules/MS-Family-Block.sgmodule | 11 ++ Modules/Q-Search-Plus.sgmodule | 171 ++++++++++++++++++ Modules/XboxCartWeb.sgmodule | 16 ++ Modules/XboxParamCapturer.sgmodule | 26 +++ Modules/XboxWebController.sgmodule | 26 +++ Modules/Xbox_Rewrite.sgmodule | 17 ++ Modules/batch_approval.sgmodule | 13 ++ README.md | 2 + Scripts/Add8TFDlcToCart.js | 103 +++++++++++ Scripts/ApprovalCartId.js | 56 ++++++ Scripts/AutoClearApprovalCartId.js | 22 +++ Scripts/BuyRequestReplace.js | 83 +++++++++ Scripts/CartParameter.js | 89 +++++++++ Scripts/ClearApprovalCartId.js | 48 +++++ Scripts/ClearXboxProductList_Web.js | 48 +++++ Scripts/NewAddToCart.js | 156 ++++++++++++++++ Scripts/NewAddToCart_Web.js | 102 +++++++++++ Scripts/SyncXboxCloud.js | 171 ++++++++++++++++++ Scripts/XboxProductList.js | 113 ++++++++++++ Scripts/authorization&cartId.js | 44 +++++ Scripts/batch_purchase_send.js | 141 +++++++++++++++ Scripts/ms_family_block.js | 58 ++++++ Surge-main/Discard/AddToCart.js | 129 +++++++++++++ .../Discard/CoreHaloLinkCollector.sgmodule | 24 +++ Surge-main/Discard/GetProductId.js | 41 +++++ Surge-main/Discard/corehalo_capture.js | 51 ++++++ Surge-main/Discard/corehalo_dump.js | 84 +++++++++ Surge-main/Modules/BuyRequestReplace.sgmodule | 12 ++ Surge-main/Modules/MS-Family-Block.sgmodule | 11 ++ Surge-main/Modules/Q-Search-Plus.sgmodule | 171 ++++++++++++++++++ Surge-main/Modules/XboxCartWeb.sgmodule | 16 ++ Surge-main/Modules/XboxParamCapturer.sgmodule | 26 +++ Surge-main/Modules/XboxWebController.sgmodule | 26 +++ Surge-main/Modules/Xbox_Rewrite.sgmodule | 17 ++ Surge-main/Modules/batch_approval.sgmodule | 13 ++ Surge-main/README.md | 2 + Surge-main/Scripts/Add8TFDlcToCart.js | 103 +++++++++++ Surge-main/Scripts/ApprovalCartId.js | 56 ++++++ Surge-main/Scripts/AutoClearApprovalCartId.js | 22 +++ Surge-main/Scripts/BuyRequestReplace.js | 83 +++++++++ Surge-main/Scripts/CartParameter.js | 89 +++++++++ Surge-main/Scripts/ClearApprovalCartId.js | 48 +++++ .../Scripts/ClearXboxProductList_Web.js | 48 +++++ Surge-main/Scripts/NewAddToCart.js | 156 ++++++++++++++++ Surge-main/Scripts/NewAddToCart_Web.js | 102 +++++++++++ Surge-main/Scripts/SyncXboxCloud.js | 171 ++++++++++++++++++ Surge-main/Scripts/XboxProductList.js | 113 ++++++++++++ Surge-main/Scripts/authorization&cartId.js | 44 +++++ Surge-main/Scripts/batch_purchase_send.js | 141 +++++++++++++++ Surge-main/Scripts/ms_family_block.js | 58 ++++++ 56 files changed, 3714 insertions(+) create mode 100644 Discard/AddToCart.js create mode 100644 Discard/CoreHaloLinkCollector.sgmodule create mode 100644 Discard/GetProductId.js create mode 100644 Discard/corehalo_capture.js create mode 100644 Discard/corehalo_dump.js create mode 100644 Modules/BuyRequestReplace.sgmodule create mode 100644 Modules/MS-Family-Block.sgmodule create mode 100644 Modules/Q-Search-Plus.sgmodule create mode 100644 Modules/XboxCartWeb.sgmodule create mode 100644 Modules/XboxParamCapturer.sgmodule create mode 100644 Modules/XboxWebController.sgmodule create mode 100644 Modules/Xbox_Rewrite.sgmodule create mode 100644 Modules/batch_approval.sgmodule create mode 100644 README.md create mode 100644 Scripts/Add8TFDlcToCart.js create mode 100644 Scripts/ApprovalCartId.js create mode 100644 Scripts/AutoClearApprovalCartId.js create mode 100644 Scripts/BuyRequestReplace.js create mode 100644 Scripts/CartParameter.js create mode 100644 Scripts/ClearApprovalCartId.js create mode 100644 Scripts/ClearXboxProductList_Web.js create mode 100644 Scripts/NewAddToCart.js create mode 100644 Scripts/NewAddToCart_Web.js create mode 100644 Scripts/SyncXboxCloud.js create mode 100644 Scripts/XboxProductList.js create mode 100644 Scripts/authorization&cartId.js create mode 100644 Scripts/batch_purchase_send.js create mode 100644 Scripts/ms_family_block.js create mode 100644 Surge-main/Discard/AddToCart.js create mode 100644 Surge-main/Discard/CoreHaloLinkCollector.sgmodule create mode 100644 Surge-main/Discard/GetProductId.js create mode 100644 Surge-main/Discard/corehalo_capture.js create mode 100644 Surge-main/Discard/corehalo_dump.js create mode 100644 Surge-main/Modules/BuyRequestReplace.sgmodule create mode 100644 Surge-main/Modules/MS-Family-Block.sgmodule create mode 100644 Surge-main/Modules/Q-Search-Plus.sgmodule create mode 100644 Surge-main/Modules/XboxCartWeb.sgmodule create mode 100644 Surge-main/Modules/XboxParamCapturer.sgmodule create mode 100644 Surge-main/Modules/XboxWebController.sgmodule create mode 100644 Surge-main/Modules/Xbox_Rewrite.sgmodule create mode 100644 Surge-main/Modules/batch_approval.sgmodule create mode 100644 Surge-main/README.md create mode 100644 Surge-main/Scripts/Add8TFDlcToCart.js create mode 100644 Surge-main/Scripts/ApprovalCartId.js create mode 100644 Surge-main/Scripts/AutoClearApprovalCartId.js create mode 100644 Surge-main/Scripts/BuyRequestReplace.js create mode 100644 Surge-main/Scripts/CartParameter.js create mode 100644 Surge-main/Scripts/ClearApprovalCartId.js create mode 100644 Surge-main/Scripts/ClearXboxProductList_Web.js create mode 100644 Surge-main/Scripts/NewAddToCart.js create mode 100644 Surge-main/Scripts/NewAddToCart_Web.js create mode 100644 Surge-main/Scripts/SyncXboxCloud.js create mode 100644 Surge-main/Scripts/XboxProductList.js create mode 100644 Surge-main/Scripts/authorization&cartId.js create mode 100644 Surge-main/Scripts/batch_purchase_send.js create mode 100644 Surge-main/Scripts/ms_family_block.js diff --git a/Discard/AddToCart.js b/Discard/AddToCart.js new file mode 100644 index 0000000..0fef111 --- /dev/null +++ b/Discard/AddToCart.js @@ -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(); +} diff --git a/Discard/CoreHaloLinkCollector.sgmodule b/Discard/CoreHaloLinkCollector.sgmodule new file mode 100644 index 0000000..924ee23 --- /dev/null +++ b/Discard/CoreHaloLinkCollector.sgmodule @@ -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 diff --git a/Discard/GetProductId.js b/Discard/GetProductId.js new file mode 100644 index 0000000..d45a11a --- /dev/null +++ b/Discard/GetProductId.js @@ -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(); diff --git a/Discard/corehalo_capture.js b/Discard/corehalo_capture.js new file mode 100644 index 0000000..fab1a23 --- /dev/null +++ b/Discard/corehalo_capture.js @@ -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({}); diff --git a/Discard/corehalo_dump.js b/Discard/corehalo_dump.js new file mode 100644 index 0000000..24124cf --- /dev/null +++ b/Discard/corehalo_dump.js @@ -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) => ` +
+ ${index + 1}. + ${link} +
+`).join(""); + +// 页面样式 +const css = ` + +`; + +const bodyContent = count > 0 + ? `
${listHtml}
` + : `
📭 暂无捕获的链接
`; + +const html = ` + + + + + + CoreHalo Links + ${css} + + +
+

捕获列表 ${count}

+ ${bodyContent} + +
+ +`; + +// 4. 清空存储 (提取后即焚) +$persistentStore.write("[]", "corehalo_links"); + +// 5. 返回网页响应 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Modules/BuyRequestReplace.sgmodule b/Modules/BuyRequestReplace.sgmodule new file mode 100644 index 0000000..8cf5c49 --- /dev/null +++ b/Modules/BuyRequestReplace.sgmodule @@ -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 diff --git a/Modules/MS-Family-Block.sgmodule b/Modules/MS-Family-Block.sgmodule new file mode 100644 index 0000000..0effbfa --- /dev/null +++ b/Modules/MS-Family-Block.sgmodule @@ -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 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 diff --git a/Modules/XboxCartWeb.sgmodule b/Modules/XboxCartWeb.sgmodule new file mode 100644 index 0000000..0e18005 --- /dev/null +++ b/Modules/XboxCartWeb.sgmodule @@ -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 diff --git a/Modules/XboxParamCapturer.sgmodule b/Modules/XboxParamCapturer.sgmodule new file mode 100644 index 0000000..18b5377 --- /dev/null +++ b/Modules/XboxParamCapturer.sgmodule @@ -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 diff --git a/Modules/XboxWebController.sgmodule b/Modules/XboxWebController.sgmodule new file mode 100644 index 0000000..cb45a6a --- /dev/null +++ b/Modules/XboxWebController.sgmodule @@ -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 diff --git a/Modules/Xbox_Rewrite.sgmodule b/Modules/Xbox_Rewrite.sgmodule new file mode 100644 index 0000000..b05c451 --- /dev/null +++ b/Modules/Xbox_Rewrite.sgmodule @@ -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 diff --git a/Modules/batch_approval.sgmodule b/Modules/batch_approval.sgmodule new file mode 100644 index 0000000..3c091fe --- /dev/null +++ b/Modules/batch_approval.sgmodule @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1428af1 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Surge +自用 diff --git a/Scripts/Add8TFDlcToCart.js b/Scripts/Add8TFDlcToCart.js new file mode 100644 index 0000000..4902e6d --- /dev/null +++ b/Scripts/Add8TFDlcToCart.js @@ -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(); +} diff --git a/Scripts/ApprovalCartId.js b/Scripts/ApprovalCartId.js new file mode 100644 index 0000000..395b97e --- /dev/null +++ b/Scripts/ApprovalCartId.js @@ -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({}); diff --git a/Scripts/AutoClearApprovalCartId.js b/Scripts/AutoClearApprovalCartId.js new file mode 100644 index 0000000..3d9ebed --- /dev/null +++ b/Scripts/AutoClearApprovalCartId.js @@ -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({}); +})(); diff --git a/Scripts/BuyRequestReplace.js b/Scripts/BuyRequestReplace.js new file mode 100644 index 0000000..cca6112 --- /dev/null +++ b/Scripts/BuyRequestReplace.js @@ -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 }); + } +} diff --git a/Scripts/CartParameter.js b/Scripts/CartParameter.js new file mode 100644 index 0000000..f5ee7a1 --- /dev/null +++ b/Scripts/CartParameter.js @@ -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({}); diff --git a/Scripts/ClearApprovalCartId.js b/Scripts/ClearApprovalCartId.js new file mode 100644 index 0000000..49a8662 --- /dev/null +++ b/Scripts/ClearApprovalCartId.js @@ -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 = ` + + + + + + Clear List + + + +
+

✅ 操作成功

+

ApprovalCartId 已被清空

+
+ +`; + +// 5. 返回给浏览器 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Scripts/ClearXboxProductList_Web.js b/Scripts/ClearXboxProductList_Web.js new file mode 100644 index 0000000..546a77d --- /dev/null +++ b/Scripts/ClearXboxProductList_Web.js @@ -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 = ` + + + + + + Clear List + + + +
+

✅ 操作成功

+

XboxProductList 已被清空

+
+ +`; + +// 5. 返回给浏览器 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Scripts/NewAddToCart.js b/Scripts/NewAddToCart.js new file mode 100644 index 0000000..120a71a --- /dev/null +++ b/Scripts/NewAddToCart.js @@ -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(); +} diff --git a/Scripts/NewAddToCart_Web.js b/Scripts/NewAddToCart_Web.js new file mode 100644 index 0000000..98947e9 --- /dev/null +++ b/Scripts/NewAddToCart_Web.js @@ -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(`
${icon} ${message} ${detail}
`); +} + +const generateRiskSessionId = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => (c === "x" ? (Math.random() * 16 | 0) : ((Math.random() * 4 | 8) | 0)).toString(16)); + +const toNum = k => { const m = /^product(\d+)$/.exec(k); return m ? parseInt(m[1], 10) : Number.MAX_SAFE_INTEGER; }; +const normEntry = v => { + if (!v || typeof v !== "object") return null; + const productId = String(v.ProductId ?? v.productId ?? "").trim(); + const skuId = String(v.SkuId ?? v.skuId ?? "").trim(); + const availabilityId = String(v.AvailabilityId ?? v.availabilityId ?? "").trim(); + if (!productId || !skuId || !availabilityId) return null; + return { productId, skuId, availabilityId }; +}; + +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 = `Xbox Cart

执行结果: 成功 ${successCount} / 剩余 ${remainingCount}

${logBuffer.join("")}
`; + + $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(); +} diff --git a/Scripts/SyncXboxCloud.js b/Scripts/SyncXboxCloud.js new file mode 100644 index 0000000..6569a9e --- /dev/null +++ b/Scripts/SyncXboxCloud.js @@ -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, '''); +} + +function renderUI(title, message, type = "success") { + const colorMap = { + success: "#107C10", + warning: "#d83b01", + error: "#c50f1f", + info: "#0078d4" + }; + const color = colorMap[type] || "#0078d4"; + + const html = ` + + + + + + ${escapeHTML(title)} + + + +
+

${escapeHTML(title)}

+
${message}
+
SyncXbox Cloud Queue
+ +
+ +`; + + $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("❌ 连接失败", `

无法连接至服务器。

${escapeHTML(String(error))}

`, "error"); + } + + let payload; + try { + payload = JSON.parse((data || '').trim() || '{}'); + } catch (e) { + $notification.post("❌ 同步失败", "返回 JSON 无法解析", ""); + return renderUI("❌ 解析错误", `

服务器返回内容不是合法 JSON。

${escapeHTML(String(e.message || e))}

`, "error"); + } + + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + $notification.post("❌ 同步失败", "返回结构异常", ""); + return renderUI("❌ 数据异常", `

服务器返回的数据结构不正确。

`, "error"); + } + + if (!payload.ok) { + $notification.post("❌ 同步失败", "服务器返回 ok=false", ""); + return renderUI("❌ 同步失败", `

服务器返回失败状态。

`, "error"); + } + + const currentGroup = payload.currentGroup || {}; + const keys = Object.keys(currentGroup); + + if (!keys.length) { + $notification.post("📭 当前无待同步组", "云端队列为空", ""); + return renderUI("📭 没有可同步内容", `

当前没有待同步的 Product 分组。

`, "warning"); + } + + // 第二步:写入本地,成功后才删云端 + const writeOK = $persistentStore.write(JSON.stringify(currentGroup), storeKey); + if (!writeOK) { + $notification.post("❌ 同步失败", "写入 Surge 存储失败", ""); + // 写入失败,不发 clear,云端数据保留,下次可以重试 + return renderUI("❌ 写入失败", `

无法写入 Surge 本地存储,云端数据未删除,下次访问可重试。

`, "error"); + } + + // 第三步:本地写入成功,删除云端当前组 + $httpClient.get(clearUrl, (clearError, clearResponse, clearData) => { + if (clearError) { + $notification.post("⚠️ 本地已写入", `第 ${payload.currentGroupIndex} 组已保存`, "但云端清理失败,下次访问仍是这一组"); + return renderUI( + "⚠️ 当前组已写入,但云端未清理", + `

已成功写入第 ${escapeHTML(String(payload.currentGroupIndex))} 组,共 ${keys.length} 个商品。

+

但清理云端当前组时失败,下次访问时仍会是这一组。

`, + "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 => `
  • ${escapeHTML(k)}
  • `).join(''); + + let message = + `

    本次只同步了第 ${escapeHTML(String(payload.currentGroupIndex))} 组,共 ${keys.length} 个商品。

    +

    当前这一组已经从云端队列删除。

    +

    现在还剩 ${remainingGroups} 组待处理。

    `; + + if (remainingGroups > 0 && nextGroupIndex !== null) { + message += `

    下次访问网页时,将同步第 ${escapeHTML(String(nextGroupIndex))} 组(共 ${escapeHTML(String(nextGroupCount))} 个商品)。

    `; + } else { + message += `

    所有分组已经处理完毕。

    `; + } + + message += ``; + + $notification.post("✅ 当前组同步成功", `第 ${payload.currentGroupIndex} 组已同步`, `剩余 ${remainingGroups} 组`); + return renderUI("✅ 当前组同步完成", message, "success"); + }); +}); diff --git a/Scripts/XboxProductList.js b/Scripts/XboxProductList.js new file mode 100644 index 0000000..0ac52df --- /dev/null +++ b/Scripts/XboxProductList.js @@ -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 }); + } +})(); diff --git a/Scripts/authorization&cartId.js b/Scripts/authorization&cartId.js new file mode 100644 index 0000000..f227554 --- /dev/null +++ b/Scripts/authorization&cartId.js @@ -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({}); diff --git a/Scripts/batch_purchase_send.js b/Scripts/batch_purchase_send.js new file mode 100644 index 0000000..fbe198e --- /dev/null +++ b/Scripts/batch_purchase_send.js @@ -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 }); + } + }); + }); +} diff --git a/Scripts/ms_family_block.js b/Scripts/ms_family_block.js new file mode 100644 index 0000000..bed90f6 --- /dev/null +++ b/Scripts/ms_family_block.js @@ -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(); diff --git a/Surge-main/Discard/AddToCart.js b/Surge-main/Discard/AddToCart.js new file mode 100644 index 0000000..0fef111 --- /dev/null +++ b/Surge-main/Discard/AddToCart.js @@ -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(); +} diff --git a/Surge-main/Discard/CoreHaloLinkCollector.sgmodule b/Surge-main/Discard/CoreHaloLinkCollector.sgmodule new file mode 100644 index 0000000..924ee23 --- /dev/null +++ b/Surge-main/Discard/CoreHaloLinkCollector.sgmodule @@ -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 diff --git a/Surge-main/Discard/GetProductId.js b/Surge-main/Discard/GetProductId.js new file mode 100644 index 0000000..d45a11a --- /dev/null +++ b/Surge-main/Discard/GetProductId.js @@ -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(); diff --git a/Surge-main/Discard/corehalo_capture.js b/Surge-main/Discard/corehalo_capture.js new file mode 100644 index 0000000..fab1a23 --- /dev/null +++ b/Surge-main/Discard/corehalo_capture.js @@ -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({}); diff --git a/Surge-main/Discard/corehalo_dump.js b/Surge-main/Discard/corehalo_dump.js new file mode 100644 index 0000000..24124cf --- /dev/null +++ b/Surge-main/Discard/corehalo_dump.js @@ -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) => ` +
    + ${index + 1}. + ${link} +
    +`).join(""); + +// 页面样式 +const css = ` + +`; + +const bodyContent = count > 0 + ? `
    ${listHtml}
    ` + : `
    📭 暂无捕获的链接
    `; + +const html = ` + + + + + + CoreHalo Links + ${css} + + +
    +

    捕获列表 ${count}

    + ${bodyContent} + +
    + +`; + +// 4. 清空存储 (提取后即焚) +$persistentStore.write("[]", "corehalo_links"); + +// 5. 返回网页响应 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Surge-main/Modules/BuyRequestReplace.sgmodule b/Surge-main/Modules/BuyRequestReplace.sgmodule new file mode 100644 index 0000000..8cf5c49 --- /dev/null +++ b/Surge-main/Modules/BuyRequestReplace.sgmodule @@ -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 diff --git a/Surge-main/Modules/MS-Family-Block.sgmodule b/Surge-main/Modules/MS-Family-Block.sgmodule new file mode 100644 index 0000000..0effbfa --- /dev/null +++ b/Surge-main/Modules/MS-Family-Block.sgmodule @@ -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 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 diff --git a/Surge-main/Modules/XboxCartWeb.sgmodule b/Surge-main/Modules/XboxCartWeb.sgmodule new file mode 100644 index 0000000..0e18005 --- /dev/null +++ b/Surge-main/Modules/XboxCartWeb.sgmodule @@ -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 diff --git a/Surge-main/Modules/XboxParamCapturer.sgmodule b/Surge-main/Modules/XboxParamCapturer.sgmodule new file mode 100644 index 0000000..18b5377 --- /dev/null +++ b/Surge-main/Modules/XboxParamCapturer.sgmodule @@ -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 diff --git a/Surge-main/Modules/XboxWebController.sgmodule b/Surge-main/Modules/XboxWebController.sgmodule new file mode 100644 index 0000000..cb45a6a --- /dev/null +++ b/Surge-main/Modules/XboxWebController.sgmodule @@ -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 diff --git a/Surge-main/Modules/Xbox_Rewrite.sgmodule b/Surge-main/Modules/Xbox_Rewrite.sgmodule new file mode 100644 index 0000000..b05c451 --- /dev/null +++ b/Surge-main/Modules/Xbox_Rewrite.sgmodule @@ -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 diff --git a/Surge-main/Modules/batch_approval.sgmodule b/Surge-main/Modules/batch_approval.sgmodule new file mode 100644 index 0000000..3c091fe --- /dev/null +++ b/Surge-main/Modules/batch_approval.sgmodule @@ -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 diff --git a/Surge-main/README.md b/Surge-main/README.md new file mode 100644 index 0000000..1428af1 --- /dev/null +++ b/Surge-main/README.md @@ -0,0 +1,2 @@ +# Surge +自用 diff --git a/Surge-main/Scripts/Add8TFDlcToCart.js b/Surge-main/Scripts/Add8TFDlcToCart.js new file mode 100644 index 0000000..4902e6d --- /dev/null +++ b/Surge-main/Scripts/Add8TFDlcToCart.js @@ -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(); +} diff --git a/Surge-main/Scripts/ApprovalCartId.js b/Surge-main/Scripts/ApprovalCartId.js new file mode 100644 index 0000000..395b97e --- /dev/null +++ b/Surge-main/Scripts/ApprovalCartId.js @@ -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({}); diff --git a/Surge-main/Scripts/AutoClearApprovalCartId.js b/Surge-main/Scripts/AutoClearApprovalCartId.js new file mode 100644 index 0000000..3d9ebed --- /dev/null +++ b/Surge-main/Scripts/AutoClearApprovalCartId.js @@ -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({}); +})(); diff --git a/Surge-main/Scripts/BuyRequestReplace.js b/Surge-main/Scripts/BuyRequestReplace.js new file mode 100644 index 0000000..cca6112 --- /dev/null +++ b/Surge-main/Scripts/BuyRequestReplace.js @@ -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 }); + } +} diff --git a/Surge-main/Scripts/CartParameter.js b/Surge-main/Scripts/CartParameter.js new file mode 100644 index 0000000..f5ee7a1 --- /dev/null +++ b/Surge-main/Scripts/CartParameter.js @@ -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({}); diff --git a/Surge-main/Scripts/ClearApprovalCartId.js b/Surge-main/Scripts/ClearApprovalCartId.js new file mode 100644 index 0000000..49a8662 --- /dev/null +++ b/Surge-main/Scripts/ClearApprovalCartId.js @@ -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 = ` + + + + + + Clear List + + + +
    +

    ✅ 操作成功

    +

    ApprovalCartId 已被清空

    +
    + +`; + +// 5. 返回给浏览器 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Surge-main/Scripts/ClearXboxProductList_Web.js b/Surge-main/Scripts/ClearXboxProductList_Web.js new file mode 100644 index 0000000..546a77d --- /dev/null +++ b/Surge-main/Scripts/ClearXboxProductList_Web.js @@ -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 = ` + + + + + + Clear List + + + +
    +

    ✅ 操作成功

    +

    XboxProductList 已被清空

    +
    + +`; + +// 5. 返回给浏览器 +$done({ + response: { + status: 200, + headers: { "Content-Type": "text/html;charset=utf-8" }, + body: html + } +}); diff --git a/Surge-main/Scripts/NewAddToCart.js b/Surge-main/Scripts/NewAddToCart.js new file mode 100644 index 0000000..120a71a --- /dev/null +++ b/Surge-main/Scripts/NewAddToCart.js @@ -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(); +} diff --git a/Surge-main/Scripts/NewAddToCart_Web.js b/Surge-main/Scripts/NewAddToCart_Web.js new file mode 100644 index 0000000..98947e9 --- /dev/null +++ b/Surge-main/Scripts/NewAddToCart_Web.js @@ -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(`
    ${icon} ${message} ${detail}
    `); +} + +const generateRiskSessionId = () => "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => (c === "x" ? (Math.random() * 16 | 0) : ((Math.random() * 4 | 8) | 0)).toString(16)); + +const toNum = k => { const m = /^product(\d+)$/.exec(k); return m ? parseInt(m[1], 10) : Number.MAX_SAFE_INTEGER; }; +const normEntry = v => { + if (!v || typeof v !== "object") return null; + const productId = String(v.ProductId ?? v.productId ?? "").trim(); + const skuId = String(v.SkuId ?? v.skuId ?? "").trim(); + const availabilityId = String(v.AvailabilityId ?? v.availabilityId ?? "").trim(); + if (!productId || !skuId || !availabilityId) return null; + return { productId, skuId, availabilityId }; +}; + +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 = `Xbox Cart

    执行结果: 成功 ${successCount} / 剩余 ${remainingCount}

    ${logBuffer.join("")}
    `; + + $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(); +} diff --git a/Surge-main/Scripts/SyncXboxCloud.js b/Surge-main/Scripts/SyncXboxCloud.js new file mode 100644 index 0000000..6569a9e --- /dev/null +++ b/Surge-main/Scripts/SyncXboxCloud.js @@ -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, '''); +} + +function renderUI(title, message, type = "success") { + const colorMap = { + success: "#107C10", + warning: "#d83b01", + error: "#c50f1f", + info: "#0078d4" + }; + const color = colorMap[type] || "#0078d4"; + + const html = ` + + + + + + ${escapeHTML(title)} + + + +
    +

    ${escapeHTML(title)}

    +
    ${message}
    +
    SyncXbox Cloud Queue
    + +
    + +`; + + $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("❌ 连接失败", `

    无法连接至服务器。

    ${escapeHTML(String(error))}

    `, "error"); + } + + let payload; + try { + payload = JSON.parse((data || '').trim() || '{}'); + } catch (e) { + $notification.post("❌ 同步失败", "返回 JSON 无法解析", ""); + return renderUI("❌ 解析错误", `

    服务器返回内容不是合法 JSON。

    ${escapeHTML(String(e.message || e))}

    `, "error"); + } + + if (!payload || typeof payload !== 'object' || Array.isArray(payload)) { + $notification.post("❌ 同步失败", "返回结构异常", ""); + return renderUI("❌ 数据异常", `

    服务器返回的数据结构不正确。

    `, "error"); + } + + if (!payload.ok) { + $notification.post("❌ 同步失败", "服务器返回 ok=false", ""); + return renderUI("❌ 同步失败", `

    服务器返回失败状态。

    `, "error"); + } + + const currentGroup = payload.currentGroup || {}; + const keys = Object.keys(currentGroup); + + if (!keys.length) { + $notification.post("📭 当前无待同步组", "云端队列为空", ""); + return renderUI("📭 没有可同步内容", `

    当前没有待同步的 Product 分组。

    `, "warning"); + } + + // 第二步:写入本地,成功后才删云端 + const writeOK = $persistentStore.write(JSON.stringify(currentGroup), storeKey); + if (!writeOK) { + $notification.post("❌ 同步失败", "写入 Surge 存储失败", ""); + // 写入失败,不发 clear,云端数据保留,下次可以重试 + return renderUI("❌ 写入失败", `

    无法写入 Surge 本地存储,云端数据未删除,下次访问可重试。

    `, "error"); + } + + // 第三步:本地写入成功,删除云端当前组 + $httpClient.get(clearUrl, (clearError, clearResponse, clearData) => { + if (clearError) { + $notification.post("⚠️ 本地已写入", `第 ${payload.currentGroupIndex} 组已保存`, "但云端清理失败,下次访问仍是这一组"); + return renderUI( + "⚠️ 当前组已写入,但云端未清理", + `

    已成功写入第 ${escapeHTML(String(payload.currentGroupIndex))} 组,共 ${keys.length} 个商品。

    +

    但清理云端当前组时失败,下次访问时仍会是这一组。

    `, + "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 => `
  • ${escapeHTML(k)}
  • `).join(''); + + let message = + `

    本次只同步了第 ${escapeHTML(String(payload.currentGroupIndex))} 组,共 ${keys.length} 个商品。

    +

    当前这一组已经从云端队列删除。

    +

    现在还剩 ${remainingGroups} 组待处理。

    `; + + if (remainingGroups > 0 && nextGroupIndex !== null) { + message += `

    下次访问网页时,将同步第 ${escapeHTML(String(nextGroupIndex))} 组(共 ${escapeHTML(String(nextGroupCount))} 个商品)。

    `; + } else { + message += `

    所有分组已经处理完毕。

    `; + } + + message += `
      ${list}
    `; + + $notification.post("✅ 当前组同步成功", `第 ${payload.currentGroupIndex} 组已同步`, `剩余 ${remainingGroups} 组`); + return renderUI("✅ 当前组同步完成", message, "success"); + }); +}); diff --git a/Surge-main/Scripts/XboxProductList.js b/Surge-main/Scripts/XboxProductList.js new file mode 100644 index 0000000..0ac52df --- /dev/null +++ b/Surge-main/Scripts/XboxProductList.js @@ -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 }); + } +})(); diff --git a/Surge-main/Scripts/authorization&cartId.js b/Surge-main/Scripts/authorization&cartId.js new file mode 100644 index 0000000..f227554 --- /dev/null +++ b/Surge-main/Scripts/authorization&cartId.js @@ -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({}); diff --git a/Surge-main/Scripts/batch_purchase_send.js b/Surge-main/Scripts/batch_purchase_send.js new file mode 100644 index 0000000..fbe198e --- /dev/null +++ b/Surge-main/Scripts/batch_purchase_send.js @@ -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 }); + } + }); + }); +} diff --git a/Surge-main/Scripts/ms_family_block.js b/Surge-main/Scripts/ms_family_block.js new file mode 100644 index 0000000..bed90f6 --- /dev/null +++ b/Surge-main/Scripts/ms_family_block.js @@ -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();