From 3ea76b3a3c3d4213d7f9f5e9cb018d104cb59e3b Mon Sep 17 00:00:00 2001 From: XXhaos Date: Fri, 3 Apr 2026 10:13:41 +0800 Subject: [PATCH] Update NewAddToCart_Web.js --- Scripts/NewAddToCart_Web.js | 105 ++++++++++++++++++++++-------------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/Scripts/NewAddToCart_Web.js b/Scripts/NewAddToCart_Web.js index dbb8b62..c174bc8 100644 --- a/Scripts/NewAddToCart_Web.js +++ b/Scripts/NewAddToCart_Web.js @@ -2,9 +2,11 @@ * Xbox Cart Web Runner * 远程路径: https://raw.githubusercontent.com/dragonisheep/Surge/refs/heads/master/Scripts/NewAddToCart_Web.js * - * 优先级: - * 1. 远程待同步 Product(fetch_and_clear,单次只取一组) - * 2. 本地 XboxProductList(兜底) + * 流程: + * 1. GET 读取远程当前组(服务端加锁) + * 2. 执行加购 + * 3. 加购完成后 GET clear(服务端释放锁 + 弹出该组) + * 4. 若远程无数据,回退到本地 XboxProductList */ const MARKET = "NG"; @@ -12,9 +14,9 @@ const LOCALE = "en-ng"; const FRIENDLY_NAME = `cart-${MARKET}`; const CLIENT_CONTEXT = { client: "UniversalWebStore.Cart", deviceType: "Pc" }; -const REMOTE_URL = 'https://cc.dragonisheep.com/surge?token=xbox123&action=fetch_and_clear'; -const LOCAL_KEY = 'XboxProductList'; -const LOCK_KEY = 'SyncXboxLock'; +const REMOTE_READ_URL = 'https://cc.dragonisheep.com/surge?token=xbox123'; +const REMOTE_CLEAR_URL = 'https://cc.dragonisheep.com/surge?token=xbox123&action=clear'; +const LOCAL_KEY = 'XboxProductList'; const MUID = $persistentStore.read("cart-x-authorization-muid"); const MS_CV = $persistentStore.read("cart-ms-cv"); @@ -24,7 +26,8 @@ const results = { success: [], failure: [] }; const successKeys = []; let currentIndex = 0; let productList = []; -let sourceLabel = ""; // 记录数据来源,用于最终通知 +let sourceLabel = ""; +let useRemote = false; // 是否使用了远程数据(决定加购后是否发 clear) function log(type, message, detail = "") { const icon = type === "success" ? "✅" : (type === "error" ? "❌" : "ℹ️"); @@ -69,27 +72,45 @@ const HEADERS = { function finalizeAndClean() { const successCount = results.success.length; const failureCount = results.failure.length; + + // 清理本地 XboxProductList 中已成功的 key(仅本地模式) let remainingCount = 0; + if (!useRemote) { + try { + let storeObj; try { storeObj = JSON.parse($persistentStore.read(LOCAL_KEY) || "{}"); } catch { storeObj = {}; } + for (const k of successKeys) { + if (k && Object.prototype.hasOwnProperty.call(storeObj, k)) delete storeObj[k]; + } + remainingCount = Object.keys(storeObj).filter(k => /^product\d+$/.test(k)).length; + $persistentStore.write(JSON.stringify(storeObj), LOCAL_KEY); + log("info", "本地清理完成", `剩余: ${remainingCount}`); + } catch (e) { log("error", "清理异常", String(e)); } + } - try { - // 只清理本地 XboxProductList 中已成功的 key - let storeObj; try { storeObj = JSON.parse($persistentStore.read(LOCAL_KEY) || "{}"); } catch { storeObj = {}; } - for (const k of successKeys) { - if (k && Object.prototype.hasOwnProperty.call(storeObj, k)) delete storeObj[k]; + const doFinish = () => { + $notification.post( + "🛒 Xbox 加购完成", + `成功: ${successCount} / 失败: ${failureCount}`, + `来源: ${sourceLabel}${!useRemote ? ` | 剩余本地: ${remainingCount}` : ''}` + ); + const html = `Xbox Cart

执行结果: 成功 ${successCount} / 失败 ${failureCount} | 来源: ${sourceLabel}

${logBuffer.join("")}
`; + $done({ response: { status: 200, headers: { "Content-Type": "text/html;charset=utf-8" }, body: html } }); + }; + + if (useRemote) { + if (failureCount === 0) { + // 全部成功:发 clear,释放服务端锁并弹出该组 + log("info", "加购全部成功,通知服务端 clear"); + $httpClient.get(REMOTE_CLEAR_URL, () => doFinish()); + } else { + // 有失败:不 clear,保留服务端数据,下次可重试 + log("info", `有 ${failureCount} 个失败,保留服务端数据以便重试`); + $notification.post("⚠️ 部分失败", `${failureCount} 个加购失败`, "服务端数据已保留,可重新执行"); + doFinish(); } - remainingCount = Object.keys(storeObj).filter(k => /^product\d+$/.test(k)).length; - $persistentStore.write(JSON.stringify(storeObj), LOCAL_KEY); - log("info", "清理完成", `剩余: ${remainingCount}`); - } catch (e) { log("error", "清理异常", e); } - - $notification.post( - "🛒 Xbox 加购完成", - `成功: ${successCount} / 失败: ${failureCount}`, - `来源: ${sourceLabel} | 剩余本地: ${remainingCount}` - ); - - const html = `Xbox Cart

执行结果: 成功 ${successCount} / 失败 ${failureCount} | 来源: ${sourceLabel}

${logBuffer.join("")}
`; - $done({ response: { status: 200, headers: { "Content-Type": "text/html;charset=utf-8" }, body: html } }); + } else { + doFinish(); + } } function startTask() { @@ -102,7 +123,12 @@ function startTask() { if (productList.length === 0) { log("info", "列表为空"); $notification.post("⚠️ Xbox 脚本", "无需执行", `来源: ${sourceLabel} | 列表为空`); - finalizeAndClean(); + // 远程模式下列表为空也要释放锁 + if (useRemote) { + $httpClient.get(REMOTE_CLEAR_URL, () => $done({})); + } else { + finalizeAndClean(); + } return; } log("info", `开始任务 [${sourceLabel}]`, `数量: ${productList.length}`); @@ -137,38 +163,35 @@ function sendRequest() { } // ========================= 主流程 ========================= -// 防重入锁(和 SyncXboxCloud.js 共用同一个 lockKey) -const lockVal = $persistentStore.read(LOCK_KEY); -if (lockVal && Date.now() - parseInt(lockVal, 10) < 5000) { - // 5 秒内重复触发,直接放行 - $done({}); -} -$persistentStore.write(String(Date.now()), LOCK_KEY); - -// 第一步:尝试从远程取一组待同步 Product -$httpClient.get(REMOTE_URL, (error, response, data) => { +// ★ 服务端有锁机制,客户端不再需要时间锁 +// 第一步:尝试从远程读取当前组(服务端此时会加锁) +$httpClient.get(REMOTE_READ_URL, (error, response, data) => { let remoteGroup = null; + let groupIndex = null; if (!error && data) { try { const payload = JSON.parse((data || '').trim() || '{}'); - if (payload.ok && payload.cleared && payload.currentGroup) { + // 服务端有锁且有数据时返回 currentGroup + if (payload.ok && payload.currentGroup) { const keys = Object.keys(payload.currentGroup); if (keys.length > 0) { remoteGroup = payload.currentGroup; - log("info", "使用远程待同步 Product", `第 ${payload.currentGroupIndex} 组,共 ${keys.length} 个`); + groupIndex = payload.currentGroupIndex; + log("info", "使用远程待同步 Product", `第 ${groupIndex} 组,共 ${keys.length} 个`); } } } catch (_) {} } if (remoteGroup) { - // 使用远程数据,不写入本地 XboxProductList(已由服务端 fetch_and_clear 删除) - sourceLabel = "远程同步"; + useRemote = true; + sourceLabel = `远程第 ${groupIndex} 组`; productList = parseProductList(JSON.stringify(remoteGroup)); startTask(); } else { - // 远程无数据或连接失败,回退到本地 + // 远程无数据(队列为空或被锁)或连接失败,回退到本地 + useRemote = false; const localRaw = $persistentStore.read(LOCAL_KEY) || "{}"; sourceLabel = "本地"; productList = parseProductList(localRaw);