// ==UserScript== // @name 微软积分商城签到(重构版) // @version 1.0.4 // @description 每天在后台自动完成 Microsoft Rewards 任务获取积分奖励,✅签入(PC+App静默)、✅阅读、✅活动、✅搜索、✅Quiz、✅拼图、✅热搜API、✅二次扫描、✅积分通知、✅连签任务检测、✅每日活动自动上报 // @author kunkun // @icon https://rewards.bing.com/rewardscdn/images/rewards/rewards-icon-96.png // @license MIT // @crontab */26 6-22 * * * // @connect bing.com // @connect login.live.com // @connect login.windows.net // @connect login.microsoftonline.com // @connect rewards.bing.com // @connect prod.rewardsplatform.microsoft.com // @connect hotapi.nntool.cc // @connect hot.baiwumm.com // @connect cnxiaobai.com // @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/oauth20_desktop.srf* // @match https://rewards.bing.com/* // @match https://www.bing.com/* // @match https://cn.bing.com/* // @run-at document-start // @grant unsafeWindow // @grant GM_xmlhttpRequest // @grant GM_notification // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_cookie // @grant GM_info // @grant GM_log // @grant GM_registerMenuCommand // @grant window.close // @storageName BingRewardsAuto_Shared // @tips 此脚本为开源免费使用,请勿购买 // ==/UserScript== /* global GM_cookie, GM_getValue, GM_setValue, GM_listValues, GM_deleteValue, GM_xmlhttpRequest, GM_log, GM_info, GM_notification, GM_openInTab */ /* ==UserConfig== Config: keep: title: 持续检测(全部完成后是否继续) type: checkbox default: true lock: title: 锁定国区(非大陆IP自动停止) type: checkbox default: true span: title: 旧版搜索间隔(已停用) type: number default: 30 min: 30 unit: 秒 description: 当前每次搜索随机等待 5~30 秒,本项保留兼容旧配置,不影响实际间隔。 api: title: 搜索词来源(offline随机词,在线选项均轮换接口) type: select default: offline values: [offline, hot.nntool.cc, hot.baiwumm.com, hot.cnxiaobai.com] description: 保留旧选项兼容配置;选择任一在线项都会在接口列表中轮换,不固定使用所选域名。 code: title: 授权码链接 type: textarea description: 粘贴 login.live.com 跳转后的完整URL Tasks: sign: title: 每日签入 type: checkbox default: true read: title: 新闻阅读 type: checkbox default: true promos: title: 活动卡片(含打卡) type: checkbox default: true quiz: title: Quiz 活动自动处理 type: checkbox default: true search: title: PC搜索 type: checkbox default: true Notice: bro: 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== */ // 入口:保存 OAuth 回调后退出;兑换页和视觉搜索页跳过主任务。 // Dashboard 独立处理页面任务;其余页面按路由启动主任务和点击流程。 // 结构:平台与状态 → 解析与服务 → 任务与页面入口。 // 完成依据:搜索/阅读查进度,卡片查服务器,页面点击按 DOM 规则。 // “已尝试”只表示已操作;“待确认”表示结果未知。 (function() { 'use strict'; // GM 存储适配,原样返回值或 Promise。 const Storage = { get: (...args) => GM_getValue(...args), set: (...args) => GM_setValue(...args), remove: (...args) => GM_deleteValue(...args), keys: (...args) => GM_listValues(...args) }; // 平台适配:延迟读取浏览器对象。 const Platform = { get location() { return location; }, get window() { return typeof window === "undefined" ? undefined : window; }, get document() { return typeof document === "undefined" ? undefined : document; }, get sessionStorage() { return typeof sessionStorage === "undefined" ? undefined : sessionStorage; }, get unsafeWindow() { return typeof unsafeWindow === "undefined" ? undefined : unsafeWindow; }, get navigator() { return typeof navigator === "undefined" ? undefined : navigator; }, get history() { return typeof history === "undefined" ? undefined : history; }, get MutationObserver() { return typeof MutationObserver === "undefined" ? undefined : MutationObserver; }, get info() { return GM_info; }, request: (...args) => GM_xmlhttpRequest(...args), log: (...args) => GM_log(...args), notify: (...args) => GM_notification(...args), openTab: (...args) => GM_openInTab(...args), registerMenu: (...args) => GM_registerMenuCommand(...args), cookie: (...args) => GM_cookie(...args), prompt: (...args) => prompt(...args), alert: (...args) => alert(...args), confirm: (...args) => confirm(...args) }; // 处理授权回调,跳过无需启动任务的页面。 function shouldSkipPage({ Platform, Storage }) { // 奖励面板不启动任务。 if (/^(?:www|cn)\.bing\.com$/i.test(Platform.location.hostname) && /^\/rewards\/panelflyout\/?$/i.test(Platform.location.pathname)) return true; if (Platform.location.hostname === "rewards.bing.com" && /^\/(?:api\/getuserinfo|signin|signin-oidc|createuser)\/?$/i.test(Platform.location.pathname)) return true; // 保存 OAuth 回调 URL,并清除旧刷新凭据。 if (Platform.location.hostname === "login.live.com" && Platform.location.pathname === "/oauth20_desktop.srf") { const code = new URLSearchParams(Platform.location.search).get("code"); if (code) { Storage.set("Config.code", Platform.location.href); Storage.set("Config.token", false); if (Storage.get("Notice.bro", true)) { try { Platform.notify({ title: "🟢 授权成功", text: "授权码已捕获,可关闭此页" }); } catch(_) {} } try { Platform.history.replaceState({}, "", "about:blank"); } catch(_) {} setTimeout(() => { try { Platform.window.close(); } catch(_) {} }, 200); } return true; } // 视觉搜索页不启动主任务,防止递归开页。 const bingFeatures = new URLSearchParams(Platform.location.search).get("features") ?.split(",").map(item => item.trim().toLowerCase()) || []; if (/^(?:www\.|cn\.)?bing\.com$/i.test(Platform.location.hostname) && bingFeatures.includes("vsstreak")) return true; return false; } if (shouldSkipPage({ Platform, Storage })) return; // 固定配置。 const RewardsAuto = { // 代码和用户设置均开启时才锁区。 lockRegion: true, // 桌面、移动网页和 App 的 User-Agent。 ua: { pc: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0", mobile: "Mozilla/5.0 (Linux; Android 16; Redmi K20 Pro Build/BP4A.251205.006; ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.132 Mobile Safari/537.36 EdgA/131.0.0.0", app: "Mozilla/5.0 (Linux; Android 16; Redmi K20 Pro Build/BP4A.251205.006; ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/144.0.7559.132 Mobile Safari/537.36 BingSapphire/32.6.2110003560", }, appConfig: { // App 固定参数;阅读服务可改用服务器返回的 offerId。 rewardsAppId: "SAAndroid/32.6.2110003560", channel: "SAAndroid", offerIds: { readArticle: "ENUS_readarticle3_30points", } }, searchPool: [ "agentic AI tools for everyday work 2026", "multimodal AI assistants voice and vision", "on device AI laptops comparison 2026", "AI smart glasses live translation", "generative AI video tools comparison 2026", "AI search engines with source citations", "digital twins for smart factories", "quantum computing breakthroughs 2026", "brain computer interface clinical trials 2026", "passkeys and passwordless account security", "home battery storage systems comparison 2026", "solid state EV battery production progress", "vehicle to home bidirectional charging", "hydrogen fuel cell trucks latest developments", "smart rings sleep tracking accuracy", "remote patient monitoring wearable devices", "assistive technology for independent living", "robot lawn mowers navigation comparison", "Nintendo Switch 2 best games 2026", "handheld gaming PCs battery life comparison", "cloud gaming services performance 2026", "FIFA World Cup 2026 highlights and best goals", "Formula 1 2026 technical regulations explained", "Milano Cortina 2026 Winter Olympics highlights", "2026 tennis Grand Slam season highlights", "James Webb Space Telescope discoveries 2026", "Artemis II mission latest updates", "sustainable aviation fuel commercial flights", "slow travel destinations 2026", "2026 science fiction movies and streaming series", "2026 十月新番追番指南", "2026 热门国漫推荐", "动漫角色人气排行", "二次元高清动漫壁纸", "动漫手办新品与收藏", "2026 漫展时间与 Cosplay 展示", "原神最新版本活动攻略", "崩坏星穹铁道角色配队攻略", "绝区零新角色培养建议", "鸣潮最新版本探索攻略", "2026 Steam 热门游戏推荐", "2026 国产单机游戏新作", "DeepSeek 最新模型与本地部署", "2026 国产开源大模型对比", "AI 编程助手代码质量评测", "ComfyUI 二次元绘画工作流", "AI 动漫角色一致性生成", "AI 音乐生成与编曲教程", "2026 旗舰手机影像与续航对比", "国产显卡与游戏性能评测", "宇树机器人最新产品演示", "2026 折叠屏手机选购", "6G 通信与卫星直连技术", "2026 游戏显示器 OLED 与 Mini LED 对比", "汉服美女国风写真", "旗袍美女人像摄影", "2026 女明星红毯造型", "气质美女日常穿搭", "美女高清手机壁纸", "美女人像摄影姿势" ], // 在线模式轮换来源,所选域名不固定。 apiConfig: { mode: Storage.get("Config.api", "offline"), sources: [ ["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"], }], ], }, skipPatterns: [ "referral", "refer and earn", "sweepstake", "entries", "install the", "set bing as your default", "bing wallpaper", "punch card", "ancient coin", "sea of thieves", "rewards extension", "redemption goal", "order history", "claim your gift", "shop to earn", "set goal", "Available tomorrow", "Offer is Locked", "Earn -1 points" ], }; // 各模块共享的本轮状态。 // access token 存内存,refresh token 存 Config.token。 const RunState = { token: false, region: "CN", host: "www.bing.com", dateNowNum: 0, dateNowStr: "", ipInfo: "", startTime: 0, forceRun: false, }; // 搜索进度与热搜缓存。 const SearchState = { lastSearchProgress: -1, restrictedTimes: 0, searchTrackingDate: undefined, wordList: [], wordIndex: 0 }; const ReadingState = { progress: 0, max: 30 }; // 通知渠道定义;发送时读取配置。 const Webhooks = [ { name: "企业微信", url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=", storageKey: "Notice.wework", configName: "企业微信 Webhook", hint: "群机器人webhook key", buildMessage: message => ({ "msgtype": "text", "text": { "content": `> ${new Date().toLocaleString()}\n\n ## ${Platform.info.script.name}\n ${message}` }, }), }, { name: "钉钉", url: "https://oapi.dingtalk.com/robot/send?access_token=", storageKey: "Notice.dingding", configName: "钉钉机器人 Access Token", hint: "不加签,关键词需包含 #", buildMessage: message => ({ "msgtype": "markdown", "markdown": { "title": Platform.info.script.name, "text": `> ${new Date().toLocaleString()}\n ### ${Platform.info.script.name}\n ${message}` }, }), }, { name: "飞书", url: "https://open.feishu.cn/open-apis/bot/v2/hook/", storageKey: "Notice.feishu", configName: "飞书机器人 Webhook", hint: "不加签,关键词需包含 #", buildMessage: message => ({ "msg_type": "interactive", "card": { "schema": "2.0", "header": { "title": { "tag": "plain_text", "content": Platform.info.script.name }, "template": "orange" }, "body": { "elements": [{ "tag": "markdown", "text_align": "center", "content": `#### ${new Date().toLocaleString()}\n ${message}` }] } } }), }, { name: "PushMe", url: "https://push.i-i.me/?push_key=", storageKey: "Notice.pushme", configName: "PushMe Key", hint: "push.i-i.me 推送key", buildMessage: message => ({ "type": "markdown", "title": `${Platform.info.script.name}[#rewards!https://rewards.bing.com/rewards.png]`, "content": `\n ${message}` }), }, { name: "Bark", url: "https://api.day.app/", storageKey: "Notice.bark", configName: "Bark Key", hint: "bark.day.app 推送key", buildMessage: message => ({ "group": "rewards", "icon": "https://rewards.bing.com/rewards.png", "title": Platform.info.script.name, "markdown": `\n ${message}` }), }, ]; // 运行锁状态,供 RunGuard 和 TaskRunLock 共用。 const LockState = { prefix: "Config.taskRunLock.", releasedPrefix: "Config.taskRunReleased.", pageExitStorageKey: "RewardsAuto.taskRunPageExit", ttl: 10 * 60 * 1000, heartbeatMs: 30000, key: "", id: "", held: false, lost: false, acquiring: false, timer: null, renewing: null, pageLeaving: false, pageExitListener: null }; // 纯数据解析,无网络或存储操作。 const DataParsers = { // 有效用户数据须含可识别积分,且无明确未登录标记。 validUserInfo(data) { if (!data || typeof data !== "object" || Array.isArray(data)) return false; const dashboard = data.dashboard || data, status = dashboard.userStatus; if (!status || typeof status !== "object" || Array.isArray(status)) return false; if ([data, dashboard, status].some(item => ["isAuthenticated", "isSignedIn", "isLoggedIn"].some(key => item[key] === false))) return false; const points = status.availablePoints ?? dashboard.availablePoints ?? data.balance; return (typeof points === "number" || typeof points === "string" && /^\d+$/.test(points)) && Number.isSafeInteger(Number(points)) && Number(points) >= 0; }, booleanFlag(value) { return value === true || value === "true" || value === 1 || value === "1" ? true : value === false || value === "false" || value === 0 || value === "0" ? false : null; }, // 仅解析非负安全整数。 parseNonNegativeInteger(value, trimString = true) { if (typeof value !== "number" && typeof value !== "string") return NaN; if (typeof value === "string" && !/^\d+$/.test(trimString ? value.trim() : value)) return NaN; const number = Number(value); return Number.isSafeInteger(number) && number >= 0 ? number : NaN; }, // 取首个有效余额;允许 0,全无效时返回 null。 accountBalance(...values) { for (const value of values) { const balance = DataParsers.parseNonNegativeInteger(value); if (Number.isFinite(balance)) return balance; } return null; }, userInfoBalance(data) { const dashboard = data?.dashboard || data; return this.accountBalance(dashboard?.userStatus?.availablePoints, dashboard?.availablePoints, data?.balance); }, dapiBalance(response) { return this.accountBalance(response?.balance, response?.userStatus?.availablePoints); }, sumSearchCounters(items) { if (!Array.isArray(items)) return { progress: 0, max: 0 }; return items.reduce((acc, item) => { acc.progress += Number(item?.pointProgress || 0); acc.max += Number(item?.pointProgressMax || item?.pointMax || 0); return acc; }, { progress: 0, max: 0 }); }, // 解码 Flight 字符串分片,不执行页面脚本。 flightChunks(html) { return [...String(html || "").matchAll( /self\.__next_f\.push\(\s*\[\s*1\s*,\s*("(?:\\.|[^"\\])*")\s*\]\s*\)/g )].map(match => { try { return JSON.parse(match[1]); } catch (_) { return ""; } }); }, // 按 partner、活动计数和当日标记识别连签,忽略公共文案。 parseStreakData(html) { const tasks = new Map(); let bonus = null; const visit = value => { if (!value || typeof value !== "object") return; if (!Array.isArray(value)) { const partner = String(value.partner || "").trim().toLowerCase(); const completed = Number(value.activitiesCompleted); const total = Number(value.activitiesTotal); if (partner && Number.isFinite(completed) && Number.isFinite(total) && Object.prototype.hasOwnProperty.call(value, "isCurrentDayCompleted")) { tasks.set(partner, { partner, title: String(value.title || ""), activitiesCompleted: completed, activitiesTotal: total, completedDays: Number(value.completedDays), totalDays: Number(value.totalDays), isCurrentDayCompleted: value.isCurrentDayCompleted === true, isEnabled: value.isEnabled !== false }); } const stampProgress = Number(value.activityProgress); const stampTotal = Number(value.activitiesTotal); const reward = Number(value.streakBonus); if (!partner && Number.isFinite(stampProgress) && Number.isFinite(stampTotal) && stampTotal > 0 && Number.isFinite(reward)) { bonus = { progress: stampProgress, total: stampTotal, reward }; } } for (const child of Object.values(value)) visit(child); }; const flight = DataParsers.flightChunks(html).join(""); for (const line of flight.split("\n")) { const record = line.match(/^[a-f0-9]+:(.*)$/i); if (!record) continue; try { visit(JSON.parse(record[1])); } catch (_) {} } return { tasks: [...tasks.values()], bonus }; } }; /** * 搜索配额与查询状态。 * @typedef {Object} SearchProgress * @property {number} progress 已获积分。 * @property {number} max 积分上限;0 不能确认完成。 * @typedef {Object} SearchQuotaResult * @property {"available"|"zero"|"missing"|"unknown"|"failed"} status 数据可用状态。 * @property {string} source 数据来源。 * @property {SearchProgress|null} pc PC 配额,无效时为 null。 * @property {SearchProgress|null} mobile 移动端配额;主任务仅执行 PC 搜索。 */ const SearchQuota = { /** * 创建含数据来源的查询结果。 * @param {SearchQuotaResult["status"]} status * @param {string} source * @param {SearchProgress|null} [pc=null] * @param {SearchProgress|null} [mobile=null] * @returns {SearchQuotaResult} */ create(status, source, pc = null, mobile = null) { return { status, source, pc, mobile }; }, // 搜索执行、受限判断和汇总共用此配额检查。 hasProgress(value) { return Number.isFinite(value?.pc?.progress) && value.pc.progress >= 0 && Number.isFinite(value.pc.max) && value.pc.max > 0; }, // 区分缺失、异常和 0/0;仅正上限为 available。 fromPC(value, source, mobile = null) { if (value === undefined) return this.create("missing", source); const progress = DataParsers.parseNonNegativeInteger(value?.progress); const max = DataParsers.parseNonNegativeInteger(value?.max); if (!Number.isFinite(progress) || !Number.isFinite(max) || (max === 0 && progress !== 0)) { return this.create("unknown", source); } return this.create(max > 0 ? "available" : "zero", source, { progress, max }, mobile); }, fromCounters(items, source) { if (items === undefined || Array.isArray(items) && items.length === 0) return this.create("missing", source); if (!Array.isArray(items)) return this.create("unknown", source); for (const item of items) { const max = (item?.pointProgressMax || item?.pointMax) ?? item?.pointProgressMax; if (!Number.isFinite(DataParsers.parseNonNegativeInteger(item?.pointProgress)) || !Number.isFinite(DataParsers.parseNonNegativeInteger(max))) return this.create("unknown", source); } return this.fromPC(DataParsers.sumSearchCounters(items), source); }, // 优先选可用配额,异常结果不覆盖有效结果。 select(results) { for (const status of ["available", "zero", "unknown", "missing", "failed"]) { const found = results.find(result => result.status === status); if (found) return found; } return this.create("unknown", "none"); } }; // 从 HTML/Flight 提取配额、余额和汇总数据。 const RewardsPage = { prepare(html) { const flight = DataParsers.flightChunks(html); return { html, text: flight.length ? flight.join("") : html.replace(/\\"/g, '"') }; }, // 按括号和引号截取 JSON,跳过字符串内的括号。 field(text, name) { const match = new RegExp(`"${name}"\\s*:\\s*`).exec(text); if (!match) return undefined; const start = match.index + match[0].length; if (text[start] !== "{" && text[start] !== "[") return null; let depth = 0, quoted = false, escaped = false; for (let index = start; index < text.length; index++) { const char = text[index]; if (quoted) { if (escaped) escaped = false; else if (char === "\\") escaped = true; else if (char === '"') quoted = false; continue; } if (char === '"') quoted = true; else if (char === "{" || char === "[") depth++; else if (char === "}" || char === "]") { if (--depth === 0) { try { return JSON.parse(text.slice(start, index + 1)); } catch (_) { return null; } } } } return null; }, // 配额来源:结构化 PC 数据、页面表格、combinedSearch。 searchQuota(page) { const counters = this.field(page.text, "pointsCounters"); const mobile = SearchQuota.fromPC(counters?.mobile, "page").pc; const primary = counters === null ? SearchQuota.create("unknown", "page") : SearchQuota.fromPC(counters?.pc, "page", mobile); if (primary.status === "available") return primary; const candidates = [primary]; const row = page.html.match(/
\s*必应搜索\s*<\/p>\s*<\/div>\s*
([^<]+)<\/p><\/div>