// ==UserScript== // @name 微软积分商城签到(改进版) // @namespace https://geoisam.github.io // @version 3.6.67.0 // @description 每天在后台自动完成 Microsoft Rewards 任务获取积分奖励,✅签入、✅阅读、✅活动、✅打卡、✅Edge浏览、✅搜索、✅领取、✅hpquiz答题、✅rewardsApp解锁领取 // @author geoisam@qq.com // @homepage https://github.com/geoisam/FuckScripts // @supportURL https://github.com/geoisam/FuckScripts/issues // @crontab 27 9-18 * * * // @connect bing.com // @connect login.live.com // @connect rewards.bing.com // @connect prod.rewardsplatform.microsoft.com // @connect hotapi.nntool.cc // @connect hot.baiwumm.com // @connect cnxiaobai.com // @connect 192.168.2.8 // @connect disp-qryapi.3g.qq.com // @connect qyapi.weixin.qq.com // @connect oapi.dingtalk.com // @connect open.feishu.cn // @connect push.i-i.me // @connect api.day.app // @match https://login.live.com/* // @include https://login.live.com/* // @match https://rewards.bing.com/dashboard // @run-at document-end // @noframes // @all-frames // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_cookie // @grant GM_addValueChangeListener // @grant GM_removeValueChangeListener // @grant GM_info // @grant GM_log // @storageName FuckScripts_Shared_Auth // @tips 此脚本一直为 开源免费 使用,如果你是从某些地方买的话,你就是被骗了 // @license GPL-3.0 // ==/UserScript== /* global GM_cookie, GM_getValue, GM_setValue, GM_xmlhttpRequest, GM_log, GM_info, GM_notification, GM_openInTab, GM_addValueChangeListener, GM_removeValueChangeListener */ /* ==UserConfig== Config: keep: title: 持续检测(关闭则所有任务完成后不再检测) type: checkbox default: true lock: title: 锁定国区(若当前 IP 非中国大陆地区则停止) type: checkbox default: true span: title: 搜索间隔(至少 30 秒即间隔 15-45 秒) type: number default: 30 min: 30 unit: ±15秒 api: title: 搜索词接口(offline为随机搜索词) type: select default: hot.nntool.cc values: [offline, hot.nntool.cc, hot.baiwumm.com, hot.cnxiaobai.com, self.words] code: title: 授权码链接(请勿分享以免个人数据泄露) type: textarea description: https://login.live.com/oauth20_desktop.srf?code=M.C540_BAY.2.U.********-****-****-****-************&... Tasks: sign: title: 签入(Authorization Code) type: checkbox default: true read: title: 阅读(Authorization Code) type: checkbox default: true promos: title: 活动(rewards.bing.com) type: checkbox default: true edge: title: Edge 浏览连签(后台计时约 30 分钟,需授权码) type: checkbox default: true bonus: title: 每日额外积分(dashboard 按钮) type: checkbox default: true search: title: 搜索(www.bing.com) type: checkbox default: true punchcard: title: 月度打卡任务(earn 页面任务板块,隔日解锁) type: checkbox default: true quiz: title: Quiz 自动答题 type: checkbox default: true Notice: account: title: 账号标识(用于消息区分) type: text default: 账号A bro: title: 浏览器通知(当前脚本) type: checkbox default: true allpush: title: 默认全部搜索完成推送(不选每轮搜索完成推送) type: checkbox default: true wework: title: 企业微信消息推送(群机器人) type: text password: true description: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx dingding: title: 钉钉群机器人(不加签,关键词:#) type: text password: true description: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx feishu: title: 飞书群机器人(不加签,关键词:#) type: text password: true description: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx pushme: title: PushMe(push.i-i.me) type: text password: true description: xxxxxxxxxxxxxxxxxxxx bark: title: Bark(bark.day.app) type: text password: true description: xxxxxxxxxxxxxxxxxxxx ==/UserConfig== */ if (typeof window !== 'undefined' && window.location && window.location.hostname === "login.live.com") { (function() { const setAuthCookie = function(code) { if (typeof GM_cookie !== 'undefined') { GM_cookie.set({ name: "microsoft_rewards_auth_code", value: encodeURIComponent(code), domain: ".live.com", path: "/", secure: true, expirationDate: Date.now() / 1000 + 60 }); } else { const expiration = new Date(); expiration.setTime(expiration.getTime() + 60 * 1000); document.cookie = "microsoft_rewards_auth_code=" + encodeURIComponent(code) + "; expires=" + expiration.toUTCString() + "; path=/; domain=.live.com"; } }; const checkAndCapture = function() { try { const urlParams = new URLSearchParams(window.location.search); const authCode = urlParams.get("code"); if (authCode && authCode.length > 10) { const fullCode = "https://login.live.com/oauth20_desktop.srf?code=" + authCode.trim(); GM_setValue("Config.code", fullCode); GM_setValue("Config.token", false); setAuthCookie(fullCode); if (GM_getValue("Notice.bro", true)) { GM_notification({ text: "授权码获取成功!", title: GM_info.script.name, onclick: function() {} }); } return true; } } catch (e) {} return false; }; if (checkAndCapture()) { return; } window.addEventListener("hashchange", checkAndCapture); window.addEventListener("popstate", checkAndCapture); const checkInterval = setInterval(function() { if (checkAndCapture()) { clearInterval(checkInterval); } }, 300); setTimeout(function() { clearInterval(checkInterval); }, 60000); })(); return; } const FuckD = { searchPushMode: GM_getValue("Notice.search_push", "all"), wh: [ { name: "企业微信", url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=", key: GM_getValue("Notice.wework", false), msg: { "msgtype": "markdown_v2", "markdown_v2": { get content() { const account = GM_getValue("Notice.account", "账号A"); return `> ${FuckD.bing.datetimeLocaleStr}\n ## ${GM_info.script.name}\n 【${account}】: ${FuckD.bing.sendMSG}` } }, }, docs: "https://developer.work.weixin.qq.com/document/path/91770" }, { name: "钉钉", url: "https://oapi.dingtalk.com/robot/send?access_token=", key: GM_getValue("Notice.dingding", false), msg: { "msgtype": "markdown", "markdown": { "title": GM_info.script.name, get text() { const account = GM_getValue("Notice.account", "账号A"); let msgContent = FuckD.bing.sendMSG; msgContent = msgContent.replace(/\n/g, '\n\n'); return `> ${FuckD.bing.datetimeLocaleStr}\n\n ### ${GM_info.script.name}\n 【${account}】: ${msgContent}` } }, }, docs: "https://open.dingtalk.com/document/orgapp/custom-robots-send-group-messages" }, { name: "飞书", url: "https://open.feishu.cn/open-apis/bot/v2/hook/", key: GM_getValue("Notice.feishu", false), msg: { "msg_type": "interactive", "card": { "schema": "2.0", "header": { "title": { "tag": "plain_text", "content": GM_info.script.name }, "template": "orange" }, "body": { "elements": [{ "tag": "markdown", "text_align": "center", get content() { const account = GM_getValue("Notice.account", "账号A"); return `#### ${FuckD.bing.datetimeLocaleStr}\n 【${account}】: ${FuckD.bing.sendMSG}` } }] } } }, docs: "https://open.feishu.cn/document/client-docs/bot-v3/add-custom-bot" }, { name: "PushMe", url: "https://push.i-i.me/?push_key=", key: GM_getValue("Notice.pushme", false), msg: { "type": "markdown", "title": `${GM_info.script.name}[#reawrds!https://rewards.bing.com/rewards.png]`, get content() { return `\n ${FuckD.bing.sendMSG}` } }, docs: "https://push.i-i.me/docs/index" }, { name: "Bark", url: "https://api.day.app/", key: GM_getValue("Notice.bark", false), msg: { "group": "rewards", "icon": "https://rewards.bing.com/rewards.png", "title": GM_info.script.name, get markdown() { return `\n ${FuckD.bing.sendMSG}` } }, docs: "https://bark.day.app/#/tutorial" }, ], ua: { // 拟人化 P0:PC 端优先用浏览器真实 UA(与 TLS 指纹一致,避免 UA 版本声明与实际指纹不符的伪装特征;沙箱无 navigator 时回退到近期版本,需随版本更替更新) pc: (typeof navigator !== "undefined" && /Chrome\//.test(navigator.userAgent || "")) ? navigator.userAgent : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36 Edg/150.0.2420.81", // APP 通道移动 UA(dapi/移动搜索),硬编码需定期跟随版本更新 m: "Mozilla/5.0 (Linux; Android 16; MCE16 Build/BP3A.250905.014; ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/150.0.0.0 Mobile Safari/537.36 EdgA/150.0.2420.102", }, cookie: { bing: "", }, api: { mode: GM_getValue("Config.api", "offline"), arr: [ ["hot.baiwumm.com", { url: "https://hot.baiwumm.com/api/", hot: ["weibo", "douyin", "baidu", "toutiao", "thepaper", "qq", "netease", "zhihu"], }], ["hot.cnxiaobai.com", { url: "https://cnxiaobai.com/DailyHotApi/", hot: ["weibo", "douyin", "baidu", "toutiao", "thepaper", "qq-news", "netease-news", "zhihu"], }], ["hot.nntool.cc", { url: "https://hotapi.nntool.cc/", hot: ["weibo", "douyin", "baidu", "toutiao", "thepaper", "qq-news", "netease-news", "zhihu"], }], ["self.words", { url: "http://192.168.2.8:18000/get_batch/", hot: ["python","python","ai_model","ai_model","agent","shell","oracle","oracle","kingbase","migrate_o","proc","office","life","cook","shop","movie","drama","fruit"], }], ], }, tasks: { sign: GM_getValue("Tasks.sign", true), read: GM_getValue("Tasks.read", true), promos: GM_getValue("Tasks.promos", true), edge: GM_getValue("Tasks.edge", true), bonus: GM_getValue("Tasks.bonus", true), search: GM_getValue("Tasks.search", true), punchcard: GM_getValue("Tasks.punchcard", true), quiz: GM_getValue("Tasks.quiz", true), rewardsApp: GM_getValue("Tasks.rewardsApp", true), }, bing: { region: "CN", status: GM_getValue("Config.lock", true), repo: "https://github.com/geoisam/FuckScripts/", time: 3210, span: GM_getValue("Config.span", 30), code: 0, token: false, error: 0, tasks: { get sign() { return FuckD.sign.date }, get read() { return FuckD.read.date }, get promos() { return FuckD.promos.date }, get streak() { return FuckD.streak.date }, get search() { return FuckD.search.date }, get punchcard() { return FuckD.punchcard.date }, }, mainland: 1, end: 1, }, sign: { times: 0, point: -1, end: 0, }, read: { times: 0, point: 0, end: 0, }, promos: { times: 0, token: 0, end: 0, }, streak: { times: 0, point: 0, end: 0, }, edge: { times: 0, end: 0, running: false, }, search: { device: "", word: { list: [], index: 0, }, times: 0, progressNow: 0, pc: { progress: 0, max: 1, }, m: { progress: 0, max: 1, }, index: 0, end: 0, }, punchcard: { times: 0, end: 0, }, } const FuckF = { getRandomNum(num) { return Math.floor(Math.random() * num) }, getScopeRandomNum(min, max) { return Math.floor(Math.random() * (max + 1 - min) + min) }, getRandomArr(arr) { return arr.sort(() => { return Math.random() - 0.5 }) }, getRandomUUID(only = false) { const uuid = crypto.randomUUID() const sid = uuid.replace(/-/g, "").toUpperCase() return only ? sid : uuid }, getTimestamp(start = 0, end = 13) { const timestamp = Date.now() const num = timestamp.toString().substring(start, end) return Number(num) }, getDatetime(num = false, slash = true) { const today = new Date() const year = today.getFullYear() let month = today.getMonth() + 1 let day = today.getDate() month = month < 10 ? "0" + month : month day = day < 10 ? "0" + day : day const dateNow = num ? Number(`${year}${month}${day}`) : slash ? `${month}/${day}/${year}` : `${year}-${month}-${day}` return dateNow }, isJSON(s) { try { const j = JSON.parse(s) const p = Array.isArray(j) || (typeof j === "object" && j !== null) return p ? true : false } catch { return false } }, log(title, text, push = false) { GM_log(title + text + "🔚") if (!GM_getValue("Notice.bro", true) || !push) return GM_notification({ text: text, title: GM_info.script.name + title, onclick: () => { if (typeof push === 'string' && push.startsWith('http')) { GM_openInTab(push, { active: true, insert: true, setParent: true }); } else if (title.includes("🔵") || title.includes("🎉")) { GM_openInTab("https://rewards.bing.com/earn", { active: true, insert: true, setParent: true }); } else { GM_openInTab("https://rewards.bing.com/dashboard", { active: true, insert: true, setParent: true }); } }, }) }, xhr(options, only = false) { return new Promise((resolve, reject) => { const start = FuckF.getTimestamp(); try { GM_xmlhttpRequest({ ...options, timeout: 15000, ontimeout: () => { const cost = (FuckF.getTimestamp() - start) / 1000; reject(new Error(`请求超时!用时 ${cost} 秒`)); }, onload: (xhr) => { const cost = (FuckF.getTimestamp() - start) / 1000; if (xhr.status >= 200 && xhr.status < 300) { resolve(only ? xhr.finalUrl : xhr.responseText); return; } const redirectStatuses = [301, 302, 307, 308]; if (redirectStatuses.includes(xhr.status)) { const match = xhr.responseHeaders?.match(/Location:\s*(.*?)\s*\r?\n/i); resolve(match ? match[1] : false); return; } // 附带响应体前 120 字符摘要:500 多为 React flight 错误帧(如 1:E{"digest":"..."}), // 没有响应体内容则服务端报错原因完全是排查盲区 const bodySnippet = (xhr.responseText || "").replace(/\s+/g, " ").slice(0, 120); reject(new Error(`请求失败,用时 ${cost} 秒,状态码:${xhr.status}${bodySnippet ? ` 🔛${bodySnippet}` : ""}`)); }, onerror: (err) => { const cost = (FuckF.getTimestamp() - start) / 1000; let errDetail = ""; if (err) { errDetail = err.error || err.statusText || (typeof err === "object" ? JSON.stringify(err) : err); } else { errDetail = "未知网络错误"; } reject(new Error(`请求异常!用时 ${cost} 秒 🔛${errDetail}`)); }, }); } catch (e) { reject(e); } }); }, } FuckF.getRandomSubstring = (str, min = 20, max = 32) => { const length = str.length if (length <= min) { return str } const randomLength = FuckF.getScopeRandomNum(min, max) return str.substring(0, randomLength) } FuckF.getRandomApiHot = () => { const lastIndex = parseInt(GM_getValue("Config.index", -1)) const filteredArr = FuckD.api.hot.filter((name, index) => index != lastIndex) const randomIndex = FuckF.getRandomNum(filteredArr.length) GM_setValue("Config.index", randomIndex) return filteredArr[randomIndex] } FuckF.queryCorpus = [ "what is the weather forecast for this weekend", "how to improve sleep quality naturally", "best breakfast ideas for busy mornings", "how to plan a three day trip to tokyo", "what are the latest electric car battery trends", "how to compare noise cancelling headphones", "why do cats purr when they are relaxed", "easy sourdough bread recipe for beginners", "how to learn rust programming step by step", "what is the difference between async and threading", "best mechanical keyboard switches for typing", "how to fix a leaky kitchen faucet", "healthy dinner ideas under thirty minutes", "how do solar panels work on cloudy days", "latest space exploration news this week", "how to start a small balcony garden", "what causes northern lights aurora", "budget gaming laptop buying guide", "how to organize photos on iphone", "best coffee brewing methods at home", "explain quantum computing in simple terms", "compare index funds and etfs for beginners", "summarize today's technology news highlights", "Copilot prompt to draft a weekly workout plan", "Copilot prompt to compare two travel destinations", "Copilot prompt to explain a difficult concept simply", "Copilot prompt to summarize a long article", "Copilot prompt to make a healthy meal plan", "天气预报 明天适合出门吗", "今日新闻热点 有哪些值得关注", "家常菜菜谱 简单晚餐推荐", "周末旅游攻略 周边游路线", "健康养生知识 睡眠质量提升", "科技资讯 人工智能最新进展", "电影推荐 最近高分电影", "股票行情 今日市场观察", "体育赛事 本周比赛安排", "历史上的今天 有哪些事件", "手机拍照技巧 夜景怎么拍", "咖啡豆怎么选 新手指南", "通勤耳机推荐 降噪和音质对比", "新能源汽车续航 怎么看参数", "解释一下大模型上下文窗口", "帮我比较有氧运动和力量训练", "总结最近科技新闻重点", "给我一个学习英语口语的计划", "如何用 Copilot 写一封商务邮件", ] FuckF.getOfflineQueryWord = () => { const query = FuckD.search.word.list.length > 0 ? FuckD.search.word.list[FuckF.getRandomNum(FuckD.search.word.list.length)] : FuckF.queryCorpus[FuckF.getRandomNum(FuckF.queryCorpus.length)] const suffix = Math.random().toString(36).slice(2, 6) return /[\u4e00-\u9fa5]/.test(query) ? `${query} ${suffix}` : `${query} ${suffix}` } // 等级门槛(newLevel2)与 App 限定(rewardsApp)任务无法通过后台完成,直接跳过 FuckF.isGatedOffer = (offerId, html) => { const id = String(offerId || ""); if (/RewardsApp|locked_level2/i.test(id)) return true; if (!html) return false; const at = html.indexOf(`"offerId":"${id}"`); if (at === -1) return false; const seg = html.slice(Math.max(0, at - 1000), at + 200); return /rewardsApp|newLevel2|"isLocked":true/i.test(seg); } // 手动粘贴的授权链接里 code 可能带 URL 编码(%24 = $),先还原再送去换 token FuckF.parseCode = (str) => { if (!str) return null; const m = String(str).match(/M\.[^&]+/); if (!m) return null; let c = m[0]; try { const d = decodeURIComponent(c); if (d !== c) { c = d; FuckF.log("🔍", "授权码含 URL 编码,已自动还原"); } } catch (e) { } return [c]; } // HPQuiz 首页活动任务 // 尽力读取 bing.com 域名 cookie(service_worker 下 GM_cookie 回调可能不触发,2.5s 超时兜底) FuckF.getBingCookie = (name) => new Promise((resolve) => { let settled = false; const finish = (v) => { if (!settled) { settled = true; resolve(v || ""); } }; setTimeout(() => finish(""), 2500); try { if (typeof GM_cookie === 'undefined') return finish(""); const cb = (cookie) => finish(cookie && cookie.value ? String(cookie.value) : ""); try { GM_cookie.get({ url: "https://www.bing.com", name }, cb); } catch (e) { GM_cookie.get(name, cb); } } catch (e) { finish(""); } }); FuckF.taskHPQuiz = async () => { if (!FuckD.tasks.quiz) return true; FuckF.log("📝", "开始 HPQuiz 首页活动任务..."); const quizServiceUrl = "https://www.bing.com/funapi/api/v1/QuizService/GetContent"; const recordUrl = "https://www.bing.com/funapi/api/quiz/record?ajaxreq=1"; const reportUrl = "https://www.bing.com/rewardsapp/reportActivity"; const authtoken = "321530aa-bbed-4376-a7f1-1c6fd7d2ca2b"; const appUA = "Mozilla/5.0 (Linux; Android 14; MEIZU 21 Build/UKQ1.230917.001; ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/117.0.0.0 Mobile Safari/537.36 BingSapphire/32.6.2110003561"; const searchHost = FuckD.bing.host || "cn.bing.com"; // MUID/SID 从浏览器 cookie 读取:x-search-clientid 必须与 MUID cookie 一致, // 乱传随机值会导致服务端不计分;SID 用于答案页 URL 关联会话 const [muidCookie, ssCookie] = await Promise.all([ FuckF.getBingCookie("MUID"), FuckF.getBingCookie("_SS"), ]); const userId = (muidCookie || GM_getValue("Config.MUID", "") || FuckF.getRandomUUID(true)).toUpperCase(); const sid = (ssCookie.match(/SID=([^&;]+)/) || [])[1] || ""; const clientHeader = muidCookie ? { "x-search-clientid": muidCookie.toUpperCase() } : {}; // 上报积分(抓包:每次 POST 前都先 GET reportActivity 初始化 rewards 会话,url 为页面地址) const reportActivity = async (targetUrl) => { try { await FuckF.xhr({ url: `https://${searchHost}/rewardsapp/reportActivity`, headers: { "User-Agent": appUA, "x-search-market": "zh-CN", ...clientHeader, "Referer": `https://${searchHost}/`, }, }); } catch (e) { FuckF.log("🟡", `HPQuiz:reportActivity 会话初始化失败 🔛${e.message}`); } await FuckF.xhr({ method: "POST", url: reportUrl, headers: { "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", "User-Agent": appUA, "Accept": "*/*", "request_user_info": "true", "x-search-market": "zh-CN", ...clientHeader, "Referer": `https://${searchHost}/`, }, data: `url=${encodeURIComponent(targetUrl)}&V=web` }); }; // 先建立 rewards 会话:提取账号标识 EPuid(按账号隔离每日进度)+ 登录态诊断 // IsAuthenticated/Balance 用于确认 service_worker 环境是否正确携带浏览器 cookie let accountKey = ""; try { const sessionResp = await FuckF.xhr({ url: `https://${searchHost}/rewardsapp/reportActivity`, headers: { "User-Agent": appUA, "x-search-market": "zh-CN", ...clientHeader, "Referer": `https://${searchHost}/`, }, }); if (sessionResp) { const ep = (sessionResp.match(/"EPuid":"([^"]+)"/) || [])[1] || ""; if (ep) accountKey = ep; const auth = (sessionResp.match(/"IsAuthenticated":(\w+)/) || [])[1] || ""; if (auth) FuckF.log("🔍", `HPQuiz:会话诊断 host=${searchHost} IsAuthenticated=${auth} MUID=${muidCookie ? muidCookie.slice(0, 8) + "..." : "未取到"} SID=${sid ? sid.slice(0, 8) + "..." : "未取到"}`); if (auth === "false") { FuckF.log("🟡", "HPQuiz:检测到 bing.com 未登录,积分可能无法计入,请先在浏览器登录对应账号"); } } } catch (e) { FuckF.log("🟡", `HPQuiz:会话初始化失败 🔛${e.message}`); } // 每日问答进度(跨运行续传):已回答过的题目不再重复提交,避免 cron 反复重跑 // v4:按账号(EPuid)隔离进度,修复切换账号后读到上一账号"已答完"而跳过的问题 let quizState = GM_getValue("Config.quiz", false); if (!quizState || quizState.date != FuckF.getDatetime(true) || quizState.v != 4 || (accountKey && quizState.account !== accountKey)) { quizState = { v: 4, date: FuckF.getDatetime(true), account: accountKey, submitted: [] }; GM_setValue("Config.quiz", quizState); } try { // 1. 获取今日图片,提取 quiz_id const imageResp = await FuckF.xhr({ url: "https://www.bing.com/HPImageArchive.aspx?n=1&idx=-1&format=js&pid=opal&mbl=1", headers: { "User-Agent": appUA } }); if (!imageResp || !FuckF.isJSON(imageResp)) { FuckF.log("🟡", "HPQuiz:获取今日图片失败"); return true; } const imageData = JSON.parse(imageResp); const todayImage = imageData.images?.[0]; const quizPath = todayImage?.quiz || ""; if (!quizPath) { FuckF.log("✅", "HPQuiz:今日无 Quiz 任务"); return true; } // 提取 quiz_id,如 HPQuiz_20260914_RedMacawsFlight const quizIdMatch = quizPath.match(/HPQuiz_\d+_(\w+)/); if (!quizIdMatch) { FuckF.log("✅", "HPQuiz:未找到有效 Quiz ID"); return true; } const quizCore = quizIdMatch[1]; // 核心名称,如 RedMacawsFlight const title = todayImage.copyright || quizIdMatch[0]; // 今日该 Quiz 已全部答完:直接跳过,不再拉取题目(仅一次轻量 HPImageArchive 请求) if (quizState.quizDone && quizState.quizKey === quizCore) { FuckF.log("✅", `HPQuiz:今日 Quiz 已答完(${quizState.total}/${quizState.total} 题),跳过`); return true; } FuckF.log("📝", `发现 Quiz:${title} (核心: ${quizCore})`); // 2. 获取题目和答案 const quizResp = await FuckF.xhr({ method: "POST", url: quizServiceUrl, headers: { "Content-Type": "application/json; charset=utf-8", "User-Agent": appUA, "authtoken": authtoken, }, data: JSON.stringify({ PartnerName: "StartApp", Market: "zh-cn", QuizCategory: "HOMEPAGEQUIZ" }) }); if (!quizResp || !FuckF.isJSON(quizResp)) { FuckF.log("🟡", "HPQuiz:获取题目失败"); return true; } const quizBody = JSON.parse(quizResp); const quizzes = quizBody.quizzes || []; FuckF.log("📝", `HPQuiz:API 返回 ${quizzes.length} 个 Quiz`); if (quizzes.length > 0) { const quizIds = quizzes.map(q => q.quizId || q.id || "unknown").join(", "); FuckF.log("📝", `HPQuiz:可用 ID: ${quizIds}`); } // 找到匹配的 quiz(按核心名称匹配,忽略日期) let targetQuiz = null; for (const quiz of quizzes) { const qid = quiz.quizId || quiz.id || ""; if (qid.includes(quizCore)) { targetQuiz = quiz; break; } } if (!targetQuiz) { FuckF.log("🟡", `HPQuiz:未找到 ${quizCore} 的题目`); return true; } // 3. 提取题目和正确答案 const questions = targetQuiz.questions || []; if (questions.length === 0) { FuckF.log("🟡", "HPQuiz:题目列表为空"); return true; } FuckF.log("📝", `HPQuiz:${questions.length} 道题目待回答`); // 3.1 先访问 Quiz 落地页并上报(抓包:点击首页 quiz 卡片 → QuizLanding 搜索页 → reportActivity) const fullQuizId = targetQuiz.quizId || quizIdMatch[0]; const promo = targetQuiz.promotionInfo || {}; const attrs = targetQuiz.attributes || {}; const quizQuery = attrs.query || (todayImage.copyrightlink?.match(/q=([^&]+)/)?.[1]) || quizCore; let landingUrl = (promo.bingLandingUrl || "").trim(); if (landingUrl) { // 服务端下发的落地页(www 域)转到当前访问域 landingUrl = landingUrl.replace(/^https?:\/\/www\.bing\.com/, `https://${searchHost}`); } else { const landingFilters = `mgzv3configlist%3A%22BingQA_QuizLanding_Layout%22%20IsConversation%3A%22True%22%20WQOskey%3A%22${encodeURIComponent(fullQuizId)}%22%20WQId%3A%221%22`; landingUrl = `https://${searchHost}/search?q=${encodeURIComponent(quizQuery)}&filters=${landingFilters}`; } landingUrl += `${landingUrl.includes("?") ? "&" : "?"}form=EWV001&ssp=1&safesearch=moderate&setlang=zh-hans&cc=CN&ensearch=0&PC=SANSAAND`; let referer = `https://${searchHost}/`; try { const landingFinal = await FuckF.xhr({ url: landingUrl, headers: { "User-Agent": appUA, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Upgrade-Insecure-Requests": "1", "x-requested-with": "com.microsoft.bing", "Referer": referer, }, }, true); await reportActivity(landingFinal || landingUrl); referer = landingFinal || landingUrl; FuckF.log("📝", "HPQuiz:已访问 Quiz 落地页并上报"); await new Promise(r => setTimeout(r, 1000 + Math.random() * 500)); } catch (e) { FuckF.log("🟡", `HPQuiz:落地页访问失败 🔛${e.message}`); } // 4. 逐题提交答案(按抓包流程:访问答案页 → quiz/record 记录投票 → reportActivity 上报积分) let successCount = 0; for (let i = 0; i < questions.length; i++) { const question = questions[i]; const questionText = question.questionText || ""; const questionId = question.questionId || `${targetQuiz.quizId || quizCore}_${i + 1}`; const choices = question.answerChoices || []; // 每日状态续传:已回答过的题目直接跳过,避免 cron 反复重跑同一天问卷 if (quizState.submitted.includes(questionId)) continue; // 找到正确答案及其序号(OptionText 需带字母前缀,如 "B. 美洲白杨",与抓包一致) let correctIndex = -1; for (let j = 0; j < choices.length; j++) { if (choices[j].isCorrectAnswer === true) { correctIndex = j; break; } } if (correctIndex < 0) { FuckF.log("🟡", `HPQuiz:第 ${i + 1} 题未找到正确答案,跳过`); continue; } const correctChoice = choices[correctIndex]; const optionText = `${String.fromCharCode(65 + correctIndex)}. ${correctChoice.displayText || ""}`; let step = "答案页访问"; try { // 4.1 计分 URL(requeryUrl 由服务端下发,内含 WQOskey/WQCI/WQSCORE 等计分参数) // 关键:第 2 题起 WQSCORE 是占位符 "WQSCOREVALUE",须替换为累计答对题数(全对即题目序号) let scoreUrl = (correctChoice.requeryUrl || "").replace("WQSCOREVALUE", String(i + 1)); if (!scoreUrl) { scoreUrl = `/search?q=${encodeURIComponent(questionText)}&FORM=HPQUIZ`; } if (scoreUrl.startsWith("/")) { scoreUrl = `https://${searchHost}${scoreUrl}`; } if (!/[?&]PC=/.test(scoreUrl)) { scoreUrl += `${scoreUrl.includes("?") ? "&" : "?"}PC=SANSAAND`; } // 4.2 访问 URL(抓包:App 答案页额外带 sid/format=snrjson/jsoncbid/progenabled/ajaxnorecss) const visitParams = []; if (sid && !/[?&]sid=/.test(scoreUrl)) visitParams.push(`sid=${encodeURIComponent(sid)}`); visitParams.push("format=snrjson", `jsoncbid=${i}`, "progenabled=1", "ajaxnorecss=1"); const visitUrl = `${scoreUrl}${scoreUrl.includes("?") ? "&" : "?"}${visitParams.join("&")}`; // 4.3 访问答案搜索结果页:干净 URL 优先(本环境验证可记分),失败时才试 snrjson 完整参数 const pageHeaders = { "User-Agent": appUA, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Upgrade-Insecure-Requests": "1", "x-requested-with": "com.microsoft.bing", "Referer": referer, }; let visitFinal = null; try { visitFinal = await FuckF.xhr({ url: scoreUrl, headers: pageHeaders, }, true); } catch (e) { FuckF.log("🟡", `HPQuiz 第 ${i + 1} 题答案页(干净URL)失败 🔛${e.message},尝试 snrjson 完整参数`); visitFinal = await FuckF.xhr({ url: visitUrl, headers: { "User-Agent": appUA, "Referer": `https://${searchHost}/` }, }, true); } referer = visitFinal || scoreUrl; // 4.4 记录投票(抓包:POST funapi/api/quiz/record,content-type 为 text/plain) step = "投票记录"; const recordResp = await FuckF.xhr({ method: "POST", url: recordUrl, headers: { "Content-Type": "text/plain;charset=UTF-8", "User-Agent": appUA, "x-requested-with": "com.microsoft.bing", "Referer": `https://${searchHost}/`, "Origin": `https://${searchHost}`, }, data: JSON.stringify({ PartnerId: "BingQAUX", QuestionText: questionText, OptionText: optionText, UserId: userId }) }); if (recordResp && FuckF.isJSON(recordResp)) { const rec = JSON.parse(recordResp); if (Number(rec.ErrorCode) !== 200) { FuckF.log("🟡", `HPQuiz 第 ${i + 1} 题投票异常 ErrorCode=${rec.ErrorCode}`); } } // 4.5 上报积分(抓包:POST rewardsapp/reportActivity,url 为计分 URL 原样,不带 snrjson 参数) step = "积分上报"; await reportActivity(scoreUrl); quizState.submitted.push(questionId); GM_setValue("Config.quiz", quizState); successCount++; FuckF.log("📝", `HPQuiz 第 ${i + 1}/${questions.length} 题已提交:${questionText.substring(0, 20)}...`); await new Promise(r => setTimeout(r, 1500 + Math.random() * 1000)); } catch (e) { FuckF.log("🟡", `HPQuiz 第 ${i + 1} 题提交失败(${step}):${e.message}`); } } // 5. 全部答完:上报完结页(抓包:q=Bing quiz, WQQI=题目数, WQCI=-1, WQSCORE=最终得分) // 这一步是任务完成的关键标志,昨晚/今晚两份抓包均有此请求 if (quizState.submitted.length >= questions.length) { try { const finalScore = questions.length; // 本流程始终选正确答案,最终得分即题目数 const endFilters = `mgzv3configlist%3A%22BingQA_Quiz_layout%22+dw_answerstobesuppressed%3A%22ConversationalSearch%22+IsConversation%3A%22True%22+WQOskey%3A%22${encodeURIComponent(fullQuizId)}%22+WQId%3A%221%22+WQQI%3A%22${questions.length}%22+WQCI%3A%22-1%22+ShowTimesTaskPaneTrigger%3A%22false%22+WQSCORE%3A%22${finalScore}%22`; const endUrl = `https://${searchHost}/search?q=Bing+quiz&filters=${endFilters}&FORM=CWQHYL&rnoreward=1&skipopalnative=true&PC=SANSAAND`; // 访问完结页:干净 URL 优先(本环境验证可记分),失败时才试 snrjson 完整参数 const endVisitParams = []; if (sid) endVisitParams.push(`sid=${encodeURIComponent(sid)}`); endVisitParams.push("format=snrjson", `jsoncbid=${questions.length}`, "progenabled=1", "ajaxnorecss=1"); const endVisitUrl = `${endUrl}&${endVisitParams.join("&")}`; try { await FuckF.xhr({ url: endUrl, headers: { "User-Agent": appUA, "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7", "Upgrade-Insecure-Requests": "1", "x-requested-with": "com.microsoft.bing", "Referer": referer, }, }, true); } catch (e) { FuckF.log("🟡", `HPQuiz:完结页(干净URL)失败 🔛${e.message},尝试 snrjson 完整参数`); await FuckF.xhr({ url: endVisitUrl, headers: { "User-Agent": appUA, "Referer": `https://${searchHost}/` }, }, true); } await reportActivity(endUrl); FuckF.log("📝", `HPQuiz:已上报完结页(最终得分 ${finalScore}/${questions.length})`); quizState.quizDone = true; quizState.quizKey = quizCore; quizState.total = questions.length; GM_setValue("Config.quiz", quizState); FuckF.log("✅", `HPQuiz 完成:${questions.length}/${questions.length} 题已提交并上报完结页`); } catch (e) { FuckF.log("🟡", `HPQuiz:完结页上报失败 🔛${e.message},下一轮重试`); } } else if (successCount > 0) { FuckF.log("🟡", `HPQuiz 部分完成:${quizState.submitted.length}/${questions.length} 题已提交,下一轮继续`); } return true; } catch (e) { FuckF.log("🔴", `HPQuiz 异常:${e.message}`); return true; } } // ===== 仅限积分商城应用卡片(App 专属每日优惠,如"社会工作先驱")===== // 网页端(普通浏览器 UA)这些卡渲染为加锁不可点,但 PC 版微软积分商城应用是 WebView2 客户端, // UA 带 MSRewards/Desktop 尾缀,服务端据此渲染为解锁态。改 UA 实测 + 抓包双重证实: // App UA 下 isLocked=false/isUnlocked=true,hash/offerId 与网页端完全一致, // 完成机制为同款 server action(POST /earn + next-action + [hash, 11, {offerid}])。 FuckF.MSREWARDS_UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36 Edg/152.0.0.0 MSRewards/Desktop/1.3.0"; FuckF.taskRewardsAppCards = async () => { if (!FuckD.tasks.rewardsApp) return true; try { // 1. 用 App UA 拉取 earn 页,拿到解锁态的 App 专属卡 const html = await FuckF.xhr({ url: "https://rewards.bing.com/earn", headers: { "User-Agent": FuckF.MSREWARDS_UA }, }); if (!html) return true; const clean = html.replace(/\\"/g, '"'); const cardsRaw = FuckF.extractArrayAfterKey(clean, "activityCards"); if (!cardsRaw) return true; let cards = []; try { cards = JSON.parse(cardsRaw.replace(/\\"/g, '"')); } catch { return true; } // 2. 筛选:App 专属卡(offerId 含 Moreactivities_RewardsApp 或 unlockCriteria=rewardsApp)且未完成 const targets = cards.filter(c => { const oid = c.offerId || c.name || ""; const isAppCard = /Moreactivities_RewardsApp/i.test(oid) || c.unlockCriteria === "rewardsApp"; return isAppCard && !c.isCompleted && c.hash && (parseInt(c.points) || 0) > 0; }); if (targets.length === 0) return true; // 3. server action 标识(与活动任务同源;未缓存页面时自行抓取) const actionInfo = await FuckF.getRewardsAction([lastRewardHtml.dash, lastRewardHtml.earn]); const actionId = actionInfo.ids.reportActivity || "70babbc81d2724f60d29a95c03b3d739cba77cea92"; FuckF.log("📱", `发现 ${targets.length} 个仅限 App 卡片,通过 Rewards 应用通道完成...`); let done = 0; for (const card of targets) { const headers = { "content-type": "text/plain;charset=UTF-8", "next-action": actionId, "referer": "https://rewards.bing.com/earn", // 显式覆盖 origin:扩展 service_worker 请求被浏览器自动附带 Origin: chrome-extension://, // 风控账号的 server action 对非站点同源 origin 直接抛 500 "origin": "https://rewards.bing.com", "user-agent": FuckF.MSREWARDS_UA, "next-router-state-tree": FuckF.getRouterStateTree("earn"), }; if (actionInfo.dpl) headers["x-deployment-id"] = actionInfo.dpl; try { await FuckF.xhr({ method: "POST", url: "https://rewards.bing.com/earn", headers, data: JSON.stringify([ card.hash, 11, { "offerid": card.offerId, "isPromotional": "$undefined", "timezoneOffset": "-480" } ]), }); done++; FuckF.log("🟢", `仅限 App 卡片已完成:${card.title} +${card.points}p`); await new Promise(r => setTimeout(r, FuckF.getScopeRandomNum(1500, 3000))); } catch (e) { FuckF.log("🟡", `仅限 App 卡片失败:${card.title} 🔛${e.message}`); } } if (done > 0) FuckF.log("✅", `仅限 App 卡片完成:${done}/${targets.length} 个`); } catch (e) { FuckF.log("🟡", `仅限 App 卡片任务异常 🔛${e.message}`); } return true; }; FuckF.getToken = async (url, maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await FuckF.xhr({ url: url }); if (result && FuckF.isJSON(result)) { const res = JSON.parse(result); const refresh = res.refresh_token; const access = res.access_token; if (refresh && access) { GM_setValue("Config.token", refresh); FuckD.bing.token = access; return true; } } } catch (e) { if (e.message.includes("400") || e.message.includes("401")) { return false; } if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, 3210)); continue; } FuckF.log("🔴", `Token 请求失败!🔛${e.message}`); return null; } } return null; } FuckF.renewToken = async () => { if (!FuckD.tasks.sign && !FuckD.tasks.read && !FuckD.tasks.edge) return true; let refreshToken = GM_getValue("Config.token", false); const authUrl = "https://login.live.com/oauth20_authorize.srf?client_id=0000000040170455&response_type=code&scope=service::prod.rewardsplatform.microsoft.com::MBI_SSL&redirect_uri=https://login.live.com/oauth20_desktop.srf"; const pollForAuthCodeFromWindow = (authWindow, maxSeconds) => { return new Promise((resolve) => { const startTime = Date.now(); const checkInterval = setInterval(() => { try { if (authWindow && !authWindow.closed) { const currentUrl = authWindow.location.href; if (currentUrl && currentUrl.includes("code=")) { const urlParams = new URLSearchParams(new URL(currentUrl).search); const authCode = urlParams.get("code"); if (authCode && authCode.length > 10) { clearInterval(checkInterval); const fullCode = `https://login.live.com/oauth20_desktop.srf?code=${authCode.trim()}`; GM_setValue("Config.code", fullCode); GM_setValue("Config.token", false); FuckF.log("🟢", "通过子窗口检测到授权码"); resolve(authCode.match(/M\.[^&]+/)); return; } } } } catch (e) {} if (Date.now() - startTime >= maxSeconds * 1000) { clearInterval(checkInterval); resolve(null); } }, 300); }); }; const pollForAuthCodeViaRequest = (maxSeconds) => { return new Promise((resolve) => { const startTime = Date.now(); const checkInterval = setInterval(() => { GM_xmlhttpRequest({ method: "GET", url: "https://login.live.com/oauth20_desktop.srf", onload: function(response) { if (response.responseText) { const match = response.responseText.match(/code=([^&"]+)/); if (match && match[1] && match[1].length > 10) { clearInterval(checkInterval); const fullCode = `https://login.live.com/oauth20_desktop.srf?code=${match[1].trim()}`; GM_setValue("Config.code", fullCode); GM_setValue("Config.token", false); FuckF.log("🟢", "通过GM_xmlhttpRequest检测到授权码"); resolve(match[1].match(/M\.[^&]+/)); return; } } }, onerror: function() {} }); if (Date.now() - startTime >= maxSeconds * 1000) { clearInterval(checkInterval); resolve(null); } }, 1000); }); }; const getAuthCodeFromCookie = function() { return new Promise((resolve) => { let settled = false; const finish = (v) => { if (!settled) { settled = true; resolve(v); } }; // service_worker(@crontab 定时脚本)环境下 GM_cookie.get 回调可能永不触发, // 不加超时会令 await 永久挂起、整个授权码轮询卡死(日志表现为此后再无任何输出) setTimeout(() => finish(null), 3000); try { if (typeof GM_cookie !== 'undefined') { GM_cookie.get("microsoft_rewards_auth_code", (cookie) => { if (cookie && cookie.value) { finish(decodeURIComponent(cookie.value)); } else { finish(null); } }); } else if (typeof document !== 'undefined') { const value = "; " + document.cookie; const parts = value.split("; microsoft_rewards_auth_code="); if (parts.length === 2) { finish(decodeURIComponent(parts.pop().split(";").shift())); } else { finish(null); } } else { finish(null); } } catch (e) { finish(null); } }); }; const pollForAuthCode = async (maxSeconds, excludeCode = "") => { return new Promise((resolve) => { let settled = false; let listenerId = null; const done = (match) => { if (settled) return; settled = true; if (listenerId != null && typeof GM_removeValueChangeListener !== 'undefined') { try { GM_removeValueChangeListener(listenerId); } catch (e) {} } resolve(match); }; const accept = (code) => { if (settled || !code) return; const m = String(code).match(/M\.[^&]+/); // 排除已失效的旧授权码,防止读到清空前的残留快照值 if (m && (!excludeCode || m[0] !== excludeCode)) done(m); }; // 实时跨脚本监听:捕获脚本(同 storageName)写入 Config.code 后立即收到通知, // 不依赖本运行上下文内 GM_getValue 能否看到跨脚本写入 if (typeof GM_addValueChangeListener !== 'undefined') { try { listenerId = GM_addValueChangeListener("Config.code", (name, oldValue, newValue) => { accept(newValue); }); } catch (e) {} } (async () => { const startTime = Date.now(); while (!settled && Date.now() - startTime < maxSeconds * 1000) { await new Promise(r => setTimeout(r, 500)); if (settled) break; let code = GM_getValue("Config.code", ""); if (!code) { const cookieCode = await getAuthCodeFromCookie(); if (cookieCode) { code = cookieCode; GM_setValue("Config.code", code); GM_setValue("Config.token", false); } } accept(code); } done(null); })(); }); }; const fetchCodeLogic = async (msg) => { // 记录旧授权码:清空只影响存储,轮询时用它过滤残留的失效旧码 const oldCodeMatch = (GM_getValue("Config.code", "") || "").match(/M\.[^&]+/); const excludeCode = oldCodeMatch ? oldCodeMatch[0] : ""; GM_setValue("Config.code", ""); FuckF.log("🟡", `${msg},正在打开授权页面...`); let authWindow = null; if (typeof window !== 'undefined' && window.open) { authWindow = window.open(authUrl, '_blank', 'width=600,height=600'); } else { GM_openInTab(authUrl, { active: true, insert: true, setParent: true }); } await new Promise(resolve => setTimeout(resolve, 2000)); FuckF.log("🟡", `请在打开的页面中完成登录授权,等待授权码...`); FuckF.log("🟡", `等待授权码...请在打开的授权页面中完成登录`); let codeMatch = await pollForAuthCode(120, excludeCode); if (codeMatch) { FuckF.log("🟢", "授权码获取成功"); return codeMatch; } FuckD.bing.sendMSG = `${msg},自动获取授权码超时,请手动复制授权页面URL后点击脚本菜单的"粘贴授权码"选项`; FuckF.log("🟡", FuckD.bing.sendMSG, authUrl); FuckF.send(FuckD.wh); codeMatch = await pollForAuthCode(120, excludeCode); if (codeMatch) { FuckF.log("🟢", "授权码手动获取成功"); return codeMatch; } FuckD.tasks.sign = false; FuckD.tasks.read = false; return false; }; if (!refreshToken) { let code = GM_getValue("Config.code", ""); let codeMatch = code ? code.match(/M\.[^&]+/) : null; if (!codeMatch) { codeMatch = await fetchCodeLogic("检测到授权码为空"); if (!codeMatch) return false; } const url = `https://login.live.com/oauth20_token.srf?client_id=0000000040170455&code=${encodeURIComponent(codeMatch[0])}&redirect_uri=https://login.live.com/oauth20_desktop.srf&grant_type=authorization_code`; const token = await FuckF.getToken(url); if (token === null) return false; if (!token) { codeMatch = await fetchCodeLogic("检测到授权码失效"); if (!codeMatch) return false; return await FuckF.renewToken(); } FuckD.bing.sendMSG = "Token 获取成功!"; FuckF.log("🟢", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); return true; } else { const url = `https://login.live.com/oauth20_token.srf?client_id=0000000040170455&refresh_token=${encodeURIComponent(refreshToken)}&scope=service::prod.rewardsplatform.microsoft.com::MBI_SSL&grant_type=REFRESH_TOKEN`; const token = await FuckF.getToken(url); if (token === null) return false; if (!token) { GM_setValue("Config.token", false); GM_setValue("Config.code", ""); const codeMatch = await fetchCodeLogic("检测到 Token 失效"); if (!codeMatch) return false; return await FuckF.renewToken(); } return true; } } FuckF.getRewardsInfo = async (maxRetries = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const earnHtml = await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); const cleanHtml = earnHtml.replace(/\\"/g, '"'); const balMatch = cleanHtml.match(/"balance":(\d+)/) || cleanHtml.match(/"availablePoints":(\d+)/); const keyword = '"pointsCounters":{'; const idx = cleanHtml.indexOf(keyword); if (!balMatch || idx === -1) { if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, 3210)); continue; } return false; } let balance = parseInt(balMatch[1]); let pcMax = 60, pcCur = 0; let mobMax = 0, mobCur = 0; let dailyPoint = 0; let todayTotal = 0; let start = idx + 17; let openBraces = 0; let end = start; for (let i = start; i < cleanHtml.length; i++) { if (cleanHtml[i] === '{') openBraces++; if (cleanHtml[i] === '}') { openBraces--; if (openBraces === 0) { end = i + 1; break; } } } try { const pts = JSON.parse(cleanHtml.substring(start, end)); pcMax = pts.pc?.max ?? 60; pcCur = pts.pc?.progress ?? 0; mobMax = pts.mobile?.max ?? 0; mobCur = pts.mobile?.progress ?? 0; dailyPoint = pts.dailyOffer ?? 0; todayTotal = pts.totalPoints ?? 0; } catch { FuckF.log("🟡", "仪表盘解析异常,将使用保底数值"); } FuckD.bing.todayTotalPoints = todayTotal; return { userStatus: { availablePoints: balance, counters: { pcSearch: [{ pointProgress: pcCur, pointProgressMax: pcMax }], mobileSearch: [{ pointProgress: mobCur, pointProgressMax: mobMax }], dailyPoint: [{ pointProgress: dailyPoint }] } } }; } catch (e) { if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, 3210)); continue; } FuckF.log("🔴", `仪表盘请求失败!🔛 ${e.message}`); } } return false; } FuckF.taskSign = async () => { if (!FuckD.tasks.sign || FuckD.sign.date == FuckD.bing.dateNowNum || FuckD.sign.times > 2 || FuckD.sign.end > 0) { FuckD.sign.end++ return true } if (FuckD.sign.point >= 0) { FuckD.sign.end++ if (FuckD.sign.date != FuckD.bing.dateNowNum) { FuckD.sign.date = FuckD.bing.dateNowNum FuckD.bing.sendMSG = `📅哥哥好棒!签入任务已完成!\n${FuckD.sign.point > 0 ? `✨今日签入奖励:${FuckD.sign.point}` : "🍵今日已签入,无法二次签入"}` FuckF.log("🔵", FuckD.bing.sendMSG, true) FuckF.send(FuckD.wh) } GM_setValue("Config.tasks", FuckD.bing.tasks) return true } try { const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } const region = FuckD.bing.status ? "cn" : FuckD.bing.region.toLowerCase() const result = await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", "x-rewards-country": region, "x-rewards-partnerid": "startapp", "x-rewards-flights": "rwgobig", }, data: JSON.stringify({ "amount": 1, "attributes": {}, "id": FuckF.getRandomUUID(), "type": 103, "country": region, "risk_context": {}, "channel": "SAAndroid", }), }) if (result && FuckF.isJSON(result)) { FuckD.sign.times = 0 const res = JSON.parse(result) const point = res.response.activity.p FuckD.sign.point = point || 0; GM_setValue("Config.signPoint", FuckD.sign.point); } else { FuckD.sign.times++ } } catch (e) { FuckF.log("🔴", `签入任务出错!🔛${e.message}`) FuckD.sign.times++; } return false } FuckF.getReadPro = async () => { try { const result = await FuckF.xhr({ url: "https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", }, }); if (result && FuckF.isJSON(result)) { const res = JSON.parse(result); const promos = res.response?.promotions || []; const readTask = promos.find(x => x.attributes?.offerid === 'ENUS_readarticle3_30points'); if (readTask) { const progress = parseInt(readTask.attributes.progress) || 0; const max = parseInt(readTask.attributes.max) || 30; return { progress, max }; } } } catch (e) { FuckF.log("🔴", `阅读进度获取出错!🔛${e.message}`); } return false; } // 拉取 dapi APP 通道的全量活动列表(每日打卡等 APP 专属活动在此下发,与阅读进度同源) FuckF.getPromotions = async () => { try { const result = await FuckF.xhr({ url: "https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", }, }); if (result && FuckF.isJSON(result)) { return JSON.parse(result).response?.promotions || []; } } catch (e) { FuckF.log("🔴", `活动列表获取出错!🔛${e.message}`); } return false; } FuckF.taskRead = async () => { if (!FuckD.tasks.read || FuckD.read.times > 2 || FuckD.read.end > 0) { FuckD.read.end++; return true; } try { const readPro = await FuckF.getReadPro(); if (!readPro) { FuckD.read.times++ return false; } let cur = readPro.progress || 0; let max = readPro.max || 30; if (FuckD.read.date != FuckD.bing.dateNowNum) { FuckF.log("📖", `阅读进度获取成功:${cur}/${max}`); await new Promise(resolve => setTimeout(resolve, 500)); } if (cur >= max) { if (FuckD.read.date != FuckD.bing.dateNowNum) { FuckF.log("📖", "接口返回进度已满,但本地无今日记录,强制开始阅读..."); cur = 0; max = 30; } else { FuckD.read.end++; return true; } } const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } const region = FuckD.bing.status ? "cn" : FuckD.bing.region.toLowerCase(); FuckD.read.date = 0; const readsNeeded = Math.ceil((max - cur) / 3); for (let i = 0; i < readsNeeded; i++) { await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", "x-rewards-country": region, }, data: JSON.stringify({ "amount": 1, "country": region, "id": FuckF.getRandomUUID(), "type": 101, "attributes": { "offerid": "ENUS_readarticle3_30points", }, }), }); FuckF.log("📖", `正在阅读第 ${i + 1}/${readsNeeded} 篇文章...`); const waitTime = FuckF.getScopeRandomNum(3000, 7000); await new Promise(resolve => setTimeout(resolve, waitTime)); } FuckD.read.end++; if (FuckD.read.date != FuckD.bing.dateNowNum) { FuckD.read.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); const actualCur = cur + (readsNeeded * 3); const finalCur = actualCur > max ? max : actualCur; GM_setValue("Config.readPoint", finalCur); FuckD.bing.sendMSG = `哥哥好棒!📖阅读任务已完成!\n✨今日阅读奖励:${finalCur}/${max}`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return true; } catch (e) { FuckF.log("🔴", `阅读任务出错!🔛${e.message}`); FuckD.read.times++; return false; } } // 构建 Next.js 新版路由状态树(next-router-state-tree 请求头) FuckF.getRouterStateTree = (segment) => { const refreshFlag = 4096; const tree = ["", { children: ["(nav)", { children: [segment, { children: ["PAGE", {}, null, null, refreshFlag] }, null, null, refreshFlag] }, null, null, refreshFlag] }, null, null, refreshFlag + 16]; return encodeURIComponent(JSON.stringify(tree)); } // 清除 action 标识缓存(404 时调用,下次运行重新提取) FuckF.clearActionCache = () => { GM_setValue("Config.actionMap", ""); GM_setValue("Config.actionId", ""); GM_setValue("Config.actionBuild", ""); GM_setValue("Config.actionDpl", ""); } // 从 rewards.bing.com 页面引用的 JS 包中动态提取 server action ID 映射表 // 微软每次重新部署站点都会变更这些 ID,硬编码会导致交卷/领取请求 404 // 返回 { ids: { reportActivity, reportClaimAllPoints, ... }, dpl } FuckF.getRewardsAction = async (htmls) => { const REQUIRED_ACTIONS = ["reportActivity", "reportClaimAllPoints"]; const cached = { build: GM_getValue("Config.actionBuild", ""), map: GM_getValue("Config.actionMap", ""), id: GM_getValue("Config.actionId", ""), dpl: GM_getValue("Config.actionDpl", ""), }; let cachedMap = {}; try { cachedMap = JSON.parse(cached.map) || {}; } catch {} if (!cachedMap.reportActivity && cached.id) cachedMap.reportActivity = cached.id; try { let combined = (htmls || []).filter(Boolean).join("\n"); let dpl = combined.match(/[?&](?:amp;)?dpl=([A-Za-z0-9._-]+)/i)?.[1] || ""; let build = dpl || combined.match(/\/_next\/static\/([A-Za-z0-9._-]+)\//)?.[1] || ""; // 缓存含交卷标识且构建未变化(或未提供页面、无从判断)时直接复用,避免重复抓取 JS 包 // 领取标识缺失不阻断缓存,taskClaim 有已知标识兜底 const cacheOK = cached.build && cachedMap.reportActivity; if (cacheOK && (!build || build === cached.build)) { return { ids: cachedMap, dpl: cached.dpl || dpl }; } // 未提供页面 HTML 时(如领取任务单独调用)自行抓取 if (!combined) { try { const dashHtml = await FuckF.xhr({ url: "https://rewards.bing.com/dashboard" }); const earnHtml = await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); combined = [dashHtml, earnHtml].filter(Boolean).join("\n"); dpl = combined.match(/[?&](?:amp;)?dpl=([A-Za-z0-9._-]+)/i)?.[1] || ""; build = dpl || combined.match(/\/_next\/static\/([A-Za-z0-9._-]+)\//)?.[1] || ""; } catch {} } const chunkSet = new Set(); const chunkRegex = /(?:\/_next\/)?(static\/(?:chunks|immutable|media)\/[\w\-./()]+?\.js)/g; let chunkMatch; while ((chunkMatch = chunkRegex.exec(combined)) !== null) { chunkSet.add("/_next/" + chunkMatch[1]); } // 从 webpack 运行时清单中枚举按需加载的动态分包(领取组件等不在初始分包中) // 新版分包哈希已非纯十六进制(如 4ds1xh5s3),放宽字符集 const extractDynamicCandidates = (text, set) => { const re = /\b(\d{2,6}):"([a-z0-9_-]{8,})"/g; let m; while ((m = re.exec(text)) !== null) { set.add(`/_next/static/chunks/${m[1]}-${m[2]}.js`); set.add(`/_next/static/chunks/${m[1]}.${m[2]}.js`); } } // 页面相关静态分包优先 const queue = [...chunkSet].sort((a, b) => { const score = (x) => /(dashboard|earn|page|layout|nav)/i.test(x) ? 0 : 1; return score(a) - score(b); }).slice(0, 40); const dynSeed = new Set(); extractDynamicCandidates(combined, dynSeed); queue.push(...dynSeed); // 已知历史领取分包兜底(CDN 保留旧分包,新版清单不可枚举时仍可从中提取领取标识) queue.push("/_next/static/chunks/4069k0g6u-2ge.js"); const hex = "[a-f0-9]{40,64}"; const createRegex = new RegExp(`createServerReference\\s*\\)?\\s*\\(\\s*"(${hex})"([\\s\\S]{0,800}?)\\)`, "g"); const registerRegex = new RegExp(`registerServerReference\\s*\\)?\\s*\\([^,]+,\\s*"(${hex})"([\\s\\S]{0,800}?)\\)`, "g"); const nonNames = ["callServer", "findSourceMapURL", "encodeFormAction", "default"]; const scanChunk = (js, map) => { for (const regex of [createRegex, registerRegex]) { regex.lastIndex = 0; let m; while ((m = regex.exec(js)) !== null) { const names = [...(m[2] || "").matchAll(/"([A-Za-z_$][\w$]*)"/g)] .map(x => x[1]) .filter(n => !nonNames.includes(n) && n.length > 3); const name = names[names.length - 1]; if (name && !map[name]) map[name] = m[1]; } } } const actionMap = {}; const visited = new Set(); let fetched = 0; const maxFetch = 100; while (queue.length && fetched < maxFetch) { const path = queue.shift(); if (visited.has(path)) continue; visited.add(path); fetched++; try { const js = await FuckF.xhr({ url: `https://rewards.bing.com${path}` }); if (!js) continue; scanChunk(js, actionMap); if (!REQUIRED_ACTIONS.every(k => actionMap[k])) { // 已抓分包中可能含有更完整的动态分包清单,持续发现 const more = new Set(); extractDynamicCandidates(js, more); for (const p of more) { if (!visited.has(p) && !queue.includes(p)) queue.push(p); } } if (REQUIRED_ACTIONS.every(k => actionMap[k])) break; } catch {} } if (actionMap.reportActivity) { GM_setValue("Config.actionBuild", build); GM_setValue("Config.actionMap", JSON.stringify(actionMap)); GM_setValue("Config.actionDpl", dpl); FuckF.log("🧩", `服务端标识更新成功(build:${build || "未知"},动作数:${Object.keys(actionMap).length})`); return { ids: actionMap, dpl }; } FuckF.log("🟡", "未能从页面 JS 包中提取服务端标识,使用缓存/默认值"); } catch (e) { FuckF.log("🟡", `服务端标识获取出错!🔛${e.message}`); } return { ids: cachedMap, dpl: cached.dpl || "" }; } // ---- 稳健提取数组(括号配平,不依赖固定锚点如 "discovery",上游 3.6.50 合并)---- FuckF.extractArrayAfterKey = function(html, key) { const marker = '"' + key + '":'; const idx = html.indexOf(marker); if (idx === -1) return null; let i = html.indexOf('[', idx); if (i === -1) return null; let depth = 0, inStr = false, esc = false; for (let j = i; j < html.length; j++) { const c = html[j]; if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; } else { if (c === '"') inStr = true; else if (c === '[') depth++; else if (c === ']') { depth--; if (depth === 0) return html.slice(i, j + 1); } } } return null; }; // ---- 日期归一化为 YYYYMMDD,解析失败返回 null(上游 3.6.50 合并)---- FuckF.normalizeDate = function(d) { if (d == null) return null; let m = String(d).match(/(\d{1,2})\/(\d{1,2})\/(\d{4})/); // M/D/YYYY 或 MM/DD/YYYY if (m) return Number(m[3] + String(m[1]).padStart(2, '0') + String(m[2]).padStart(2, '0')); m = String(d).match(/(\d{4})[-/](\d{1,2})[-/](\d{1,2})/); // YYYY-M-D 或 YYYY/MM/DD if (m) return Number(m[1] + String(m[2]).padStart(2, '0') + String(m[3]).padStart(2, '0')); const t = Date.parse(d); if (!isNaN(t)) { const dt = new Date(t); return Number(dt.getFullYear() + String(dt.getMonth()+1).padStart(2, '0') + String(dt.getDate()).padStart(2, '0')); } return null; }; // ---- 每日额外积分(仅当 dashboard 页面打开时,按 XPath 点击按钮,上游 3.6.50 合并)---- FuckF.claimExtraPoints = async () => { if (!FuckD.tasks.bonus) return; const todayNum = FuckF.getDatetime(true); if (GM_getValue("Config.bonusDate", 0) == todayNum) { GM_log("每日额外积分今日已领取,跳过"); return; } const xpath = "/html/body/div[2]/div[2]/div/main/div/button[2]/div/div"; const findBtn = () => { let btn = null; try { btn = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue; } catch (e) {} if (btn && btn.offsetParent !== null) return btn; // 仅当按钮可见才点击 const fallbacks = ["main button", "div[class*='bonus'] button", "button[class*='reward']"]; for (const sel of fallbacks) { const els = document.querySelectorAll(sel); for (const el of els) { const txt = (el.innerText || el.textContent || "").replace(/\s/g, ""); if (/额外/.test(txt)) return el; // 仅匹配含"额外"的按钮,避免误点页面其它"积分"按钮 } } return null; }; let tries = 0; const tick = () => { const btn = findBtn(); if (btn) { btn.click(); GM_setValue("Config.bonusDate", todayNum); FuckF.log("🎉", "已点击每日额外积分按钮"); return; } if (tries++ < 40) setTimeout(tick, 1000); else FuckF.log("🟡", "未找到每日额外积分按钮(XPath 可能已变更),请反馈页面结构"); }; tick(); }; FuckF.taskPromos = async () => { FuckF.log("🔵", `活动任务检测:promos=${FuckD.tasks.promos}, times=${FuckD.promos.times}, end=${FuckD.promos.end}, date=${FuckD.promos.date}`); if (!FuckD.tasks.promos || FuckD.promos.times > 2 || FuckD.promos.end > 0 || (!GM_getValue("Config.keep", true) && FuckD.promos.date == FuckD.bing.dateNowNum)) { FuckD.promos.end++; return true; } // 锁定任务检测:识别必应 APP 专属任务并跳过 const isLockedCard = (c) => { if (!c) return false; // 已解锁(isLocked=false 且 isUnlocked=true):unlockCriteria 仅记录解锁条件(如 newLevel2),账号已达成,可完成 // 典型如 WW_Rewards_locked_level2_* 每日优惠卡;与真锁卡(isLocked=true/isUnlocked=false/unlockCriteria=rewardsApp)区分 if (c.isLocked === false && c.isUnlocked === true) return false; return c.isLocked === true || c.isUnlocked === false || (typeof c.unlockCriteria === "string" && c.unlockCriteria !== "$undefined") || /Moreactivities_RewardsApp/i.test(c.offerId || c.name || ""); }; // 跳过非当日完成任务的过滤器 const daysEn = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; const todayDayEn = daysEn[new Date().getDay()]; const skipCompletedFilter = (offerId) => { if (offerId.includes("Evergreen") && !offerId.includes(todayDayEn)) return true; const dateMatch = offerId.match(/(20\d{6})/); if (dateMatch && dateMatch[1] !== FuckD.bing.dateNowNum.toString()) return true; if (/(w[1-5]|week|month)/i.test(offerId) || /(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\d{2}/i.test(offerId)) return true; return false; }; let promosArr = []; let seenIds = new Set(); let dashPoints = 0, dashMax = 0; let earnPoints = 0, earnMax = 0; let lockedCount = 0, lockedPoints = 0; let dashHtml = "", earnHtml = ""; try { dashHtml = await FuckF.xhr({ url: "https://rewards.bing.com/dashboard" }); lastRewardHtml.dash = dashHtml; const cleanDash = dashHtml.replace(/\\"/g, '"'); const todayNum = FuckD.bing.dateNowNum; let dailySetItems = []; const dashRaw = FuckF.extractArrayAfterKey(cleanDash, "dailySetItems"); if (dashRaw) { try { dailySetItems = JSON.parse(dashRaw.replace(/\\"/g, '"')); } catch (e) { FuckF.log("🟡", `dailySetItems 解析失败,尝试兜底旧写法 🔛${e.message}`); } } if (!dailySetItems.length) { // 兜底:兼容旧版带 "discovery" 锚点的写法 const legacy = cleanDash.match(/"dailySetItems":(\[.*?\]),"discovery"/); if (legacy && legacy[1]) { try { dailySetItems = JSON.parse(legacy[1].replace(/\\"/g, '"')); } catch (e) {} } } for (const item of dailySetItems) { if (item.points === 0 || seenIds.has(item.offerId)) continue; const itemDate = FuckF.normalizeDate(item.date); if (itemDate !== null && itemDate !== todayNum) continue; // 解析不出日期则不跳过,避免误杀今日活动 seenIds.add(item.offerId); dashMax += item.points; if (item.isCompleted) { dashPoints += item.points; } else { promosArr.push({ id: item.offerId, hash: item.hash, url: "https://rewards.bing.com/dashboard", points: item.points, type: "dash" }); } } earnHtml = await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); lastRewardHtml.earn = earnHtml; const cleanEarn = earnHtml.replace(/\\"/g, '"'); // 优先结构化解析 activityCards:只有这样才能读到 isLocked / isUnlocked / unlockCriteria let earnCards = null; const earnCardsRaw = FuckF.extractArrayAfterKey(cleanEarn, "activityCards"); if (earnCardsRaw) { try { const arr = JSON.parse(earnCardsRaw.replace(/\\"/g, '"')); if (Array.isArray(arr) && arr.length) earnCards = arr; } catch (e) {} } if (earnCards) { FuckF.log("🔵", `Earn 页面结构化解析:发现 ${earnCards.length} 个活动卡片`); for (const card of earnCards) { const points = parseInt(card.points) || 0; const offerId = card.offerId || card.name || ""; const title = card.title || card.name || ""; if (points === 0 || !offerId || seenIds.has(offerId)) continue; if (isLockedCard(card)) { lockedCount++; lockedPoints += points; seenIds.add(offerId); continue; } // Quiz 任务可选跳过 if (/quiz|trivia/i.test(`${offerId} ${title}`) && !GM_getValue("Tasks.quiz", true)) { FuckF.log("⚪", `跳过 Quiz 任务:${offerId}(已关闭自动答题)`); seenIds.add(offerId); continue; } if (/quiz|trivia/i.test(`${offerId} ${title}`)) { FuckF.log("📝", `发现 Quiz 任务:${title || offerId} +${points}p`); } if (card.isCompleted && skipCompletedFilter(offerId)) continue; seenIds.add(offerId); earnMax += points; if (card.isCompleted) { earnPoints += points; } else { promosArr.push({ id: offerId, hash: card.hash || "", url: "https://rewards.bing.com/earn", points: points, type: "earn" }); } } } else if (cleanEarn.includes('"activityCards":[')) { // 兜底:正则解析 FuckF.log("🟡", "Earn 页面使用正则兜底解析"); const taskRegex = /"isCompleted":(true|false).*?"points":(\d+).*?"offerId":"([^"]+)","hash":"([^"]+)"/g; let match; while ((match = taskRegex.exec(cleanEarn)) !== null) { const isCompleted = match[1] === "true"; const points = parseInt(match[2]); const offerId = match[3]; const hash = match[4]; if (points === 0 || seenIds.has(offerId)) continue; // 正则读不到结构化字段,退回上下文扫描 + offerId 模式排除 App 锁定任务 const ctx = cleanEarn.substring(Math.max(0, match.index - 500), match.index + 500); if (/Moreactivities_RewardsApp/i.test(offerId) || /"unlockCriteria":"[^"]+"/.test(ctx) || /"isLocked":true/.test(ctx)) { lockedCount++; lockedPoints += points; seenIds.add(offerId); continue; } // Quiz 任务可选跳过 if (/quiz|trivia/i.test(offerId) && !GM_getValue("Tasks.quiz", true)) { FuckF.log("⚪", `跳过 Quiz 任务:${offerId}(已关闭自动答题)`); seenIds.add(offerId); continue; } if (/quiz|trivia/i.test(offerId)) { FuckF.log("📝", `发现 Quiz 任务:${offerId} +${points}p`); } if (isCompleted && skipCompletedFilter(offerId)) continue; seenIds.add(offerId); earnMax += points; if (isCompleted) { earnPoints += points; } else { promosArr.push({ id: offerId, hash: hash, url: "https://rewards.bing.com/earn", points: points, type: "earn" }); } } } } catch (e) { FuckF.log("🔴", `活动解析出错!🔛${e.message}`); } const todayPromosSaved = FuckD.promos.date == FuckD.bing.dateNowNum ? GM_getValue("Config.promosPoint", 0) : 0; GM_setValue("Config.promosPoint", Math.max(todayPromosSaved, dashPoints + earnPoints)); const dashTasks = promosArr.filter(i => i.type === 'dash').length; const earnTasks = promosArr.filter(i => i.type === 'earn').length; const totalNewTasks = dashTasks + earnTasks; if (totalNewTasks < 1) { FuckD.promos.end++; if (FuckD.promos.date != FuckD.bing.dateNowNum) { FuckD.promos.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); const dashReport = dashMax > 0 ? `\n📱手机端活动:${dashPoints}/${dashMax}` : ""; const earnReport = earnMax > 0 ? `\n💻电脑端活动:${earnPoints}/${earnMax}` : ""; const lockedReport = lockedCount > 0 ? `\n🔒${lockedCount} 个任务需在必应 App 完成(${lockedPoints} 分),已跳过` : ""; FuckD.bing.sendMSG = `🧩活动任务已完成!${dashReport}${earnReport}${lockedReport}`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return true; } try { const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } FuckD.promos.date = 0; const actionInfo = await FuckF.getRewardsAction([dashHtml, earnHtml]); const actionId = actionInfo.ids.reportActivity || "70babbc81d2724f60d29a95c03b3d739cba77cea92"; const taskMsgs = []; if (dashTasks > 0) taskMsgs.push(`${dashTasks}个手机端活动`); if (earnTasks > 0) taskMsgs.push(`${earnTasks}个电脑端活动`); FuckF.log("🧩", `检测到有${taskMsgs.join("、")}未完成,开始执行...`); await new Promise(resolve => setTimeout(resolve, 500)); let dashCurrent = 1; let earnCurrent = 1; let skipped = 0; for (const item of promosArr) { if (item.type === 'dash') { FuckF.log("📱", `正在执行第${dashCurrent}/${dashTasks}个手机端活动...`); dashCurrent++; } else { FuckF.log("💻", `正在执行第${earnCurrent}/${earnTasks}个电脑端活动...`); earnCurrent++; } const reqHeaders = { "content-type": "text/plain;charset=UTF-8", "next-action": actionId, "referer": item.url, // 显式覆盖 origin:扩展 service_worker 请求被浏览器自动附带 Origin: chrome-extension://, // 风控账号的 server action 对非站点同源 origin 直接抛 500(对照实验证实), // 若运行环境允许覆盖该头则可直接走通网页通道 "origin": "https://rewards.bing.com", "next-router-state-tree": FuckF.getRouterStateTree(item.type === 'dash' ? "dashboard" : "earn"), }; if (actionInfo.dpl) { reqHeaders["x-deployment-id"] = actionInfo.dpl; } const postData = JSON.stringify([ item.hash, 11, { "offerid": item.id, "isPromotional": "$undefined", "timezoneOffset": "-480" } ]); let posted = false; // 网页通道重试 3 次:500 多为服务端瞬时拒绝,404 为标识失效直接抛出由外层清缓存 for (let attempt = 1; attempt <= 3 && !posted; attempt++) { try { await FuckF.xhr({ method: "POST", url: item.url, headers: reqHeaders, data: postData, }); posted = true; } catch (e) { if (e.message.includes("404")) throw e; if (attempt === 3) { FuckF.log("🟡", `活动 ${item.id} 网页通道 3 次失败:${e.message}`); } else { await new Promise(resolve => setTimeout(resolve, FuckF.getScopeRandomNum(2000, 4000))); } } } // 网页通道失败且 token 可用时改走 dapi APP 通道(type 101 + offerid,幂等、即时记账,不做 origin 校验); // 注意 renewToken 仅在签入/阅读任务启用时执行,必须先判 token 空 if (!posted && FuckD.bing.token) { try { const region = FuckD.bing.status ? "cn" : (FuckD.bing.region || "").toLowerCase(); const dapiResp = await FuckF.reportDapiActivity({ type: 101, attributes: { offerid: item.id }, region }); if (dapiResp && FuckF.isJSON(dapiResp)) { const res = JSON.parse(dapiResp); if (res.code === 0 && res.response?.activity) { posted = true; FuckF.log("🟢", `活动 ${item.id} 已改由 APP 通道完成`); } } } catch (e) { FuckF.log("🟡", `活动 ${item.id} APP 通道兜底失败:${e.message}`); } } if (!posted) { // 双通道均失败:跳过该项继续队列,不累计积分,由下轮运行补做 skipped++; FuckF.log("🔴", `活动 ${item.id} 双通道均失败,跳过该项`); continue; } if (item.type === 'dash') { dashPoints += item.points; } else { earnPoints += item.points; } GM_setValue("Config.promosPoint", Math.max(todayPromosSaved, dashPoints + earnPoints)); if (dashCurrent - 1 + earnCurrent - 1 < totalNewTasks) { await new Promise(resolve => setTimeout(resolve, FuckF.getScopeRandomNum(2000, 4000))); } } // 复核:交卷后重拉 dashboard 核对每日活动完成状态,及早发现交卷标识失效(上游 3.6.50 合并) if (dashTasks > 0) { try { const reHtml = await FuckF.xhr({ url: "https://rewards.bing.com/dashboard" }); const reClean = reHtml.replace(/\\"/g, '"'); const reRaw = FuckF.extractArrayAfterKey(reClean, "dailySetItems"); if (reRaw) { const reItems = JSON.parse(reRaw.replace(/\\"/g, '"')); const stillOpen = reItems.filter(x => !x.isCompleted && (x.points > 0) && (FuckF.normalizeDate(x.date) === FuckD.bing.dateNowNum)).length; if (stillOpen > 0) FuckF.log("🟡", `复核发现仍有 ${stillOpen} 个每日活动未完成,交卷标识可能已失效`); } } catch (e) {} } if (skipped > 0) { // 有双通道均失败的项:不标记完成,下轮定时运行自动补做 FuckF.log("🟡", `有 ${skipped} 个活动未完成(网页与 APP 通道均失败),等待下轮重试`); FuckD.promos.times++; return false; } FuckD.promos.end++; if (FuckD.promos.date != FuckD.bing.dateNowNum) { FuckD.promos.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); const dashReport = dashMax > 0 ? `\n📱手机端活动:${dashPoints}/${dashMax}` : ""; const earnReport = earnMax > 0 ? `\n💻电脑端活动:${earnPoints}/${earnMax}` : ""; const lockedReport = lockedCount > 0 ? `\n🔒${lockedCount} 个任务需在必应 App 完成(${lockedPoints} 分),已跳过` : ""; FuckD.bing.sendMSG = `🧩活动任务已完成!${dashReport}${earnReport}${lockedReport}`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return true; } catch (e) { FuckF.log("🔴", `活动交卷出错!🔛${e.message}`); if (e.message.includes("404")) { // 标识已失效,清除缓存以便下次运行重新提取 FuckF.clearActionCache(); } } FuckD.promos.times++; return false; } // dapi APP 通道活动上报(与阅读/打卡同链路):type 101 + offerid,服务端即时记账、幂等、不做 origin 校验。 // 作为网页通道(rewards.bing.com server action)被风控账号 500 拒绝时的交卷兜底,返回原始响应字符串,异常向上抛。 FuckF.reportDapiActivity = async ({ type, attributes = {}, region }) => { return await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", "x-rewards-country": region, }, data: JSON.stringify({ "amount": 1, "country": region, "id": FuckF.getRandomUUID(), "type": type, "attributes": attributes, }), }); }; // 每日连续打卡活动(3 项共 30 分) // 2026-08-19 起新版网页端不再下发 dailySetItems 数据(正则零匹配、DOM 卸载、旧 API 全部下线), // 该活动仅在必应 APP 可见,改走 dapi APP 通道识别;若微软恢复网页端数据则自动让位给活动任务老路线。 // 打卡项结构(dapi 实测 2026-08-20):offerid 为 Gamification_DailySet_区域_日期_Child序号,type=urlreward。 // 完成方式(2026-08-21 线上实测):访问 attributes.destination(必应搜索 URL,即便携带 BTDSUOID 跟踪参数与移动 UA)不会记账; // 正确链路与阅读任务同源:POST dapi/me/activities type 101 + offerid,服务端即时记账(响应 activity.p 为到账积分)、幂等。 FuckF.taskStreak = async () => { if (!FuckD.tasks.promos || FuckD.streak.date == FuckD.bing.dateNowNum || FuckD.streak.times > 2 || FuckD.streak.end > 0) { FuckD.streak.end++; return true; } try { // 弹性双通道:网页端仍有每日任务数据时由 taskPromos 处理,本通道自动让位 const dashCache = (lastRewardHtml.dash || "").replace(/\\"/g, '"'); if (dashCache.includes('"dailySetItems":[{')) { FuckD.streak.end++; if (FuckD.streak.date != FuckD.bing.dateNowNum) { FuckD.streak.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); FuckF.log("📅", "网页端已恢复每日任务数据,打卡由活动任务通道处理"); } return true; } const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } const promos = await FuckF.getPromotions(); if (!promos) { FuckD.streak.times++; return false; } // 打卡项识别:Gamification_DailySet 前缀 + offerid 内嵌今日日期 //(精确排除 Sapphire_AppNewBonus_dailyset 新人奖励、Gamification_Sapphire_DailyCheckIn 累计签到等同名活动) const isStreakItem = (p) => /^Gamification_DailySet_/.test(p.attributes?.offerid || ""); const isToday = (p) => { const dateMatch = (p.attributes?.offerid || "").match(/(20\d{6})/); return !dateMatch || dateMatch[1] == FuckD.bing.dateNowNum; }; const isDone = (p) => { const attr = p.attributes || {}; const progress = parseInt(attr.progress) || 0; const max = parseInt(attr.max) || 0; return max > 0 ? progress >= max : false; }; const streakItems = promos.filter(p => isStreakItem(p) && isToday(p)); const pending = streakItems.filter(p => !isDone(p)); if (FuckD.streak.date != FuckD.bing.dateNowNum) { const brief = streakItems.length > 0 ? streakItems.map(p => { const attr = p.attributes || {}; return `${(attr.offerid || "?").replace(/^Gamification_DailySet_\w+_/, "")} ${attr.progress ?? "?"}/${attr.max ?? "?"}`; }).join(",") : "无"; FuckF.log("📅", `APP 通道打卡项(${streakItems.length} 项):${brief}`); } let streakPoint = 0; if (pending.length > 0) { FuckD.streak.date = 0; FuckF.log("📅", `识别到 ${pending.length} 个未完成每日打卡项,通过 APP 通道上报完成...`); const region = FuckD.bing.status ? "cn" : FuckD.bing.region.toLowerCase(); const donePoints = (items) => items.filter(isDone).reduce((s, p) => s + (parseInt(p.attributes?.max) || 0), 0); // dapi 活动上报(与阅读任务同链路,type 101 + offerid,实测服务端即时记账);返回实际上账积分,异常返回 null const reportViaDapi = async (attr) => { const result = await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", "x-rewards-country": region, }, data: JSON.stringify({ "amount": 1, "country": region, "id": FuckF.getRandomUUID(), "type": 101, "attributes": { "offerid": attr.offerid || "" }, }), }); if (result && FuckF.isJSON(result)) { const res = JSON.parse(result); if (res.code === 0 && res.response?.activity) { return parseInt(res.response.activity.p) || 0; } } return null; }; const pointBefore = donePoints(streakItems); for (let i = 0; i < pending.length; i++) { const attr = pending[i].attributes || {}; FuckF.log("📅", `正在完成打卡项 ${i + 1}/${pending.length}:${attr.title || attr.offerid}(+${parseInt(attr.max) || 0} 分)`); try { const got = await reportViaDapi(attr); if (got !== null) { streakPoint += got; } else { FuckF.log("🟡", `打卡项 ${i + 1} 上报响应无到账记录(可能已完成过)`); } } catch (e) { FuckF.log("🔴", `打卡项 ${i + 1} 上报失败!🔛${e.message}`); } if (i < pending.length - 1) { await new Promise(resolve => setTimeout(resolve, FuckF.getScopeRandomNum(3000, 6000))); } } // 复核:以服务端进度为最终依据(实测记账即时生效);复核失败时保留上报响应累计值 await new Promise(resolve => setTimeout(resolve, 5000)); const recheck = await FuckF.getPromotions(); if (recheck) { const after = recheck.filter(p => isStreakItem(p) && isToday(p)); streakPoint = Math.max(0, donePoints(after) - pointBefore); } } FuckD.streak.end++; if (FuckD.streak.date != FuckD.bing.dateNowNum) { FuckD.streak.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); GM_setValue("Config.streakPoint", streakPoint); if (streakPoint > 0) { FuckD.bing.sendMSG = `📅每日打卡任务已完成!\n✨今日打卡奖励:${streakPoint}`; } else if (pending.length > 0) { FuckD.bing.sendMSG = `📅打卡上报已执行但服务端未确认到账,请稍后在必应 APP 核对打卡进度`; } else if (streakItems.length > 0) { FuckD.bing.sendMSG = `📅每日打卡任务已完成!(APP 通道显示全部完成)`; } else { FuckD.bing.sendMSG = `📅未在 APP 通道发现每日打卡项,请前往必应 APP 手动完成(约 30 分)`; } FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return true; } catch (e) { FuckF.log("🔴", `每日打卡任务出错!🔛${e.message}`); FuckD.streak.times++; return false; } } // ===== 月度打卡任务(Punchcard / Quest)===== // earn 页面「任务」板块中的月度 punchcard 任务,含多个子任务(通常 4 个), // 每个子任务需隔 24 小时才解锁下一个,全部完成奖励 +50 积分。 // 完成方式:网页端 server action 交卷(通道A)+ dapi APP 通道 type 101 上报 offerid(通道B,与每日打卡/阅读同源)。 // // ⚠️ 轮询节流:cron 每 20 分钟触发一次,若每次都拉 earn 页 + quest 详情页 + 交卷请求, // 既浪费请求又容易被判异常。故引入按月维度的持久化状态 Config.punchcardState: // { month: 202609, logDate: 20260906, quests: { [offerId]: { done, total, complete, lastDoneAt } } } // 以下两种情况在发起任何网络请求之前直接跳过(每日最多记录一条日志): // 1) 本月 quest 全部完成(done >= total)→ 本月不再执行,跨月(month 变化)自动重置并重新识别; // 2) 刚完成过子项 / 下一子项被服务端锁定 → 处于 24h 解锁冷却中,等冷却结束再检查。 // 仅当「首次识别到新 quest」或「冷却已结束」时才真正拉取页面执行。 FuckF.PUNCHCARD_COOLDOWN_MS = 24 * 60 * 60 * 1000; FuckF.getPunchcardState = () => { let st = null; try { st = JSON.parse(GM_getValue("Config.punchcardState", "") || "") || null; } catch {} if (!st || typeof st !== "object") st = {}; if (!st.quests || typeof st.quests !== "object") st.quests = {}; st.month = Number(st.month) || 0; st.logDate = Number(st.logDate) || 0; return st; }; FuckF.savePunchcardState = (st) => GM_setValue("Config.punchcardState", JSON.stringify(st)); // 标记今日已处理(供「持续检测」关闭时的整体完成判定使用) FuckF.punchcardMarkToday = () => { if (FuckD.punchcard.date != FuckD.bing.dateNowNum) { FuckD.punchcard.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); } }; // 每日仅记录一次(push 为 true 时同时推送通知),避免定时轮询刷屏 FuckF.punchcardLogOnce = (st, msg, push = false) => { if (st.logDate == FuckD.bing.dateNowNum) return; st.logDate = FuckD.bing.dateNowNum; FuckF.savePunchcardState(st); FuckD.bing.sendMSG = msg; FuckF.log(push ? "🔵" : "📋", msg, push); if (push) FuckF.send(FuckD.wh); }; FuckF.taskPunchcard = async () => { if (!FuckD.tasks.punchcard || FuckD.punchcard.times > 2 || FuckD.punchcard.end > 0 || (!GM_getValue("Config.keep", true) && FuckD.punchcard.date == FuckD.bing.dateNowNum)) { FuckD.punchcard.end++; return true; } const now = Date.now(); const monthKey = Number(String(FuckD.bing.dateNowNum).substring(0, 6)); let st = FuckF.getPunchcardState(); // 跨月重置:上月缓存作废,重新识别本月任务 if (st.month !== monthKey) { st = { month: monthKey, logDate: 0, quests: {} }; } // ---- 零请求快速跳过:本月已全部完成 / 子项处于 24h 解锁冷却 ---- const cachedIds = Object.keys(st.quests); if (cachedIds.length > 0) { const briefCached = cachedIds.map(id => `${st.quests[id].done || 0}/${st.quests[id].total || 0}`).join(","); const unfinished = cachedIds.filter(id => !st.quests[id].complete); if (unfinished.length === 0) { FuckD.punchcard.end++; FuckF.punchcardMarkToday(); FuckF.punchcardLogOnce(st, `📋本月月度打卡任务已全部完成(${briefCached}),本月不再重复执行`, true); return true; } // 未完成项的剩余冷却时间;全部仍在冷却中则本轮不发起任何请求 const waits = unfinished.map(id => FuckF.PUNCHCARD_COOLDOWN_MS - (now - (Number(st.quests[id].lastDoneAt) || 0))); if (waits.every(w => w > 0)) { const hours = Math.ceil(Math.min(...waits) / (60 * 60 * 1000)); FuckD.punchcard.end++; FuckF.punchcardMarkToday(); FuckF.punchcardLogOnce(st, `📋月度打卡任务下一子项未解锁,约 ${hours} 小时后重试(进度:${briefCached})`); return true; } } try { if (!FuckD.bing.token) { FuckF.log("📋", "月度任务需 Token,当前不可用,跳过"); FuckD.punchcard.end++; return true; } const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } const region = FuckD.bing.status ? "cn" : FuckD.bing.region.toLowerCase(); // 1) 从 earn 页面提取活跃 quest 的 offerId 列表 const earnHtml = await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); if (!earnHtml) { FuckD.punchcard.times++; return false; } const cleanEarn = earnHtml.replace(/\\"/g, '"'); // quest 卡片结构:"offerId":"ENWW_pcparent_...punchcard" + href="/earn/quest/..." const questRegex = /"offerId":"([^"]*punchcard[^"]*)"/g; const questIds = []; let qm; while ((qm = questRegex.exec(cleanEarn)) !== null) { if (!questIds.includes(qm[1])) questIds.push(qm[1]); } if (questIds.length === 0) { FuckD.punchcard.end++; FuckF.punchcardMarkToday(); FuckF.punchcardLogOnce(st, "📋未发现月度打卡任务(可能本月已完成或尚未发布)"); return true; } st.month = monthKey; FuckF.savePunchcardState(st); FuckF.log("📋", `发现 ${questIds.length} 个月度打卡任务:${questIds.join(", ")}`); // 2) 逐个 quest 处理(已完成或仍在冷却中的 quest 直接跳过,不拉详情页) let totalPoints = 0; let anyProgress = false; const reports = []; for (const questOfferId of questIds) { const cached = st.quests[questOfferId]; const cachedLast = cached ? Number(cached.lastDoneAt) || 0 : 0; if (cached && (cached.complete || (cachedLast > 0 && (now - cachedLast) < FuckF.PUNCHCARD_COOLDOWN_MS))) { // 已完成,或上次完成后仍在 24h 解锁冷却内(子项隔日才开放) reports.push(`${cached.done || 0}/${cached.total || 0}`); continue; } // 3) 抓取 quest 详情页,解析子任务 const questUrl = `https://rewards.bing.com/earn/quest/${encodeURIComponent(questOfferId)}`; const questHtml = await FuckF.xhr({ url: questUrl }); if (!questHtml) { FuckF.log("🟡", `月度任务 ${questOfferId} 详情页获取失败`); continue; } const cleanQuest = questHtml.replace(/\\"/g, '"'); // 提取进度信息(如 "2/4 个任务") const progressMatch = cleanQuest.match(/(\d+)\s*\/\s*(\d+)\s*个任务/); const doneCount = progressMatch ? parseInt(progressMatch[1]) : 0; const totalCount = progressMatch ? parseInt(progressMatch[2]) : 0; // 提取子任务数据 // quest 详情页子任务结构(与 earn 页 activityCards 不同): // {"href":"...","offerId":"ENWW_pcchildN_urlreward_...","hash":"...","isCompleted":bool,"isLocked":bool,"isDisabled":bool} // 注意:无 points 字段,用 href 而非 destination,有 isDisabled 字段 const subTaskRegex = /"offerId":"(ENWW_pcchild\d+_urlreward[^"]*)"\s*,\s*"hash":"([^"]+)"\s*,\s*"isCompleted":(true|false)\s*,\s*"isLocked":(true|false)\s*,\s*"isDisabled":(true|false)/g; const subTasks = []; const seenSubIds = new Set(); let sm; while ((sm = subTaskRegex.exec(cleanQuest)) !== null) { const offerId = sm[1]; const hash = sm[2]; const isCompleted = sm[3] === "true"; const isLocked = sm[4] === "true"; const isDisabled = sm[5] === "true"; if (seenSubIds.has(offerId)) continue; seenSubIds.add(offerId); subTasks.push({ offerId, hash, isCompleted, isLocked, isDisabled }); } if (subTasks.length === 0 && totalCount === 0) { FuckF.log("🟡", `月度任务 ${questOfferId} 未解析到子任务数据,页面结构可能已变更`); continue; } const effectiveTotal = totalCount || subTasks.length; const effectiveDone = doneCount || subTasks.filter(s => s.isCompleted).length; // 以服务端进度为准写入缓存(无论本轮是否执行动作,下轮都可据此快速跳过) const q = { done: effectiveDone, total: effectiveTotal, complete: effectiveDone >= effectiveTotal, lastDoneAt: cachedLast, }; st.quests[questOfferId] = q; FuckF.savePunchcardState(st); FuckF.log("📋", `月度任务进度:${effectiveDone}/${effectiveTotal}(${questOfferId})`); if (q.complete) { reports.push(`${q.done}/${q.total}`); continue; } // 4) 完成未完成的子任务(每次只做一个,服务端有 24h 冷却) // 优先选择已解锁(!isLocked && !isDisabled)且未完成的任务 const pending = subTasks.filter(s => !s.isCompleted && !s.isLocked && !s.isDisabled); if (pending.length === 0) { // 子项被服务端锁定(隔日才解锁):起算冷却,24 小时内不再重复拉取 if (!q.lastDoneAt) { q.lastDoneAt = now; FuckF.savePunchcardState(st); } const hours = Math.ceil((FuckF.PUNCHCARD_COOLDOWN_MS - (now - q.lastDoneAt)) / (60 * 60 * 1000)); reports.push(`${q.done}/${q.total}`); FuckF.log("📋", `下一子项未解锁(服务端锁定),约 ${hours} 小时后重试(进度:${q.done}/${q.total})`); continue; } const task = pending[0]; // 只完成第一个可执行的 anyProgress = true; FuckF.log("📋", `正在完成月度任务子项:${task.offerId}`); let posted = false; // 通道A:网页端 server action(与 taskPromos 同源) try { const actionInfo = await FuckF.getRewardsAction([cleanQuest]); const actionId = actionInfo.ids.reportActivity || "70babbc81d2724f60d29a95c03b3d739cba77cea92"; const reqHeaders = { "content-type": "text/plain;charset=UTF-8", "next-action": actionId, "referer": questUrl, "origin": "https://rewards.bing.com", "next-router-state-tree": FuckF.getRouterStateTree("earn"), }; if (actionInfo.dpl) reqHeaders["x-deployment-id"] = actionInfo.dpl; const postData = JSON.stringify([ task.hash, 11, { "offerid": task.offerId, "isPromotional": "$undefined", "timezoneOffset": "-480" } ]); for (let attempt = 1; attempt <= 3 && !posted; attempt++) { try { await FuckF.xhr({ method: "POST", url: questUrl, headers: reqHeaders, data: postData }); posted = true; FuckF.log("📋", `子项通过网页通道完成`); } catch (e) { if (e.message.includes("404")) throw e; if (attempt === 3) FuckF.log("🟡", `子项网页通道 3 次失败:${e.message}`); else await new Promise(resolve => setTimeout(resolve, FuckF.getScopeRandomNum(2000, 4000))); } } } catch (e) { FuckF.log("🟡", `子项网页通道异常:${e.message}`); } // 通道B:dapi APP 通道兜底(网页通道失败且 token 可用时) if (!posted && FuckD.bing.token) { try { const dapiResult = await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.m, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "SAAndroid/31.4.2110003555", "x-rewards-ismobile": "true", "x-rewards-country": region, }, data: JSON.stringify({ "amount": 1, "country": region, "id": FuckF.getRandomUUID(), "type": 101, "attributes": { "offerid": task.offerId }, }), }); if (dapiResult && FuckF.isJSON(dapiResult)) { const res = JSON.parse(dapiResult); if (res.code === 0 && res.response?.activity) { posted = true; const got = parseInt(res.response.activity.p) || 0; totalPoints += got; FuckF.log("📋", `子项通过 APP 通道完成!获得 ${got} 积分`); } else { FuckF.log("🟡", `子项 APP 通道响应异常:${dapiResult.substring(0, 200)}`); } } } catch (e) { FuckF.log("🔴", `子项 APP 通道兜底失败!🔛${e.message}`); } } if (!posted) { FuckF.log("🔴", `子项双通道均失败,等待下轮重试`); FuckD.punchcard.times++; return false; } // 更新缓存:进度 +1,并开始 24h 冷却(下轮据此零请求跳过) q.done = Math.min(q.total, q.done + 1); q.complete = q.done >= q.total; q.lastDoneAt = Date.now(); st.quests[questOfferId] = q; FuckF.savePunchcardState(st); reports.push(`${q.done}/${q.total}`); } FuckD.punchcard.end++; FuckF.punchcardMarkToday(); const brief = reports.join(",") || "无"; const allComplete = questIds.length > 0 && questIds.every(id => st.quests[id] && st.quests[id].complete); let msg; if (anyProgress) { msg = `📋月度打卡任务已推进!\n✨本次完成 1 个子项(进度:${brief})`; if (totalPoints > 0) msg += `\n🎁本次到账:${totalPoints} 积分`; } else if (allComplete) { msg = `📋月度打卡任务本月已全部完成(${brief})`; } else { msg = `📋月度打卡任务进度:${brief}`; } FuckF.punchcardLogOnce(st, msg, true); return true; } catch (e) { FuckF.log("📋", `月度打卡任务出错!🔛${e.message}`); FuckD.punchcard.times++; return false; } } // ===== Edge 浏览连签(Browsing Streak on Edge)===== // 微软新增任务:每天在 Edge 累计浏览满 30 分钟即记账,连续 7 天有额外积分与印章(7 天奖励 5/10/20/30/40/80/120)。 // 机制(参考成熟实现 + dapi edge 通道):服务端按「每次上报覆盖 report_per_minutes 分钟」累计进度, // 需分次上报(默认每 5 分钟 1 次、共 6 次),两次上报间隔小于 report_per_minutes 会被判 isDuplicate 不记账, // 累计满 30 分钟后 promotion.attributes.complete 变为 true。 // ⚠️ ScriptCat service worker 生命周期短,无法在一次运行内保持 30 分钟循环,故改为「每次 cron 触发做 1 次上报」模型: // 通过 Config.edge={date,count,last,complete,activated,notified,extra} GM 持久化跨运行续传, // cron 每 20 分钟触发一次,远大于 5 分钟去重窗口,不会被判 isDuplicate,约 2 小时内完成全部 6 次上报。 // 仅服务端确认记账(code=0 且非 isDuplicate)才 count++;上报满 reportCount 次但服务端未确认 complete 时, // 重置计数继续上报(最多 extra 3 轮),避免"次数满了但任务实际没完成"。 FuckF.EDGE_PROMO_NAME = "edge_browsing_streak_flight"; FuckF.EDGE_ACTIVATION_OFFER = "edge_flight_1_ww_treatment_eligible"; FuckF.EDGE_TARGET_MINUTES = 30; // 上报满 reportCount 次但服务端未确认完成时的最大重试轮数(每轮重新上报 reportCount 次) FuckF.EDGE_MAX_EXTRA_ROUNDS = 3; // 拉取 dapi edge 通道资料(含浏览连签 promotion),与 getPromotions 同域不同 channel FuckF.getEdgeProfile = async (region) => { try { const result = await FuckF.xhr({ url: "https://prod.rewardsplatform.microsoft.com/dapi/me?channel=edge&options=Profile,Promotions", headers: { "accept": "*/*", "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.pc, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "EdgeDesktop", "x-rewards-partnerid": "EdgeHub", "x-rewards-country": region, "x-rewards-language": region === "cn" ? "zh-CN" : "en-US", }, }); if (result && FuckF.isJSON(result)) { const res = JSON.parse(result); if (res.code === 0) return res.response || {}; } } catch (e) { FuckF.log("🔴", `Edge 通道资料获取出错!🔛${e.message}`); } return false; }; FuckF.findEdgePromotion = (profile) => { const list = (profile && profile.promotions) || []; return list.find(p => p && p.name === FuckF.EDGE_PROMO_NAME) || null; }; // Edge 浏览时长上报:dapi/me/activities,type 取 promotion.report_offer_type(默认 714),attributes.offerid 为上报标识 // 返回解析后的响应对象(含 response.isDuplicate);异常向上抛由调用方处理 FuckF.reportEdgeActivity = async ({ offerid, type, region }) => { const result = await FuckF.xhr({ method: "POST", url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities", headers: { "accept": "*/*", "content-type": "application/json; charset=UTF-8", "user-agent": FuckD.ua.pc, "authorization": `Bearer ${FuckD.bing.token}`, "x-rewards-appid": "EdgeDesktop", "x-rewards-partnerid": "EdgeHub", "x-rewards-country": region, "x-rewards-language": region === "cn" ? "zh-CN" : "en-US", }, data: JSON.stringify({ amount: 1, attributes: { offerid }, request_user_info: true, type, }), }); if (result && FuckF.isJSON(result)) return JSON.parse(result); return null; }; // 一次性激活(多数账号已由 Edge 自动激活,promotion 直接存在则不会走到这里): // 从网页端 earn 页 React 数据里取 edge 连签的 activationOfferId/activationHash/activationActivityType, // 复用活动交卷同款 server action(reportActivity)提交激活。返回 activated|already|absent|failed FuckF.activateEdgeBrowsing = async () => { try { let html = lastRewardHtml.earn || ""; if (!html || !html.includes("edge_flight")) { html = (await FuckF.xhr({ url: "https://rewards.bing.com/earn?section=streaks" })) || ""; } const clean = html.replace(/\\"/g, '"'); const idx = clean.indexOf('"activationOfferId":"' + FuckF.EDGE_ACTIVATION_OFFER); if (idx < 0) { // 网页端未见 edge 连签锚点:有 edge partner 说明结构变化(failed),否则账号未开通(absent) return /"partner":"edge"/.test(clean) ? "failed" : "absent"; } const win = clean.substring(Math.max(0, idx - 600), idx + 500); if (/"isEnabled":true/.test(win)) return "already"; const offerM = win.match(/"activationOfferId":"([^"]+)"/); const hashM = win.match(/"activationHash":"([^"]+)"/); const typeM = win.match(/"activationActivityType":(\d+)/); const offerId = offerM ? offerM[1] : FuckF.EDGE_ACTIVATION_OFFER; const hash = hashM ? hashM[1] : ""; const actType = typeM ? parseInt(typeM[1]) : 714; if (!hash || hash === "$undefined") { FuckF.log("🟡", "Edge 浏览连签:未取到激活 hash,请先在 Edge 浏览器打开一次活动页以激活"); return "failed"; } const actionInfo = await FuckF.getRewardsAction([html]); const actionId = actionInfo.ids.reportActivity || "70babbc81d2724f60d29a95c03b3d739cba77cea92"; const headers = { "content-type": "text/plain;charset=UTF-8", "next-action": actionId, "referer": "https://rewards.bing.com/earn", "origin": "https://rewards.bing.com", "next-router-state-tree": FuckF.getRouterStateTree("earn"), }; if (actionInfo.dpl) headers["x-deployment-id"] = actionInfo.dpl; await FuckF.xhr({ method: "POST", url: "https://rewards.bing.com/earn", headers, data: JSON.stringify([hash, actType, { offerid: offerId, isPromotional: "$undefined", timezoneOffset: "-480" }]), }); FuckF.log("🟢", "Edge 浏览连签:已提交激活请求"); return "activated"; } catch (e) { FuckF.log("🟡", `Edge 浏览连签:激活失败 🔛${e.message}`); return "failed"; } }; FuckF.edgeNotifyDone = (st, save) => { if (st.notified) return; st.notified = true; save(); FuckD.bing.sendMSG = "🌐Edge 浏览连签任务已完成!(今日 30 分钟浏览已记账)"; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); }; let edgeBrowsingRunning = false; FuckF.taskEdgeBrowsing = async () => { if (!FuckD.tasks.edge || edgeBrowsingRunning) return; if (!FuckD.bing.token) { FuckF.log("🟡", "Edge 浏览连签:暂无可用 Token(需先完成授权码配置),本次跳过"); return; } edgeBrowsingRunning = true; const sleep = (ms) => new Promise(r => setTimeout(r, ms)); try { const region = FuckD.bing.status ? "cn" : (FuckD.bing.region || "").toLowerCase(); const today = FuckD.bing.dateNowNum; let st = GM_getValue("Config.edge", false); if (!st || st.date != today) { st = { date: today, count: 0, last: 0, complete: false, activated: false, notified: false, extra: 0 }; } const save = () => GM_setValue("Config.edge", st); // 不在此处提前返回:需先向服务端核实,避免切换账号后继承上一账号的"已完成"标记而跳过 let profile = await FuckF.getEdgeProfile(region); let promo = profile && FuckF.findEdgePromotion(profile); // promotion 缺失:尝试网页通道一次性激活(每天仅试一次) if (!promo && !st.activated) { st.activated = true; save(); const act = await FuckF.activateEdgeBrowsing(); if (act === "absent") { FuckF.log("🟡", "Edge 浏览连签:账号未开通该活动,跳过"); return; } if (act === "activated" || act === "already") { await sleep(FuckF.getScopeRandomNum(3000, 6000)); profile = await FuckF.getEdgeProfile(region); promo = profile && FuckF.findEdgePromotion(profile); } } if (!promo) { FuckF.log("🟡", "Edge 浏览连签:edge 通道未发现该活动,可能需先在 Edge 浏览器打开一次活动页激活"); return; } const attr = promo.attributes || {}; if (String(attr.complete || "").toLowerCase() === "true") { st.complete = true; save(); FuckF.edgeNotifyDone(st, save); return; } // 服务端未确认完成但本地有完成标记(典型场景:切换账号后继承了上一账号的状态):清除后继续 if (st.complete) { st.complete = false; st.count = 0; st.last = 0; st.extra = 0; save(); FuckF.log("🟡", "Edge 浏览连签:服务端未确认完成,已清除本地完成标记,重新开始上报"); } const offerid = String(attr.offerid || "").trim(); const typeRaw = String(attr.report_offer_type || "").trim(); const type = /^\d+$/.test(typeRaw) ? typeRaw : "714"; let intervalMin = Number(attr.report_per_minutes); if (!Number.isFinite(intervalMin) || intervalMin < 1 || intervalMin > 30) intervalMin = 5; if (!offerid) { FuckF.log("🟡", "Edge 浏览连签:promotion 缺少 offerid,无法上报"); return; } const reportCount = Math.ceil(FuckF.EDGE_TARGET_MINUTES / intervalMin); const intervalMs = intervalMin * 60000; // 跨运行续传:读取持久化状态,检查是否到了下一次上报时间 const fresh = GM_getValue("Config.edge", st); if (fresh && fresh.date == today) { st = Object.assign({}, st, fresh); save(); } if (st.complete) { save(); return; } // 首次(count=0)立即上报;后续须距上次上报 ≥ intervalMs 才触发(cron 20min >> 5min 去重窗口,不会被判重) const elapsed = Date.now() - (st.last || 0); if (st.count > 0 && elapsed < intervalMs) { FuckF.log("🌐", `Edge 浏览连签:距上次上报不足 ${intervalMin} 分钟,下次 cron 再试(进度 ${st.count}/${reportCount})`); return; } // 次数已满:复核服务端完成状态;未确认完成则重置计数继续上报(防止"次数满了但任务实际没完成") if (st.count >= reportCount) { const p3 = await FuckF.getEdgeProfile(region); const pr3 = p3 && FuckF.findEdgePromotion(p3); if (pr3 && String((pr3.attributes || {}).complete || "").toLowerCase() === "true") { st.complete = true; save(); FuckF.edgeNotifyDone(st, save); } else { // 已满 reportCount 次但服务端未确认:说明期间有上报未真正记账,重置计数按间隔继续上报 if ((st.extra || 0) >= FuckF.EDGE_MAX_EXTRA_ROUNDS) { FuckF.log("🟡", `Edge 浏览连签:已重试 ${st.extra} 轮仍未确认完成,今日停止上报,等待次日重置`); } else { st.extra = (st.extra || 0) + 1; st.count = 0; st.last = 0; save(); FuckF.log("🟡", `Edge 浏览连签:已上报满 ${reportCount} 次但服务端未确认完成,重置计数继续上报(第 ${st.extra}/${FuckF.EDGE_MAX_EXTRA_ROUNDS} 轮重试)`); } } return; } FuckF.log("🌐", `Edge 浏览连签:目标 ${FuckF.EDGE_TARGET_MINUTES} 分钟,每 ${intervalMin} 分钟上报 1 次,共 ${reportCount} 次;当前进度 ${st.count}/${reportCount},本次执行上报`); let dup = false; try { const res = await FuckF.reportEdgeActivity({ offerid, type, region }); if (res && res.code === 0) { dup = res.response && res.response.isDuplicate === true; } else { // 响应异常(code 非 0 / 解析失败):不计入上报次数,留待下一轮 cron 重试 FuckF.log("🟡", `Edge 浏览连签:上报未成功 code=${res && res.code != null ? res.code : "?"},本次不计入次数,下一轮重试`); return; } } catch (e) { FuckF.log("🔴", `Edge 浏览连签:上报失败!🔛${e.message}(不计入次数,下一轮重试)`); return; } st.last = Date.now(); if (!dup) st.count++; save(); FuckF.log("🌐", `Edge 浏览连签:第 ${st.count}/${reportCount} 次上报${dup ? "(服务端判重,未记账)" : "已记账"}`); // 上报后复核服务端完成状态(complete 为最终依据) await sleep(FuckF.getScopeRandomNum(2000, 4000)); const p2 = await FuckF.getEdgeProfile(region); const pr2 = p2 && FuckF.findEdgePromotion(p2); if (pr2 && String((pr2.attributes || {}).complete || "").toLowerCase() === "true") { st.complete = true; save(); } if (st.complete) { FuckF.edgeNotifyDone(st, save); } else if (st.count >= reportCount) { // 上报次数已满但服务端未确认完成:下一轮 cron 会重置计数继续上报,直至确认 FuckF.log("🟡", `Edge 浏览连签:已上报 ${st.count}/${reportCount} 次,等待服务端确认完成(下一轮复核,未确认将继续上报)`); } } catch (e) { FuckF.log("🔴", `Edge 浏览连签任务出错!🔛${e.message}`); } finally { edgeBrowsingRunning = false; } }; // 自动领取待领积分(搜索打卡等新版奖励会进入待领池,需手动点击领取) // 领取接口幂等,无可领积分时不执行任何领取动作,不影响主流程 // 新版(dpl=20260818 起)待领数据在 dashboard 页,earn 页仅作兑底 let claimRunning = false; let lastRewardHtml = { dash: "", earn: "" }; FuckF.taskClaim = async (pages) => { if (claimRunning) return; if (!FuckD.tasks.promos && !FuckD.tasks.search) return; claimRunning = true; try { for (let attempt = 1; attempt <= 2; attempt++) { try { // 先检查待领池(dashboard 优先),没有可领积分则不执行后续领取逻辑 const useCached = attempt === 1 && pages; const dashHtml = useCached && pages.dash ? pages.dash : await FuckF.xhr({ url: "https://rewards.bing.com/dashboard" }); let clean = dashHtml.replace(/\\"/g, '"'); let claimMatch = clean.match(/"pointClaim":(null|\{|\[)/); if (!claimMatch) { const earnHtml = useCached && pages.earn ? pages.earn : await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); clean = earnHtml.replace(/\\"/g, '"'); claimMatch = clean.match(/"pointClaim":(null|\{|\[)/); } if (!claimMatch || claimMatch[1] === "null") { // 待领池为空,无需领取 return; } const before = parseInt(clean.match(/"balance":(\d+)/)?.[1] || "0"); const actionInfo = await FuckF.getRewardsAction(); // 动态提取优先,已知标识兜底(实测跨部署有效) const claimId = actionInfo.ids.reportClaimAllPoints || "00491296f1d668ad46b65342c95cb9d72a62c1fa9d"; const resp = await FuckF.xhr({ method: "POST", url: "https://rewards.bing.com/earn", headers: { "content-type": "text/plain;charset=UTF-8", "next-action": claimId, "referer": "https://rewards.bing.com/earn", // 显式覆盖 origin(同交卷):风控账号的 server action 对非站点同源 origin 抛 500 "origin": "https://rewards.bing.com", "next-router-state-tree": FuckF.getRouterStateTree("earn"), }, data: JSON.stringify([]), }); const respClean = (resp || "").trim(); if (respClean.length > 0 && !respClean.startsWith("Error") && !respClean.startsWith("{")) { const afterHtml = await FuckF.xhr({ url: "https://rewards.bing.com/earn" }); const after = parseInt(afterHtml.replace(/\\"/g, '"').match(/"balance":(\d+)/)?.[1] || "0"); if (before > 0 && after > before) { FuckF.log("✨", `已自动领取 ${after - before} 积分(当前余额:${after})`); } else { FuckF.log("✨", "积分领取请求已成功"); } } else { FuckF.log("🟡", "积分领取响应异常,可能无可领积分"); } return; } catch (e) { FuckF.log("🔴", `积分领取出错!🔛${e.message}`); if (attempt < 2) { if (e.message.includes("404")) { // 标识已失效,清除缓存重新提取后重试一次 FuckF.clearActionCache(); } else { // 5xx 等其余错误延时重试一次(领取接口幂等,重试安全) await new Promise(resolve => setTimeout(resolve, 3210)); } continue; } return; } } } catch (e) { FuckF.log("🔴", `积分领取出错!🔛${e.message}`); } finally { claimRunning = false; } } FuckF.getQueryWord = async () => { const keywords = ["天气预报", "今日新闻", "体育赛事", "股票行情", "电影推荐", "科技资讯", "美食食谱", "旅游攻略", "历史上的今天", "健康常识"]; const baseWord = keywords[FuckF.getRandomNum(keywords.length)]; const randomSuffix = Math.random().toString(36).slice(2, 6); let sentence = `${baseWord} ${randomSuffix}`; if (FuckD.api.mode != "offline") { if (FuckD.search.word.index < 1 || FuckD.search.word.list.length < 1) { const apiHot = FuckF.getRandomApiHot() try { const result = await FuckF.xhr({ url: FuckD.api.url + apiHot }) if (result && FuckF.isJSON(result)) { const res = JSON.parse(result) if (res.code == 200 && res.data && res.data.length > 0) { FuckD.search.word.index = 1 for (let i = 0; i < res.data.length; i++) { FuckD.search.word.list.push(res.data[i].title) } FuckD.search.word.list = FuckF.getRandomArr(FuckD.search.word.list) sentence = FuckD.search.word.list[FuckD.search.word.index] sentence = FuckF.getRandomSubstring(sentence) return sentence } } } catch (e) { FuckF.log("🔴", `搜索词获取出错!🔛${e.message}`) } } else { FuckD.search.word.index++ if (FuckD.search.word.index > FuckD.search.word.list.length - 1) { FuckD.search.word.index = 0 } sentence = FuckD.search.word.list[FuckD.search.word.index] sentence = FuckF.getRandomSubstring(sentence) return sentence } FuckF.log("🟡", "搜索词接口异常,已临时使用随机搜索词") } return sentence } FuckF.taskSearch = async () => { if (!FuckD.tasks.search || FuckD.search.end > 0 || (!GM_getValue("Config.keep", true) && FuckD.search.date == FuckD.bing.dateNowNum)) { FuckD.search.end++; return true; } if (FuckD.search.index === 0) { const dashboard = await FuckF.getRewardsInfo(); if (!dashboard) { return false; } const searchInfo = dashboard.userStatus?.counters || {}; FuckD.search.pc.progress = searchInfo.pcSearch?.[0]?.pointProgress || 0; let pcMax = searchInfo.pcSearch?.[0]?.pointProgressMax || 0; if (pcMax === 0) pcMax = 60; FuckD.search.pc.max = pcMax; FuckD.search.m.progress = searchInfo.mobileSearch?.[0]?.pointProgress || 0; let mMax = searchInfo.mobileSearch?.[0]?.pointProgressMax || 0; if (mMax === 0) mMax = 0; FuckD.search.m.max = mMax; const currentTotalSearch = FuckD.search.pc.progress + FuckD.search.m.progress; const lastTotalSearch = GM_getValue("Config.lastSearchProgress", -1); let restrictedTimes = GM_getValue("Config.restrictedTimes", 0); if (lastTotalSearch !== -1) { if (currentTotalSearch === lastTotalSearch && currentTotalSearch < (pcMax + mMax)) { restrictedTimes++; } else { restrictedTimes = 0; } } GM_setValue("Config.lastSearchProgress", currentTotalSearch); GM_setValue("Config.restrictedTimes", restrictedTimes); if (restrictedTimes >= 2) { FuckD.search.end++; GM_setValue("Config.lastSearchProgress", -1); GM_setValue("Config.restrictedTimes", 0); const pcReport = FuckD.search.pc.max > 0 ? `\n💻电脑端搜索:${FuckD.search.pc.progress}/${FuckD.search.pc.max}` : ""; const mReport = FuckD.search.m.max > 0 ? `\n📱手机端搜索:${FuckD.search.m.progress}/${FuckD.search.m.max}` : ""; FuckD.bing.sendMSG = `⚠️积分收入受限或账号异常,已中断今日搜索!${pcReport}${mReport}`; FuckF.log("🔴", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); return true; } } if (FuckD.search.pc.progress >= FuckD.search.pc.max && FuckD.search.m.progress >= FuckD.search.m.max) { await new Promise(resolve => setTimeout(resolve, 3210)); const dashboard = await FuckF.getRewardsInfo(); if (dashboard) { const searchInfo = dashboard.userStatus?.counters || {}; const realPcPro = searchInfo.pcSearch?.[0]?.pointProgress || 0; const realMPro = searchInfo.mobileSearch?.[0]?.pointProgress || 0; if (realPcPro >= FuckD.search.pc.max && realMPro >= FuckD.search.m.max) { FuckD.search.end++; if (FuckD.search.date != FuckD.bing.dateNowNum) { FuckD.search.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); GM_setValue("Config.lastSearchProgress", -1); GM_setValue("Config.restrictedTimes", 0); const pcReport = FuckD.search.pc.max > 0 ? `\n💻电脑端搜索:${realPcPro}/${FuckD.search.pc.max}` : ""; const mReport = FuckD.search.m.max > 0 ? `\n📱手机端搜索:${realMPro}/${FuckD.search.m.max}` : ""; FuckD.bing.sendMSG = `🔍搜索任务已完成!${pcReport}${mReport}`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return true; } else { FuckD.search.pc.progress = realPcPro; FuckD.search.m.progress = realMPro; FuckF.log("🔍", `检测到搜索数据延迟或漏分,开始补搜...`); } } else { FuckD.search.end++; return true; } } if (FuckD.search.index >= FuckD.search.limit) { FuckD.search.end++; const pcReport = FuckD.search.pc.max > 0 ? `\n💻电脑端搜索:${FuckD.search.pc.progress}/${FuckD.search.pc.max}` : ""; const mReport = FuckD.search.m.max > 0 ? `\n📱手机端搜索:${FuckD.search.m.progress}/${FuckD.search.m.max}` : ""; FuckD.bing.sendMSG = `本轮运行正常,共搜索 ${FuckD.search.index} 次!${pcReport}${mReport}`; FuckF.log("🔍", FuckD.bing.sendMSG); // 每轮搜索完成推送,最后不到随机次数,按所有搜索任务完成了推送一次 if (!GM_getValue("Notice.allpush", true)) { FuckF.send(FuckD.wh); } return true; } FuckD.search.date = 0; let query, params, pcorm, headers, regionMKT = ""; if (FuckD.search.pc.progress < FuckD.search.pc.max || FuckD.search.m.progress < FuckD.search.m.max) { pcorm = Math.random() > 0.6 ? false : true; if (FuckD.search.pc.progress >= FuckD.search.pc.max) pcorm = false; if (FuckD.search.m.progress >= FuckD.search.m.max) pcorm = true; } const keyword = await FuckF.getQueryWord(); try { const envStatus = await FuckF.mainlandCheck(); if (envStatus === 1) { FuckD.bing.code = -1; return true; } GM_cookie("delete", { url: "https://bing.com", name: "_EDGE_S" }); GM_cookie("delete", { url: "https://bing.com", name: "_Rwho" }); GM_cookie("delete", { url: "https://bing.com", name: "_RwBf" }); if (FuckD.bing.status) regionMKT = "&mkt=zh-CN"; params = `q=${encodeURIComponent(keyword)}&form=QBLH${regionMKT}`; query = `https://${FuckD.bing.host}/search?${params}`; if (pcorm) { FuckD.search.device = "Desktop"; headers = { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "user-agent": FuckD.ua.pc, "cookie": `_Rwho=u=d&ts=${FuckD.bing.dateNowhyphen}`, }; } else { FuckD.search.device = "Mobile"; headers = { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "user-agent": FuckD.ua.m, "cookie": `_Rwho=u=m&ts=${FuckD.bing.dateNowhyphen}`, }; } const result = await FuckF.xhr({ url: query, headers: { ...headers, "referer": `https://${FuckD.bing.host}/?form=QBLH`, } }); if (result) { const res = result.replace(/\s/g, ""); const data0 = res.match(/,IG:"([^"]+)"/); const guid = data0 ? data0[1] : FuckF.getRandomUUID(true); const data = res.match(/class="b_algo(.*?)href="(.*?)"h="ID=(.*?)">(.*?)<\/h2/); const ncheader = `https://${FuckD.bing.host}/rewardsapp/ncheader?ver=88888888&IID=SERP.5047&IG=${guid}&ajaxreq=1`; const report = `https://${FuckD.bing.host}/rewardsapp/reportActivity?IG=${guid}&IID=SERP.5047&${params}&ajaxreq=1`; headers = { ...headers, "referer": query }; await FuckF.xhr({ method: "POST", url: ncheader, headers: headers, data: "wb=1%3bi%3d1%3bv%3d1" }); await FuckF.xhr({ method: "POST", url: report, headers: headers, data: `url=${encodeURIComponent(query)}&V=web` }); if (data) { const click = `https://${FuckD.bing.host}/fd/ls/GLinkPingPost.aspx?IG=${guid}&ID=${data[3]}&url=${data[2]}`; await FuckF.xhr({ url: click, headers: headers }); } if (pcorm) { FuckD.search.pc.progress += 3; if (FuckD.search.pc.progress > FuckD.search.pc.max) FuckD.search.pc.progress = FuckD.search.pc.max; } else { FuckD.search.m.progress += 3; if (FuckD.search.m.progress > FuckD.search.m.max) FuckD.search.m.progress = FuckD.search.m.max; } } } catch (e) { FuckF.log("🔴", `搜索任务出错!🔛${e.message}`); } FuckD.search.index++; return false; } FuckF.mainlandCheck = async () => { try { const hash = ["g", "e", "o", "i", "s", "a", "m"].join(""); if (!GM_info.script.header.includes(hash)) { FuckD.bing.mainland = -1; return 1; } const result = await FuckF.xhr({ url: `https://${FuckD.bing.host}/`, headers: { "cookie": FuckD.cookie.mkt } }); if (result) { const res = result.replace(/\s/g, ""); const data = res.match(/Region:"(.*?)"(.*?)RevIpCC:"(.*?)"/); if (data) { const ipcc = data[3].toUpperCase(); FuckD.bing.region = ipcc; if (FuckD.bing.status && ipcc != "CN") { try { const qryResult = await FuckF.xhr({ url: "https://disp-qryapi.3g.qq.com/v1/dispatch", headers: { "referer": "https://3g.qq.com/" } }); if (qryResult && FuckF.isJSON(qryResult)) { const resJSON = JSON.parse(qryResult); FuckD.bing.ip = (resJSON.code == 0 && resJSON.extra && resJSON.extra.ip) ? resJSON.extra.ip : ""; let rawInfo = (resJSON.code == 0 && resJSON.ipInfo) ? String(resJSON.ipInfo) : ""; rawInfo = rawInfo.replace(/[#*]+/g, " ").trim(); FuckD.bing.ipInfo = rawInfo ? `\n🌏所在地区:${rawInfo}` : ""; } } catch { console.debug("获取附加 IP 信息失败"); } return 1; } } return 0; } return 2; } catch { return 2; } } FuckF.send = async (webhook) => { const now = new Date() FuckD.bing.datetimeLocaleStr = now.toLocaleString() await Promise.all(webhook.map(async (i) => { if (!i.key) return let message = `「${i.name}」消息推送` const safeKey = String(i.key).trim(); const targetUrl = safeKey.startsWith("http") ? safeKey : i.url + safeKey; try { const result = await FuckF.xhr({ method: "POST", url: targetUrl, headers: { "content-type": "application/json; charset=UTF-8", }, data: JSON.stringify(i.msg), }) if (result) FuckF.log("🔵", `${message}完成 🔛${result}`) } catch (e) { FuckF.log("🔴", `消息推送出错!🔛${e.message}`) } })) } window.addEventListener('message', (event) => { if (event.data && event.data.type === 'AUTH_CODE_RECEIVED') { GM_log("收到授权码消息: " + event.data.code.substring(0, 30) + "..."); } }); if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand("📋 粘贴授权码", () => { const clipboardText = prompt("请粘贴授权码链接(完整URL):"); if (clipboardText && clipboardText.includes('login.live.com/oauth20_desktop.srf') && clipboardText.includes('code=')) { GM_setValue("Config.code", clipboardText); GM_setValue("Config.token", false); GM_notification({ text: "授权码已更新,脚本将在下一次运行时使用新授权码", title: GM_info.script.name, onclick: () => {} }); FuckF.log("🟢", "授权码已手动更新"); } else { GM_notification({ text: "无效的授权码链接,请重试", title: GM_info.script.name, onclick: () => {} }); } }); } // ===== 内容脚本分支:打开 dashboard 时点击"每日额外积分"按钮(上游 3.6.50 合并)===== if (typeof window !== 'undefined' && window.location && window.location.hostname === "rewards.bing.com" && (window.location.pathname === "/dashboard" || window.location.pathname.startsWith("/dashboard"))) { FuckF.claimExtraPoints(); // 同源兜底领取:页面上下文 fetch 的 Origin 天然为站点同源, // 可绕过 service_worker 扩展 origin 被风控账号 500 拒绝的问题(领取接口幂等) (async () => { try { const dashHtml = document.documentElement.innerHTML || ""; const clean = dashHtml.replace(/\\"/g, '"'); const claimMatch = clean.match(/"pointClaim":(null|\{|\[)/); if (!claimMatch || claimMatch[1] === "null") return; const actionInfo = await FuckF.getRewardsAction(); const claimId = actionInfo.ids.reportClaimAllPoints || "00491296f1d668ad46b65342c95cb9d72a62c1fa9d"; const resp = await fetch("https://rewards.bing.com/earn", { method: "POST", credentials: "include", headers: { "content-type": "text/plain;charset=UTF-8", "next-action": claimId, "next-router-state-tree": FuckF.getRouterStateTree("earn"), }, body: JSON.stringify([]), }); if (resp.ok && /^\d+:true\s*$/m.test(await resp.text())) { FuckF.log("✨", "页面上下文同源兜底领取成功"); } } catch (e) {} })(); return; } return new Promise((resolve) => { const seconds = FuckF.getTimestamp() GM_cookie("delete", { url: "https://bing.com", name: "_EDGE_S" }) FuckD.search.limit = FuckF.getScopeRandomNum(2, 5) FuckD.bing.dateNowNum = FuckF.getDatetime(true) FuckD.bing.dateNowhyphen = FuckF.getDatetime(false, false) const tasksArr = GM_getValue("Config.tasks", false) FuckD.sign.date = tasksArr ? tasksArr.sign : 0 FuckD.read.date = tasksArr ? tasksArr.read : 0 FuckD.promos.date = tasksArr ? tasksArr.promos : 0 FuckD.streak.date = tasksArr ? tasksArr.streak : 0 FuckD.search.date = tasksArr ? tasksArr.search : 0 // 月度打卡每日完成状态此前未回读,导致每次定时运行都重新执行并重复推送 FuckD.punchcard.date = tasksArr ? tasksArr.punchcard : 0 if (FuckD.api.mode != "offline") { const defaultApiName = "hot.baiwumm.com" const currentApiName = GM_getValue("Config.api", defaultApiName) const apiConfigMap = new Map(FuckD.api.arr) const getConfigApi = apiConfigMap.get(currentApiName) || apiConfigMap.get(defaultApiName) FuckD.api.url = getConfigApi.url FuckD.api.hot = getConfigApi.hot if (!apiConfigMap.has(currentApiName)) { GM_setValue("Config.api", "offline") FuckF.log("🟡", "搜索词接口配置错误,已替换为offline") } } FuckF.tasksEnd = async () => { if (FuckD.bing.code < 0) { if (FuckD.bing.code === -1) { FuckD.bing.code = -2; FuckD.bing.sendMSG = `🚫当前 IP 非中国大陆地区,已停止运行!\n⛔IP 地址:${FuckD.bing.ip} - ${FuckD.bing.region}${FuckD.bing.ipInfo}`; FuckF.log("🔴", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } return resolve(); } const endS = !FuckD.tasks.sign || FuckD.sign.end > 0; const endR = !FuckD.tasks.read || FuckD.read.end > 0; const endP = !FuckD.tasks.promos || FuckD.promos.end > 0; const endSe = !FuckD.tasks.search || FuckD.search.end > 0; if (endS && endR && endP && endSe) { FuckD.bing.end-- if (FuckD.bing.end === 0) { const checkDone = (taskEnabled, taskDate) => !taskEnabled || taskDate == FuckD.bing.dateNowNum; const isAllDone = checkDone(FuckD.tasks.sign, FuckD.sign.date) && checkDone(FuckD.tasks.read, FuckD.read.date) && checkDone(FuckD.tasks.promos, FuckD.promos.date) && checkDone(FuckD.tasks.search, FuckD.search.date); if (isAllDone) { const allDoneDate = GM_getValue("Config.allDoneDate", 0); if (allDoneDate != FuckD.bing.dateNowNum) { GM_setValue("Config.allDoneDate", FuckD.bing.dateNowNum); const dashboard = await FuckF.getRewardsInfo(); const counters = dashboard?.userStatus?.counters || {}; const pcPro = counters.pcSearch?.[0]?.pointProgress || 0; const mPro = counters.mobileSearch?.[0]?.pointProgress || 0; const readPro = (FuckD.read.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.readPoint", 0) : 0; const promosPro = (FuckD.promos.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.promosPoint", 0) : 0; const signPro = (FuckD.sign.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.signPoint", 0) : 0; const todayPoints = pcPro + mPro + readPro + promosPro + signPro; const availablePoints = dashboard?.userStatus?.availablePoints || 0; await new Promise(resolve => setTimeout(resolve, 1000)); FuckD.bing.sendMSG = `🎉今日所选任务已全部完成!\n✨今日共获取:${todayPoints} 积分\n💰 当前总积分:${availablePoints} 积分`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } else { await new Promise(resolve => setTimeout(resolve, 1000)); FuckF.log("💤", "今日所选任务已全部完成,继续保持休眠..."); } } else { await new Promise(resolve => setTimeout(resolve, 1000)); FuckF.log("🔵", `本次运行结束!用时 ${(FuckF.getTimestamp() - seconds) / 1000} 秒`); } } resolve() } } FuckF.signStart = async () => { const result = await FuckF.taskSign() if (!result) { setTimeout(() => { FuckF.signStart() }, FuckD.bing.time) } else { FuckF.tasksEnd() } } FuckF.readStart = async () => { const result = await FuckF.taskRead() if (!result) { setTimeout(() => { FuckF.readStart() }, FuckD.bing.time) } else { FuckF.tasksEnd() } } FuckF.promosStart = async () => { const result = await FuckF.taskPromos() if (!result) { setTimeout(() => { FuckF.promosStart() }, FuckD.bing.time) } else { FuckF.log("🔵", "活动任务完成,开始 HPQuiz..."); await FuckF.taskHPQuiz() await FuckF.taskRewardsAppCards() await FuckF.taskStreak() await FuckF.taskClaim(lastRewardHtml) FuckF.tasksEnd() } } FuckF.searchStart = async () => { const result = await FuckF.taskSearch() if (!result) { let timespan = FuckD.bing.time if (FuckD.search.index < FuckD.search.limit) { const span = Number(FuckD.bing.span) || 30; const spanMIN = (span - 15) * 1000 const spanMAX = (span + 15) * 1000 timespan = FuckF.getScopeRandomNum(spanMIN, spanMAX) } FuckF.log("🔍", `第 ${FuckD.search.index}/${FuckD.search.limit} 次搜索完成\n(${FuckD.search.device}),等待 ${timespan / 1000} 秒后继续...`) setTimeout(() => { FuckF.searchStart() }, timespan) } else { await FuckF.taskClaim() FuckF.tasksEnd() } } FuckF.punchcardStart = async () => { const result = await FuckF.taskPunchcard() if (!result) { setTimeout(() => { FuckF.punchcardStart() }, FuckD.bing.time) } else { FuckF.tasksEnd() } } FuckF.tasksStart = async (initDelay = 0) => { if (GM_info.script.author != "geoisam@qq.com") return resolve(); const host = "www.bing.com" if (!FuckD.bing.host) { if (FuckD.bing.status) { FuckD.bing.host = "cn.bing.com" } else { let url = await FuckF.xhr({ url: `https://${host}/`, headers: { "cookie": FuckD.cookie.mkt, }, }, true) url = new URL(url) FuckD.bing.host = url ? url.host : host } } const envStatus = await FuckF.mainlandCheck() if (envStatus === 1) { FuckD.bing.code = -1 FuckF.tasksEnd() return resolve() } else if (envStatus === 2) { FuckD.bing.sendMSG = "🚫网络连接异常,已停止运行!"; FuckF.log("🔴", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); return resolve(); } else { const isKeep = GM_getValue("Config.keep", true); const needSign = FuckD.tasks.sign && (isKeep || FuckD.sign.date != FuckD.bing.dateNowNum); const needRead = FuckD.tasks.read && (isKeep || FuckD.read.date != FuckD.bing.dateNowNum); const needPromos = FuckD.tasks.promos && (isKeep || FuckD.promos.date != FuckD.bing.dateNowNum); const needSearch = FuckD.tasks.search && (isKeep || FuckD.search.date != FuckD.bing.dateNowNum); const needPunchcard = FuckD.tasks.punchcard && (isKeep || FuckD.punchcard.date != FuckD.bing.dateNowNum); FuckF.log("🔵", `初始化运行完成!用时 ${((FuckF.getTimestamp() - seconds) - initDelay) / 1000} 秒`); const isTokenOK = await FuckF.renewToken(); if (!isTokenOK) { FuckD.tasks.sign = false; FuckD.tasks.read = false; FuckD.bing.sendMSG = "Token 请求失败,已自动跳过签入和阅读任务,请检查网络连接"; await new Promise(resolve => setTimeout(resolve, 500)); FuckF.log("🟡", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } // Edge 浏览连签:与主流程解耦的后台计时上报(约 30 分钟),需 dapi Token;不阻塞本次运行 resolve if (FuckD.tasks.edge && FuckD.bing.token) { FuckF.taskEdgeBrowsing().catch(e => FuckF.log("🔴", `Edge 浏览连签调度异常!🔛${e.message}`)); } if (needPromos || needSearch) { const result = await FuckF.getRewardsInfo(); if (!result) { FuckD.bing.sendMSG = "⚠️请检查 rewards.bing.com 是否登录!"; FuckF.log("🔴", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); return resolve(); } else { if (needPromos) setTimeout(() => { FuckF.promosStart() }, FuckD.bing.time * 2); if (needSearch) { const counters = result.userStatus?.counters || {}; const pcPro = counters.pcSearch?.[0]?.pointProgress || 0; let pcMax = counters.pcSearch?.[0]?.pointProgressMax || 0; const mPro = counters.mobileSearch?.[0]?.pointProgress || 0; let mMax = counters.mobileSearch?.[0]?.pointProgressMax || 0; if (pcMax === 0) pcMax = 60; if (mMax === 0) mMax = 0; if (pcPro >= pcMax && mPro >= mMax) { FuckD.search.end++; if (FuckD.search.date != FuckD.bing.dateNowNum) { FuckD.search.date = FuckD.bing.dateNowNum; GM_setValue("Config.tasks", FuckD.bing.tasks); const pcReport = pcMax ? `\n💻电脑端搜索:${pcPro}/${pcMax}` : ""; const mReport = mMax ? `\n📱手机端搜索:${mPro}/${mMax}` : ""; FuckD.bing.sendMSG = `🔍搜索任务已完成!${pcReport}${mReport}`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); } FuckF.tasksEnd(); } else { FuckF.log("🔍", "检测到搜索积分未满,开始搜索..."); setTimeout(() => { FuckF.searchStart() }, 100); } } else { FuckF.searchStart(); } } } else { FuckF.promosStart(); FuckF.searchStart(); } if (needSign) { setTimeout(() => { FuckF.signStart() }, FuckD.bing.time); } else { FuckF.signStart(); } if (needRead) { setTimeout(() => { FuckF.readStart() }, FuckD.bing.time * 3); } else { FuckF.readStart(); } if (needPunchcard && FuckD.bing.token) { setTimeout(() => { FuckF.punchcardStart() }, FuckD.bing.time * 4); } else if (needPunchcard) { FuckF.punchcardStart(); } } } const isKeep = GM_getValue("Config.keep", true); const checkDone = (taskEnabled, taskDate) => !taskEnabled || taskDate == FuckD.bing.dateNowNum; const isAllDone = checkDone(FuckD.tasks.sign, FuckD.sign.date) && checkDone(FuckD.tasks.read, FuckD.read.date) && checkDone(FuckD.tasks.promos, FuckD.promos.date) && checkDone(FuckD.tasks.search, FuckD.search.date) && checkDone(FuckD.tasks.punchcard, FuckD.punchcard.date); if ((!isKeep && isAllDone) || !FuckD.bing.repo.includes("geoisam") || (!FuckD.tasks.sign && !FuckD.tasks.read && !FuckD.tasks.promos && !FuckD.tasks.search && !FuckD.tasks.punchcard)) { if (isAllDone) { const allDoneDate = GM_getValue("Config.allDoneDate", 0); if (allDoneDate != FuckD.bing.dateNowNum) { GM_setValue("Config.allDoneDate", FuckD.bing.dateNowNum); FuckF.getRewardsInfo().then(dashboard => { const counters = dashboard?.userStatus?.counters || {}; const pcPro = counters.pcSearch?.[0]?.pointProgress || 0; const mPro = counters.mobileSearch?.[0]?.pointProgress || 0; const readPro = (FuckD.read.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.readPoint", 0) : 0; const promosPro = (FuckD.promos.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.promosPoint", 0) : 0; const signPro = (FuckD.sign.date == FuckD.bing.dateNowNum) ? GM_getValue("Config.signPoint", 0) : 0; const todayPoints = pcPro + mPro + readPro + promosPro + signPro; const availablePoints = dashboard?.userStatus?.availablePoints || 0; setTimeout(() => { FuckD.bing.sendMSG = `🎉今日所选任务已全部完成!\n✨今日共获取:${todayPoints} 积分\n💰 当前总积分:${availablePoints} 积分`; FuckF.log("🔵", FuckD.bing.sendMSG, true); FuckF.send(FuckD.wh); resolve(); }, 1000); }); } else { setTimeout(() => { FuckF.log("💤", "今日所选任务已全部完成,继续保持休眠..."); resolve(); }, 1000); } } else { resolve(); } } else { //const initDelay = FuckF.getScopeRandomNum(1000, 1000); //调试用,1秒停留时间 const initDelay = FuckF.getScopeRandomNum(5000, 95000); FuckF.log("⏳", `为规避定时器特征,停留 ${initDelay / 1000} 秒后运行...`); setTimeout(() => { FuckF.tasksStart(initDelay); }, initDelay); } })