// ==UserScript== // @name Microsoft Rewards 自动任务(DAPI 后台稳定版) // @namespace https://scriptcat.org/ // @version 1.7.0 // @description 自动完成 Microsoft Rewards 每日任务:签入 / 阅读 / 搜索 / 每日活动 / 活动卡片。全部走 DAPI Bearer Token 通道。 // @author you // @license MIT // @crontab */30 * * * * // @connect bing.com // @connect cn.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 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 GM_xmlhttpRequest // @grant GM_notification // @grant GM_openInTab // @grant GM_getValue // @grant GM_setValue // @grant GM_info // @grant GM_log // @grant GM_registerMenuCommand // ==/UserScript== /* ==UserConfig== Config: keep: title: 持续检测(全部完成后是否继续) type: checkbox default: true span: title: 搜索间隔(秒) type: number default: 30 min: 15 api: title: 搜索词来源 type: select default: offline values: [offline, hot.nntool.cc, hot.baiwumm.com, hot.cnxiaobai.com] code: title: 授权码链接 type: textarea description: 粘贴 login.live.com 跳转后的完整 URL Tasks: sign: title: 每日签入(App 端) type: checkbox default: true read: title: 新闻阅读 type: checkbox default: true promos: title: 每日活动 + 活动卡片 type: checkbox default: true search: title: PC 搜索 type: checkbox default: true Notice: bro: title: 浏览器通知 type: checkbox default: true wework: title: 企业微信机器人 type: text password: true dingding: title: 钉钉机器人(关键词 #) type: text password: true feishu: title: 飞书机器人(关键词 #) type: text password: true pushme: title: PushMe Key type: text password: true bark: title: Bark Key type: text password: true ==/UserConfig== */ (function () { 'use strict'; /* ============================================================ * 0. 环境检测 * ============================================================ */ const IS_BACKGROUND = (() => { try { if (typeof WorkerGlobalScope !== 'undefined' && typeof self !== 'undefined' && self instanceof WorkerGlobalScope) { return true; } if (typeof document === 'undefined') return true; if (!document.location) return true; const href = (document.location.href) || ''; if (!/^https?:/i.test(href)) return true; return false; } catch (_) { return true; } })(); /* ============================================================ * 1. 标签页守卫 * ============================================================ */ const TabGuard = { _lastOpen: {}, _cooldown: 5 * 60 * 1000, open(url, options = {}) { if (!url) return false; const key = this._normalize(url); const now = Date.now(); const last = this._lastOpen[key] || 0; if (now - last < this._cooldown) { const wait = Math.ceil((this._cooldown - (now - last)) / 1000); GM_log(`🛡️ TabGuard 跳过重复打开(冷却 ${wait}s): ${key}`); return false; } this._lastOpen[key] = now; GM_openInTab(url, { active: false, insert: true, ...options }); return true; }, _normalize(url) { try { const u = new URL(url); return `${u.hostname}${u.pathname}`; } catch { return String(url); } }, }; /* ============================================================ * 2. 常量 * ============================================================ */ const CFG = { clientId: '0000000040170455', scope: 'service::prod.rewardsplatform.microsoft.com::MBI_SSL', redirectUri: 'https://login.live.com/oauth20_desktop.srf', authUrl: 'https://login.live.com/oauth20_authorize.srf', tokenUrl: 'https://login.live.com/oauth20_token.srf', dapiBase: 'https://prod.rewardsplatform.microsoft.com/dapi/me', appId: 'SAAndroid/32.6.2110003560', channel: 'SAAndroid', offerRead: 'ENUS_readarticle3_30points', uaPC: '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', uaMobile: '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', uaApp: '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', }; const SEARCH_POOL = [ 'what is the weather forecast tomorrow', 'how do I make sourdough bread at home', 'why is the sky blue scientific explanation', 'how to learn rust programming in 2026', 'how to fix a leaky kitchen faucet step by step', 'how to start a vegetable garden in spring', 'how to take care of a bonsai tree', 'how to meditate properly for beginners', 'how do solar panels actually work', 'how to brew the perfect espresso at home', 'how to sleep better naturally tonight', 'why do cats purr when they are happy', 'best coffee shops in san francisco downtown', 'tokyo cherry blossom season 2026 forecast', 'best noise cancelling headphones under 300', 'easy chocolate chip cookies recipe from scratch', '30 minute home workout routine no equipment', 'git rebase vs merge which one to use', 'healthy breakfast ideas under 10 minutes', 'pomodoro technique for focus and productivity', '天气预报', '今日新闻热点', '美食食谱家常菜', '旅游攻略', '健康养生知识', '科技资讯', '电影推荐', '股票行情', '体育赛事', '历史上的今天', ]; const HOT_ENDPOINTS = { 'hot.nntool.cc': k => `https://hotapi.nntool.cc/${k}`, 'hot.baiwumm.com': k => `https://hot.baiwumm.com/api/${k}`, 'hot.cnxiaobai.com': k => `https://cnxiaobai.com/DailyHotApi/${k}`, }; const HOT_KEYS = ['weibo', 'baidu', 'douyin', 'toutiao', 'zhihu']; const SKIP_PATTERNS = [ 'referral', 'refer and earn', 'sweepstake', 'entries', 'install the', 'set bing as your default', 'bing wallpaper', 'punch card', 'rewards extension', 'redemption goal', 'order history', 'claim your gift', 'shop to earn', 'set goal', 'available tomorrow', 'offer is locked', ]; /* ============================================================ * 3. 工具 * ============================================================ */ const sleep = ms => new Promise(r => setTimeout(r, ms)); const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const randSleep = (min = 3000, max = 8000) => sleep(randInt(min, max)); const todayNum = () => { const d = new Date(); return Number(`${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`); }; const todayStr = () => { const d = new Date(); return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; }; const todayStrPadded = () => { const d = new Date(); return `${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')}/${d.getFullYear()}`; }; const uuid = () => crypto.randomUUID().replace(/-/g, '').toUpperCase(); const activityId = () => (uuid() + uuid()).slice(0, 64); const safeJson = (text, fallback = null) => { try { return JSON.parse(text); } catch { return fallback; } }; function absoluteUrl(url) { if (!url) return ''; if (/^https?:\/\//i.test(url)) return url; if (url.startsWith('//')) return `https:${url}`; if (url.startsWith('/')) return `https://rewards.bing.com${url}`; return `https://rewards.bing.com/${url}`; } function xhr(options) { return new Promise((resolve, reject) => { const started = Date.now(); GM_xmlhttpRequest({ method: 'GET', ...options, timeout: options.timeout || 20000, onload(res) { const cost = ((Date.now() - started) / 1000).toFixed(2); if (res.status >= 200 && res.status < 300) return resolve(res.responseText); if ([301, 302, 303, 307, 308].includes(res.status)) { const m = (res.responseHeaders || '').match(/^location:\s*(.+)$/im); if (m) return resolve(m[1].trim()); } reject(new Error(`HTTP ${res.status}(${cost}s)`)); }, onerror: () => reject(new Error('网络错误')), ontimeout: () => reject(new Error('请求超时')), }); }); } function log(icon, msg, push = false) { GM_log(`${icon} ${msg}`); if (!push) return; if (GM_getValue('Notice.bro', true)) { try { GM_notification({ title: GM_info.script.name, text: msg, timeout: 8000 }); } catch (_) { /* 忽略 */ } } pushAll(`${icon} ${msg}`).catch(() => {}); } /* ============================================================ * 4. 消息推送 * ============================================================ */ const notifiers = [ { name: '企业微信', key: () => GM_getValue('Notice.wework', ''), url: k => `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${k}`, payload: (text, stamp) => ({ msgtype: 'text', text: { content: `${stamp}\n${text}` } }), }, { name: '钉钉', key: () => GM_getValue('Notice.dingding', ''), url: k => `https://oapi.dingtalk.com/robot/send?access_token=${k}`, payload: (text, stamp) => ({ msgtype: 'markdown', markdown: { title: GM_info.script.name, text: `# ${stamp}\n${text}` } }), }, { name: '飞书', key: () => GM_getValue('Notice.feishu', ''), url: k => `https://open.feishu.cn/open-apis/bot/v2/hook/${k}`, payload: (text, stamp) => ({ msg_type: 'interactive', card: { schema: '2.0', header: { title: { tag: 'plain_text', content: GM_info.script.name }, template: 'orange' }, body: { elements: [{ tag: 'markdown', content: `**${stamp}**\n${text}` }] }, }, }), }, { name: 'PushMe', key: () => GM_getValue('Notice.pushme', ''), url: k => `https://push.i-i.me/?push_key=${k}`, payload: (text, stamp) => ({ type: 'markdown', title: `${GM_info.script.name}\n${stamp}`, content: text }), }, { name: 'Bark', key: () => GM_getValue('Notice.bark', ''), url: k => `https://api.day.app/${k}`, payload: (text, stamp) => ({ title: GM_info.script.name, group: 'rewards', markdown: `**${stamp}**\n${text}` }), }, ]; async function pushAll(text) { const stamp = new Date().toLocaleString('zh-CN'); await Promise.all(notifiers.map(async n => { const key = String(n.key() || '').trim(); if (!key) return; const url = key.startsWith('http') ? key : n.url(key); try { await xhr({ method: 'POST', url, headers: { 'content-type': 'application/json; charset=UTF-8' }, data: JSON.stringify(n.payload(text, stamp)), }); } catch (e) { GM_log(`🔴 「${n.name}」推送失败: ${e.message}`); } })); } /* ============================================================ * 5. 授权模块 * ============================================================ */ const Auth = { accessToken: null, _obtaining: null, _capturing: false, async get(force = false) { if (this.accessToken && !force) return this.accessToken; if (this._obtaining) return this._obtaining; this._obtaining = this._doGet(force); try { return await this._obtaining; } finally { this._obtaining = null; } }, async _doGet(force) { this.accessToken = null; if (!force) { const refresh = GM_getValue('token.refresh', ''); if (refresh) { GM_log('🔄 尝试刷新 Token...'); if (await this._request({ grant_type: 'refresh_token', refresh_token: refresh, scope: CFG.scope, })) { GM_log('🟢 Token 刷新成功'); return this.accessToken; } GM_log('🟡 Refresh Token 失效'); } } const code = await this._obtainCode(); if (!code) { GM_log('🔴 未能获取授权码'); return null; } GM_log('🔄 使用授权码换取 Token...'); if (await this._request({ grant_type: 'authorization_code', code })) { GM_log('🟢 Token 获取成功'); return this.accessToken; } GM_log('🔴 授权码换取 Token 失败'); return null; }, async forceRefresh() { GM_log('🔄 强制刷新 Token...'); GM_setValue('token.refresh', ''); GM_setValue('token.time', 0); this.accessToken = null; return await this.get(true); }, async _request(params) { const body = new URLSearchParams({ client_id: CFG.clientId, redirect_uri: CFG.redirectUri, ...params, }).toString(); for (let i = 1; i <= 3; i++) { try { const text = await xhr({ method: 'POST', url: CFG.tokenUrl, headers: { 'content-type': 'application/x-www-form-urlencoded' }, data: body, timeout: 15000, }); const data = safeJson(text); if (!data) { GM_log(`🟡 Token 响应非 JSON(${i}/3): ${String(text).slice(0, 100)}`); throw new Error('响应不是 JSON'); } if (data.access_token) { this.accessToken = data.access_token; if (data.refresh_token) GM_setValue('token.refresh', data.refresh_token); GM_setValue('token.time', Date.now()); return true; } GM_log(`🔴 Token 错误: ${data.error} - ${data.error_description || ''}`); if (['invalid_grant', 'invalid_request', 'invalid_client'].includes(data.error)) { if (data.error === 'invalid_grant') GM_setValue('token.refresh', ''); return false; } } catch (e) { GM_log(`🟡 Token 请求异常(${i}/3): ${e.message}`); } await sleep(3000); } return false; }, async _obtainCode() { if (this._capturing) { GM_log('🟡 授权码捕获进行中,等待...'); for (let i = 0; i < 180; i++) { await sleep(1000); const code = this._parseCode(GM_getValue('Config.code', '')); if (code) { GM_setValue('Config.code', ''); return code; } } return null; } this._capturing = true; try { const cached = this._parseCode(GM_getValue('Config.code', '')); if (cached) { GM_setValue('Config.code', ''); GM_log('🟢 使用已保存的授权码'); return cached; } if (IS_BACKGROUND) { GM_log('🔴 后台环境无法自动授权。请点击菜单「🔑 手动授权」完成登录,再点击「📋 粘贴授权码」保存。', true); return null; } const url = `${CFG.authUrl}?client_id=${CFG.clientId}` + `&response_type=code` + `&scope=${encodeURIComponent(CFG.scope)}` + `&redirect_uri=${encodeURIComponent(CFG.redirectUri)}`; const opened = TabGuard.open(url, { active: true }); if (opened) { if (GM_getValue('Notice.bro', true)) { try { GM_notification({ title: '🔑 需要授权', text: '请登录并完成授权,脚本会自动捕获授权码', timeout: 0, }); } catch (_) { /* 忽略 */ } } GM_log('🟡 已打开授权页,等待授权码(最长 3 分钟)...'); } else { GM_log('🟡 授权页近期已打开,等待授权码...'); } for (let i = 0; i < 180; i++) { await sleep(1000); const code = this._parseCode(GM_getValue('Config.code', '')); if (code) { GM_setValue('Config.code', ''); GM_log('🟢 授权码自动捕获成功'); return code; } } GM_log('🔴 授权超时。请手动完成授权后点击「📋 粘贴授权码」保存。', true); return null; } finally { this._capturing = false; } }, _parseCode(raw) { if (!raw) return null; const s = String(raw).trim(); if (s.includes('removed=true')) { GM_log('🟡 检测到 removed=true,授权码已失效,请重新授权'); return null; } if (s.includes('code=')) { try { const code = new URL(s).searchParams.get('code'); if (code) return code; } catch (_) { /* 继续尝试裸码 */ } } return (s.length > 20 && !s.includes('http')) ? s : null; }, }; /* ============================================================ * 6. Rewards DAPI * ============================================================ */ const Rewards = { async dapi(path = '', { method = 'GET', body, retry = true } = {}) { const token = await Auth.get(); if (!token) throw new Error('无有效 Token'); try { return await xhr({ method, url: CFG.dapiBase + path, headers: { 'content-type': 'application/json; charset=UTF-8', 'user-agent': CFG.uaApp, 'authorization': `Bearer ${token}`, 'x-rewards-appid': CFG.appId, 'x-rewards-ismobile': 'true', 'x-rewards-country': 'cn', 'x-rewards-language': 'zh', }, data: body ? JSON.stringify(body) : undefined, }); } catch (e) { if (retry && /401/.test(e.message)) { GM_log('🟡 DAPI 返回 401,强制刷新 Token 后重试'); const ok = await Auth.forceRefresh(); if (ok) { return await this.dapi(path, { method, body, retry: false }); } } throw e; } }, _parseCounter(field) { if (Array.isArray(field)) { return field.reduce((acc, it) => ({ progress: acc.progress + Number(it.pointProgress ?? it.progress ?? 0), max: acc.max + Number(it.pointProgressMax ?? it.pointMax ?? it.max ?? 0), }), { progress: 0, max: 0 }); } if (field && typeof field === 'object') { return { progress: Number(field.pointProgress ?? field.progress ?? 0), max: Number(field.pointProgressMax ?? field.pointMax ?? field.max ?? 0), }; } return { progress: 0, max: 0 }; }, async getQuota() { const text = await this.dapi('?channel=SAAndroid&options=613'); const data = safeJson(text); if (!data?.response) throw new Error('DAPI 返回异常'); const resp = data.response; const counters = resp.counters || {}; const pc = this._parseCounter(counters.pcSearch); const mobile = this._parseCounter(counters.mobileSearch); // 前台环境读不到配额时,回退 getuserinfo if (pc.max === 0 && !IS_BACKGROUND) { try { GM_log('🟡 DAPI pcSearch 配额为 0,尝试 getuserinfo 兜底...'); const fb = await this.getQuotaFromUserInfo(); if (fb.pc.max > 0) { GM_log(`🟢 getuserinfo 兜底成功: PC ${fb.pc.progress}/${fb.pc.max}`); return fb; } } catch (e) { GM_log(`🟡 getuserinfo 兜底失败: ${e.message}`); } } return { pc, mobile, balance: Number(resp.balance || 0), }; }, async getQuotaFromUserInfo() { const url = `https://rewards.bing.com/api/getuserinfo?type=1&X-Requested-With=XMLHttpRequest&_=${Date.now()}`; const text = await xhr({ url, headers: { 'user-agent': CFG.uaPC, 'referer': 'https://rewards.bing.com/', 'x-requested-with': 'XMLHttpRequest', }, }); const data = safeJson(text); if (!data) throw new Error('getuserinfo 返回异常'); const dash = data.dashboard || data; const status = dash.userStatus || {}; const counters = status.counters || {}; return { pc: this._parseCounter(counters.pcSearch), mobile: this._parseCounter(counters.mobileSearch), balance: Number(status.availablePoints || dash.availablePoints || 0), }; }, async getReadProgress() { try { const text = await this.dapi('?channel=SAAndroid&options=613'); const data = safeJson(text); if (!data) return null; const promos = data.response?.promotions || []; const task = promos.find(p => p.attributes?.offerid === CFG.offerRead); if (!task?.attributes) return null; return { progress: parseInt(task.attributes.progress) || 0, max: parseInt(task.attributes.max) || 30, }; } catch (_) { return null; } }, async discoverCards() { const cards = []; const seen = new Set(); let text; try { text = await this.dapi('?channel=SAAndroid&options=613'); } catch (e) { GM_log(`🟡 DAPI 扫描卡片失败: ${e.message}`); return cards; } const data = safeJson(text); const promos = data?.response?.promotions || []; for (const p of promos) { const attrs = p.attributes || {}; const offerId = attrs.offerid; if (!offerId) continue; if (offerId === CFG.offerRead) continue; const lowerId = offerId.toLowerCase(); if (lowerId.includes('checkin') || lowerId.includes('check-in') || lowerId.includes('dailycheckin')) { continue; } const progress = parseInt(attrs.progress) || 0; const max = parseInt(attrs.max) || 1; if (max > 0 && progress >= max) continue; const title = String(p.name || attrs.title || p.description || ''); const lower = `${title} ${offerId}`.toLowerCase(); if (SKIP_PATTERNS.some(s => lower.includes(s))) continue; const key = `dapi:${offerId}`; if (seen.has(key)) continue; seen.add(key); const points = parseInt(attrs.points) || parseInt(attrs.pointProgressMax) || parseInt(attrs.max) || 10; cards.push({ offerId, points, title, kind: inferKind(offerId, title), url: attrs.destinationUrl || attrs.url || '', source: 'dapi', }); } return cards; }, async claimCard(card) { try { const body = { amount: 1, country: 'cn', id: activityId(), type: 101, attributes: { offerid: card.offerId }, channel: CFG.channel, }; const text = await this.dapi('/activities', { method: 'POST', body }); const data = safeJson(text) || {}; const resp = data.response || {}; if (resp.isDuplicate) { GM_log(` ✓ ${card.offerId} 已完成(重复)`); return true; } const pts = Number(resp.activity?.p || resp.activity?.points || 0); if (pts > 0) { GM_log(` ✓ ${card.offerId} +${pts}`); return true; } return true; } catch (e) { GM_log(`🟡 DAPI 卡片领取失败(${card.offerId}): ${e.message}`); return false; } }, }; function inferKind(offerId, title = '') { const text = `${offerId} ${title}`.toLowerCase(); if (/quiz|trivia/.test(text)) return 'quiz'; if (/puzzle/.test(text)) return 'puzzle'; if (/read/.test(text)) return 'read'; if (/search|explore/.test(text)) return 'search'; if (/daily/.test(text)) return 'daily'; if (/streak/.test(text)) return 'streak'; return 'promo'; } /* ============================================================ * 7. 页面访问 * ============================================================ */ async function visitActivityPage(url) { const finalUrl = absoluteUrl(url); if (!finalUrl) return false; try { await xhr({ url: finalUrl, headers: { 'user-agent': CFG.uaPC, 'referer': 'https://rewards.bing.com/', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8', }, timeout: 15000, }); return true; } catch (e) { GM_log(`🟡 访问活动页失败: ${e.message}`); return false; } } async function reportDailyActivity(url) { const finalUrl = absoluteUrl(url); if (!finalUrl) return false; try { const cnUrl = finalUrl .replace(/^https?:\/\/www\.bing\.com/, 'https://cn.bing.com') .replace(/^https:\/\/bing\.com/, 'https://cn.bing.com'); const urlObj = new URL(cnUrl); const ig = uuid(); try { await xhr({ method: 'GET', url: cnUrl, headers: { 'user-agent': CFG.uaPC, 'referer': 'https://rewards.bing.com/', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', }, timeout: 15000, }); } catch (_) { /* 忽略 */ } const ncheaderParams = new URLSearchParams(); ncheaderParams.set('ver', String(Date.now()).substring(0, 8)); ncheaderParams.set('IID', 'commerce.5057'); ncheaderParams.set('IG', ig); try { await xhr({ method: 'POST', url: `https://cn.bing.com/rewardsapp/ncheader?${ncheaderParams.toString()}`, headers: { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': CFG.uaPC, 'referer': cnUrl, 'origin': 'https://cn.bing.com', }, data: 'wb=1;i=1;v=1', timeout: 10000, }); } catch (_) { /* 忽略 */ } const reportParams = new URLSearchParams(); reportParams.set('IG', ig); reportParams.set('IID', 'commerce.5067'); const sp = urlObj.searchParams; if (sp.get('form')) reportParams.set('form', sp.get('form')); const ocid = sp.get('ocid') || sp.get('OCID'); if (ocid) reportParams.set('ocid', ocid); if (sp.get('rnoreward')) reportParams.set('rnoreward', sp.get('rnoreward')); const bodyParams = new URLSearchParams(); bodyParams.set('url', cnUrl); bodyParams.set('V', 'web'); await xhr({ method: 'POST', url: `https://cn.bing.com/rewardsapp/reportActivity?${reportParams.toString()}`, headers: { 'content-type': 'application/x-www-form-urlencoded', 'user-agent': CFG.uaPC, 'referer': cnUrl, 'origin': 'https://cn.bing.com', 'accept': '*/*', }, data: bodyParams.toString(), timeout: 15000, }); return true; } catch (e) { GM_log(`🟡 每日活动上报失败: ${e.message}`); return false; } } /* ============================================================ * 8. 搜索 * ============================================================ */ async function performSearch(query, mobile = false) { const host = 'cn.bing.com'; const ua = mobile ? CFG.uaMobile : CFG.uaPC; const dev = mobile ? 'm' : 'd'; const mkt = '&mkt=zh-CN'; const q = encodeURIComponent(query); const cookie = `_Rwho=u=${dev}&ts=${todayStr()}`; const searchUrl = `https://${host}/search?q=${q}&form=QBLH${mkt}`; await xhr({ url: searchUrl, headers: { 'user-agent': ua, 'cookie': cookie, 'referer': `https://${host}/?form=QBLH`, }, }); const ig = uuid(); const headers = { 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 'user-agent': ua, 'referer': `https://${host}/?form=QBLH`, 'cookie': cookie, }; const params = `q=${q}&form=QBLH${mkt}`; await xhr({ method: 'POST', url: `https://${host}/rewardsapp/ncheader?ver=88888888&IID=SERP.5047&IG=${ig}&ajaxreq=1`, headers, data: 'wb=1%3bi%3d1%3bv%3d1', }); await xhr({ method: 'POST', url: `https://${host}/rewardsapp/reportActivity?IG=${ig}&IID=SERP.5047&${params}&ajaxreq=1`, headers, data: `url=${encodeURIComponent(searchUrl)}&V=web`, }); } async function pickQuery() { const mode = GM_getValue('Config.api', 'offline'); const builder = HOT_ENDPOINTS[mode]; if (builder) { const key = HOT_KEYS[randInt(0, HOT_KEYS.length - 1)]; try { const text = await xhr({ url: builder(key), timeout: 8000 }); const data = safeJson(text); const list = Array.isArray(data?.data) ? data.data : []; if (list.length) { const item = list[randInt(0, list.length - 1)]; const title = String(item.title || '').trim(); if (title) return title.slice(0, 30); } } catch (e) { GM_log(`🟡 热搜词获取失败: ${e.message}`); } } return SEARCH_POOL[randInt(0, SEARCH_POOL.length - 1)]; } /* ============================================================ * 9. 任务实现 * ============================================================ */ async function taskSignIn() { const today = todayNum(); if (GM_getValue('state.signDate', 0) === today) { log('✅', '签入已完成'); return; } try { const text = await Rewards.dapi('/activities', { method: 'POST', body: { amount: 1, id: activityId(), type: 103, country: 'cn', channel: CFG.channel, }, }); const data = safeJson(text) || {}; const resp = data.response || {}; const pts = Number(resp.activity?.p || resp.activity?.points || 0); if (pts > 0) { log('📱', `App 签入 +${pts}`, true); } else if (resp.isDuplicate) { log('📱', 'App 今日已签入'); } else { log('📱', 'App 签入完成(无新增积分)'); } GM_setValue('state.signDate', today); } catch (e) { log('🟡', `App 签入失败: ${e.message}`); } } async function taskRead() { const info = await Rewards.getReadProgress(); if (!info) { log('🟡', '无法获取阅读进度'); return; } let { progress: cur, max } = info; if (cur >= max) { GM_setValue('state.readDate', todayNum()); log('✅', `阅读已完成 ${cur}/${max}`); return; } log('📖', `阅读进度 ${cur}/${max}`); const limit = Math.min(max - cur, 10); let success = 0; for (let i = 0; i < limit; i++) { try { const text = await Rewards.dapi('/activities', { method: 'POST', body: { amount: 1, country: 'cn', id: activityId(), type: 101, attributes: { offerid: CFG.offerRead }, }, }); const data = safeJson(text) || {}; const resp = data.response || {}; if (resp.isDuplicate) { log('📖', '阅读已达上限'); break; } const pts = Number(resp.activity?.p || 0); if (pts > 0) { success++; cur++; log('📖', `阅读第 ${i + 1} 篇 +${pts}`); } } catch (e) { log('🟡', `阅读请求失败: ${e.message}`); break; } await randSleep(); } if (success > 0) { GM_setValue('state.readDate', todayNum()); log('🔵', `阅读完成 ${cur}/${max}(本次 ${success} 篇)`, true); } else { log('🟡', '本次阅读未获得积分'); } } async function taskPromos() { const today = todayNum(); if (GM_getValue('state.promosDate', 0) === today) { log('✅', '活动卡片已完成'); return; } log('🧩', '扫描活动卡片...'); let cards; try { cards = await Rewards.discoverCards(); } catch (e) { log('🔴', `扫描卡片失败: ${e.message}`); return; } if (!cards || cards.length === 0) { GM_setValue('state.promosDate', today); log('✅', '今日无待处理卡片'); return; } log('🧩', `发现 ${cards.length} 个卡片`); let ok = 0, fail = 0; for (const card of cards) { log(' ', `[${card.kind}] ${card.offerId} +${card.points}p`); await randSleep(); try { if (card.url) { await visitActivityPage(card.url); await randSleep(2000, 4000); } if (card.url && /bing\.com/i.test(card.url)) { await reportDailyActivity(card.url); await randSleep(2000, 4000); } const result = await Rewards.claimCard(card); result ? ok++ : fail++; } catch (e) { log('🟡', `卡片处理失败(${card.offerId}): ${e.message}`); fail++; } } GM_setValue('state.promosDate', today); log('🔵', `活动完成:${ok} 成功 / ${fail} 失败`, true); } /** PC 搜索(回退版:配额为 0 时跳过) */ async function taskSearch() { let quota; try { quota = await Rewards.getQuota(); } catch (e) { log('🔴', `无法获取搜索配额: ${e.message}`); return; } const { progress, max } = quota.pc; if (!max) { log('🟡', '搜索配额为 0,跳过'); return; } if (progress >= max) { log('✅', `搜索已完成 ${progress}/${max}`); GM_setValue('state.searchDate', todayNum()); return; } log('🔍', `PC 搜索进度 ${progress}/${max}`); const span = Math.max(15, Number(GM_getValue('Config.span', 30))); let stagnant = 0; let last = progress; for (let i = 0; i < 30 && progress < max; i++) { const query = await pickQuery(); log('🔍', `搜索 ${progress}/${max}: ${query}`); try { await performSearch(query, false); } catch (e) { log('🟡', `搜索失败: ${e.message}`); } const wait = randInt((span - 15) * 1000, (span + 15) * 1000); await sleep(wait); if (i % 2 === 1) { try { const q = await Rewards.getQuota(); if (q.pc.max > 0) { progress = q.pc.progress; max = q.pc.max; if (progress <= last) { stagnant++; if (stagnant >= 3) { log('🔴', '搜索进度停滞,提前结束', true); break; } } else { stagnant = 0; last = progress; } } } catch (_) { /* 忽略 */ } } } if (progress >= max) { GM_setValue('state.searchDate', todayNum()); log('🔵', `搜索任务完成:${progress}/${max}`, true); } else { log('🔵', `搜索任务结束:${progress}/${max}`, true); } } /* ============================================================ * 10. 主流程 * ============================================================ */ const STATE = { running: false }; async function runAll() { if (STATE.running) { log('🟡', '任务正在运行中,跳过本次触发'); return; } STATE.running = true; try { const startedAt = Date.now(); log('🚀', `开始执行 Microsoft Rewards 任务(环境:${IS_BACKGROUND ? 'service_worker' : '页面'})`); const token = await Auth.get(); if (!token) { log('🔴', '未获得有效 Token,任务终止。请通过菜单「🔑 手动授权」+「📋 粘贴授权码」完成授权。', true); return; } log('🟢', 'Token 就绪'); const startBalance = await Rewards.getQuota().then(q => q.balance).catch(() => 0); log('📊', `初始积分:${startBalance}`); if (GM_getValue('Tasks.sign', true)) { await taskSignIn(); await randSleep(); } if (GM_getValue('Tasks.search', true)) { await taskSearch(); await randSleep(); } if (GM_getValue('Tasks.read', true)) { await taskRead(); await randSleep(); } if (GM_getValue('Tasks.promos', true)) { await taskPromos(); } const endBalance = await Rewards.getQuota().then(q => q.balance).catch(() => 0); const earned = (startBalance > 0 && endBalance > 0) ? (endBalance - startBalance) : 0; const cost = ((Date.now() - startedAt) / 1000).toFixed(1); let summary = ''; try { const q = await Rewards.getQuota(); const read = await Rewards.getReadProgress(); summary = `签到\t${GM_getValue('state.signDate', 0) === todayNum() ? '✅' : '❌'}\n` + `阅读\t${read ? `${read.progress}/${read.max}` : '未知'}\n` + `搜索\t${q.pc.progress}/${q.pc.max}\n` + `活动\t${GM_getValue('state.promosDate', 0) === todayNum() ? '✅' : '❌'}\n` + `今日获取\t+${earned}\n` + `总积分\t${q.balance}\n` + `耗时\t${cost}s`; } catch (_) { summary = `任务完成,用时 ${cost}s,今日获取 +${earned}`; } log('📊', summary, true); } catch (e) { log('🔴', `任务异常: ${e.message}`, true); } finally { STATE.running = false; } } function isAllDoneToday() { const today = todayNum(); const check = (enabled, date) => !enabled || date === today; return check(GM_getValue('Tasks.sign', true), GM_getValue('state.signDate', 0)) && check(GM_getValue('Tasks.read', true), GM_getValue('state.readDate', 0)) && check(GM_getValue('Tasks.promos', true), GM_getValue('state.promosDate', 0)) && check(GM_getValue('Tasks.search', true), GM_getValue('state.searchDate', 0)); } function scheduleRun() { if (!GM_getValue('Config.keep', true) && isAllDoneToday()) { log('💤', '今日任务已全部完成'); return; } const delay = randInt(5000, 60000); log('⏳', `${Math.round(delay / 1000)} 秒后启动`); setTimeout(runAll, delay); } /* ============================================================ * 11. 菜单 * ============================================================ */ function registerMenus() { GM_registerMenuCommand('🚀 立即运行', () => runAll()); GM_registerMenuCommand('🔑 手动授权', () => { const url = `${CFG.authUrl}?client_id=${CFG.clientId}` + `&response_type=code` + `&scope=${encodeURIComponent(CFG.scope)}` + `&redirect_uri=${encodeURIComponent(CFG.redirectUri)}`; GM_openInTab(url, { active: true }); }); GM_registerMenuCommand('📋 粘贴授权码', () => { const code = prompt('粘贴授权页面跳转后的完整 URL(含 code=):'); if (code && code.trim()) { if (code.includes('removed=true')) { alert('检测到 removed=true,授权码已失效。请重新点击「🔑 手动授权」获取新的授权码。'); return; } GM_setValue('Config.code', code.trim()); GM_setValue('token.refresh', ''); alert('已保存,下次运行将自动换取 Token'); } }); GM_registerMenuCommand('🧹 清除 Token', () => { GM_setValue('token.refresh', ''); GM_setValue('token.time', 0); Auth.accessToken = null; alert('Token 已清除,下次运行将需要重新授权'); }); GM_registerMenuCommand('🔁 重置今日任务状态', () => { GM_setValue('state.signDate', 0); GM_setValue('state.readDate', 0); GM_setValue('state.promosDate', 0); GM_setValue('state.searchDate', 0); alert('今日任务状态已重置'); }); GM_registerMenuCommand('📊 Token 状态', () => { const token = GM_getValue('token.refresh', ''); const time = Number(GM_getValue('token.time', 0)); let age = '未知'; if (time > 0) { const diff = Date.now() - time; const days = Math.floor(diff / 86400000); const hours = Math.floor((diff % 86400000) / 3600000); const mins = Math.floor((diff % 3600000) / 60000); const parts = []; if (days) parts.push(`${days}天`); if (hours) parts.push(`${hours}小时`); parts.push(`${mins}分钟`); age = parts.join(''); } const when = time > 0 ? new Date(time).toLocaleString('zh-CN') : '未知'; alert(`Token: ${token ? '✅ 已保存' : '❌ 无'}\n获取时间: ${when}\n已过: ${age}`); }); GM_registerMenuCommand('📢 测试通知', async () => { await pushAll('🧪 这是一条测试消息,收到说明推送配置成功'); alert('测试消息已发送,请检查各推送渠道'); }); GM_registerMenuCommand('📋 查看通知配置状态', () => { const lines = notifiers.map(n => `${n.name}: ${n.key() ? '✅ 已配置' : '❌ 未配置'}`); alert(`📊 通知接口状态\n\n${lines.join('\n')}`); }); GM_registerMenuCommand('🔕 切换浏览器通知', () => { const cur = GM_getValue('Notice.bro', true); GM_setValue('Notice.bro', !cur); alert(`浏览器通知已${!cur ? '开启' : '关闭'}`); location.reload(); }); } /* ============================================================ * 12. 入口 * ============================================================ */ (function bootstrap() { if (!IS_BACKGROUND && location.hostname === 'login.live.com' && location.pathname === '/oauth20_desktop.srf') { const code = new URLSearchParams(location.search).get('code'); if (code) { GM_setValue('Config.code', location.href); GM_setValue('token.refresh', ''); if (GM_getValue('Notice.bro', true)) { try { GM_notification({ title: '🟢 授权成功', text: '授权码已捕获,可关闭此页面' }); } catch (_) { /* 忽略 */ } } try { history.replaceState({}, '', 'about:blank'); } catch (_) { /* 忽略 */ } setTimeout(() => { try { window.close(); } catch (_) { /* 忽略 */ } }, 300); } return; } if (!IS_BACKGROUND && (location.hostname === 'www.bing.com' || location.hostname === 'cn.bing.com')) { return; } if (!IS_BACKGROUND && location.hostname === 'rewards.bing.com') { registerMenus(); return; } registerMenus(); scheduleRun(); })(); })();