// ==UserScript== // @name Microsoft Rewards 全能自动化助手 // @namespace https://scriptcat.org/ // @version 2.0.0 // @description 对齐已验证的 DAPI 移动端通道:x-rewards-ismobile + x-rewards-appid + BingSapphire UA + options=613 + counters。签入(type 103)、阅读(ENUS_readarticle3_30points)、活动卡片(type 101)、PC/移动搜索走 reportActivity 上报,全部以服务端回包为准。 // @author you // @license MIT // @match https://cn.bing.com/* // @match https://www.bing.com/* // @match https://rewards.bing.com/* // @match https://login.live.com/oauth20_desktop.srf* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_notification // @grant GM_registerMenuCommand // @grant GM_openInTab // @connect prod.rewardsplatform.microsoft.com // @connect rewards.bing.com // @connect login.live.com // @connect cn.bing.com // @connect www.bing.com // @run-at document-end // ==/UserScript== (function () { 'use strict'; /* ============================ 常量 ============================ */ 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', country: 'cn', language: 'zh', 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', 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', }; const SEARCH_POOL = [ '天气预报', '今日新闻热点', '美食食谱家常菜', '旅游攻略', '健康养生知识', '科技资讯', '电影推荐', '股票行情', '体育赛事', '历史上的今天', '人工智能', '编程教程', '学习方法', '职场技能', '理财知识', 'how to learn rust programming', 'why is the sky blue', 'best noise cancelling headphones', 'easy chocolate chip cookies recipe', 'how do solar panels work', 'how to sleep better naturally', 'pomodoro technique for focus', 'healthy breakfast ideas', 'git rebase vs merge', 'what is the weather forecast tomorrow', 'how to start a vegetable garden', ]; 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', ]; const KEYS = { accessToken: 'mr2_access_token', refreshToken: 'mr2_refresh_token', expiresAt: 'mr2_expires_at', authCode: 'mr2_auth_code', settings: 'mr2_settings', }; const DEFAULT_SETTINGS = { sign: true, // 每日签入(App 端 type 103) read: true, // 新闻阅读 promos: true, // 每日活动 + 活动卡片 pcSearch: true, // PC 搜索 mobileSearch: true,// 移动搜索 readMax: 10, // 阅读最多连领篇数 searchInterval: 30,// 搜索间隔(秒) searchCap: 40, // 单轮搜索次数上限 }; /* ============================ 工具 ============================ */ const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const randSleep = (min = 2000, max = 5000) => 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 todayCookie = () => { const d = new Date(); return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`; }; const uuid = () => { try { if (crypto && crypto.randomUUID) return crypto.randomUUID().replace(/-/g, '').toUpperCase(); } catch (e) {} let s = ''; for (let i = 0; i < 32; i++) s += Math.floor(Math.random() * 16).toString(16); return s.toUpperCase(); }; const activityId = () => (uuid() + uuid()).slice(0, 64); const safeJson = (text, fallback = null) => { try { return JSON.parse(text); } catch (e) { return fallback; } }; function getSettings() { const stored = GM_getValue(KEYS.settings, {}) || {}; return { ...DEFAULT_SETTINGS, ...stored }; } function saveSettings(s) { GM_setValue(KEYS.settings, s); } function log(msg) { console.log('[Rewards助手] ' + msg); const el = document.getElementById('mr-log-area'); if (el) { const line = document.createElement('div'); line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; el.appendChild(line); el.scrollTop = el.scrollHeight; } } /* ============================ XHR ============================ */ function xhr(options) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', ...options, timeout: options.timeout || 20000, onload(res) { if (res.status >= 200 && res.status < 300) return resolve(res.responseText); reject(new Error(`HTTP ${res.status}`)); }, onerror: () => reject(new Error('网络错误')), ontimeout: () => reject(new Error('请求超时')), }); }); } /* ============================ 授权 ============================ */ const Auth = { async get(force = false) { const at = GM_getValue(KEYS.accessToken, ''); const exp = GM_getValue(KEYS.expiresAt, 0); if (!force && at && Date.now() < exp - 60000) return at; const refresh = GM_getValue(KEYS.refreshToken, ''); if (!force && refresh) { log('🔄 尝试刷新 Token...'); if (await this._request({ grant_type: 'refresh_token', refresh_token: refresh, scope: CFG.scope })) { return GM_getValue(KEYS.accessToken, ''); } log('🟡 Refresh Token 失效,需要重新授权'); } return null; }, async _request(params) { const body = new URLSearchParams({ client_id: CFG.clientId, redirect_uri: CFG.redirectUri, ...params, }).toString(); for (let i = 0; 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) throw new Error('响应非 JSON'); if (data.access_token) { GM_setValue(KEYS.accessToken, data.access_token); if (data.refresh_token) GM_setValue(KEYS.refreshToken, data.refresh_token); GM_setValue(KEYS.expiresAt, Date.now() + (data.expires_in || 3600) * 1000); return true; } if (['invalid_grant', 'invalid_request', 'invalid_client'].includes(data.error)) { if (data.error === 'invalid_grant') GM_setValue(KEYS.refreshToken, ''); return false; } } catch (e) { console.warn('[Rewards助手] Token 请求异常:', e.message); } await sleep(2000); } return false; }, async exchangeCode(code) { return this._request({ grant_type: 'authorization_code', code }); }, openAuthPage() { const url = `${CFG.authUrl}?client_id=${CFG.clientId}` + `&response_type=code` + `&scope=${encodeURIComponent(CFG.scope)}` + `&redirect_uri=${encodeURIComponent(CFG.redirectUri)}`; try { if (typeof GM_openInTab === 'function') GM_openInTab(url, { active: true }); else window.open(url, '_blank'); } catch (e) { window.open(url, '_blank'); } log('🟡 已打开授权页,登录并完成授权,脚本会自动捕获授权码'); }, }; /* ============================ DAPI ============================ */ // ★ 关键:所有 DAPI 请求都必须带移动 App 身份头,缺一个就拿不到 App 任务集。 const Rewards = { async dapi(path = '', { method = 'GET', body } = {}) { const token = await Auth.get(); if (!token) throw new Error('无有效 Token'); const 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': CFG.country, 'x-rewards-language': CFG.language, }; try { return await xhr({ method, url: CFG.dapiBase + path, headers, data: body ? JSON.stringify(body) : undefined, }); } catch (e) { if (/401/.test(e.message)) { log('🟡 DAPI 返回 401,清除 Token 后需重新授权'); GM_setValue(KEYS.refreshToken, ''); GM_setValue(KEYS.accessToken, ''); throw e; } 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 }; }, // 读余额 + 搜索配额(options=613 + counters) async getQuota() { const text = await this.dapi('?channel=SAAndroid&options=613'); const data = safeJson(text); if (!data || !data.response) throw new Error('DAPI 返回异常'); const resp = data.response; return { pc: this._parseCounter(resp.counters ? resp.counters.pcSearch : null), mobile: this._parseCounter(resp.counters ? resp.counters.mobileSearch : null), balance: Number(resp.balance || 0), raw: data, }; }, async getPromotions() { const text = await this.dapi('?channel=SAAndroid&options=613'); const data = safeJson(text); return (data && data.response && data.response.promotions) || []; }, async claim101(offerId, withChannel = true) { const body = { amount: 1, country: CFG.country, id: activityId(), type: 101, attributes: { offerid: offerId }, }; if (withChannel) body.channel = CFG.channel; const text = await this.dapi('/activities', { method: 'POST', body }); const data = safeJson(text) || {}; const resp = data.response || {}; return { points: Number(resp.activity && (resp.activity.p || resp.activity.points) || 0), duplicate: !!resp.isDuplicate, raw: resp, }; }, async signIn() { const body = { amount: 1, id: activityId(), type: 103, country: CFG.country, channel: CFG.channel, }; const text = await this.dapi('/activities', { method: 'POST', body }); const data = safeJson(text) || {}; const resp = data.response || {}; return { points: Number(resp.activity && (resp.activity.p || resp.activity.points) || 0), duplicate: !!resp.isDuplicate, raw: resp, }; }, }; /* ============================ 搜索上报 ============================ */ // 搜索积分不是靠打开标签页,而是靠 cn.bing.com/rewardsapp/reportActivity 上报。 // mobile=true 时用移动 UA + _Rwho=u=m,覆盖移动端配额。 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=${todayCookie()}`; 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`, }); } function pickQuery() { return SEARCH_POOL[randInt(0, SEARCH_POOL.length - 1)]; } /* ============================ 任务实现 ============================ */ async function taskSignIn() { try { const r = await Rewards.signIn(); if (r.points > 0) { log(`📱 App 签入 +${r.points}`); return { ok: true, points: r.points }; } if (r.duplicate) { log('📱 App 今日已签入'); return { ok: true, points: 0 }; } log('📱 App 签入完成(本次无新增积分)'); return { ok: true, points: 0 }; } catch (e) { log(`🟡 App 签入失败: ${e.message}`); return { ok: false, points: 0 }; } } async function findReadOffer() { try { const promos = await Rewards.getPromotions(); const cand = promos.find((p) => { const a = p.attributes || {}; const id = String(a.offerid || '').toLowerCase(); const t = String(a.type || '').toLowerCase(); return id.includes('readarticle') || id.includes('msnread') || id.includes('readtoearn') || t === 'msnreadearn' || t.includes('read'); }); return cand ? cand.attributes.offerid : CFG.offerRead; } catch (e) { return CFG.offerRead; } } async function taskRead() { const settings = getSettings(); const offerRead = await findReadOffer(); let cur = 0, max = 30; try { const promos = await Rewards.getPromotions(); const task = promos.find((p) => (p.attributes || {}).offerid === offerRead); if (task && task.attributes) { cur = parseInt(task.attributes.progress) || 0; max = parseInt(task.attributes.max) || 30; } } catch (e) { /* 忽略,用默认 */ } if (cur >= max) { log(`📖 阅读已完成 ${cur}/${max}`); return { ok: true, points: 0 }; } log(`📖 阅读进度 ${cur}/${max}(offerid=${offerRead})`); const limit = Math.min(max - cur, settings.readMax || 10); let gained = 0; for (let i = 0; i < limit; i++) { try { const r = await Rewards.claim101(offerRead, false); if (r.duplicate) { log('📖 阅读已达上限'); break; } if (r.points > 0) { gained += r.points; cur++; log(`📖 阅读第 ${i + 1} 篇 +${r.points}`); } else break; } catch (e) { log(`🟡 阅读请求失败: ${e.message}`); break; } await randSleep(); } if (gained > 0) log(`🔵 阅读完成 ${cur}/${max}(本次 +${gained})`); else log('🟡 本次阅读未获得积分'); return { ok: gained > 0, points: gained }; } async function discoverCards() { const settings = getSettings(); const cards = []; const seen = new Set(); let promos; try { promos = await Rewards.getPromotions(); } catch (e) { log(`🟡 扫描卡片失败: ${e.message}`); return cards; } for (const p of promos) { const a = p.attributes || {}; const offerId = a.offerid; if (!offerId) continue; const lowerId = offerId.toLowerCase(); if (lowerId.includes('checkin') || lowerId.includes('check-in') || lowerId.includes('dailycheckin')) continue; if (lowerId.includes('readarticle') || lowerId.includes('msnread') || lowerId.includes('readtoearn')) continue; const progress = parseInt(a.progress) || 0; const max = parseInt(a.max) || 1; if (max > 0 && progress >= max) continue; const title = String(p.name || a.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); cards.push({ offerId, title, url: a.destinationUrl || a.url || '' }); } return cards; } async function taskPromos() { log('🧩 扫描活动卡片...'); const cards = await discoverCards(); if (!cards.length) { log('🧩 今日无待处理卡片'); return { ok: true, points: 0 }; } log(`🧩 发现 ${cards.length} 个卡片`); let ok = 0, gained = 0; for (const card of cards) { try { const r = await Rewards.claim101(card.offerId, true); if (r.duplicate) { log(` ✓ ${card.title || card.offerId} 已完成`); ok++; continue; } if (r.points > 0) { gained += r.points; log(` ✓ ${card.title || card.offerId} +${r.points}`); ok++; } else log(` • ${card.title || card.offerId} +0`); } catch (e) { log(`🟡 卡片失败(${card.offerId}): ${e.message}`); } await randSleep(); } log(`🔵 活动完成:${ok} 项,+${gained} 积分`); return { ok: ok > 0, points: gained }; } async function taskSearch(kind) { const settings = getSettings(); const label = kind === 'pc' ? 'PC 搜索' : '移动搜索'; let quota; try { quota = await Rewards.getQuota(); } catch (e) { log(`🔴 无法获取搜索配额: ${e.message}`); return { ok: false, points: 0 }; } const counter = kind === 'pc' ? quota.pc : quota.mobile; let { progress, max } = counter; if (!max) { log(kind === 'pc' ? '🟡 PC 搜索配额为 0,跳过' : '🟡 本账号无独立移动搜索配额(已由 PC 搜索覆盖)'); return { ok: true, points: 0 }; } if (progress >= max) { log(`✅ ${label}已完成 ${progress}/${max}`); return { ok: true, points: 0 }; } log(`🔍 ${label}进度 ${progress}/${max}`); const span = Math.max(15, settings.searchInterval || 30); const cap = settings.searchCap || 40; let stagnant = 0; let last = progress; let gained = 0; for (let i = 0; i < cap && progress < max; i++) { const query = pickQuery(); log(`🔍 搜索 ${progress}/${max}: ${query}`); try { await performSearch(query, kind === 'mobile'); } catch (e) { log(`🟡 搜索失败: ${e.message}`); } await sleep(randInt((span - 10) * 1000, (span + 10) * 1000)); if (i % 2 === 1) { try { const q = await Rewards.getQuota(); const c = kind === 'pc' ? q.pc : q.mobile; if (c.max > 0) { progress = c.progress; max = c.max; if (progress <= last) { stagnant++; if (stagnant >= 3) { log('🔴 搜索进度停滞,提前结束'); break; } } else { stagnant = 0; last = progress; } } } catch (e) { /* 忽略 */ } } } gained = Math.max(0, progress - last === 0 ? 0 : progress); // 重算真实增量 try { const q2 = await Rewards.getQuota(); const c2 = kind === 'pc' ? q2.pc : q2.mobile; gained = Math.max(0, c2.progress - counter.progress); progress = c2.progress; max = c2.max; } catch (e) {} log(`🔵 ${label}结束:${progress}/${max}(本次 +${gained})`); return { ok: gained > 0 || progress >= max, points: gained }; } /* ============================ 主流程 ============================ */ let running = false; let uiState = { total: 0, done: 0, points: 0, balance: 0, countdown: 0 }; function updateUI() { const btn = document.getElementById('mr-btn-start'); if (btn) { btn.textContent = running ? '⏹ 停止执行' : '▶ 开始执行'; btn.style.background = running ? '#e74c3c' : '#0078d4'; } const bar = document.getElementById('mr-progress-bar'); const txt = document.getElementById('mr-progress-text'); if (bar && txt) { const pct = uiState.total > 0 ? Math.round((uiState.done / uiState.total) * 100) : 0; bar.style.width = pct + '%'; txt.textContent = `${uiState.done} / ${uiState.total} 任务 (${pct}%)`; } const st = document.getElementById('mr-stats'); if (st) st.textContent = `💰 今日 +${uiState.points} | 🏆 余额 ${uiState.balance}`; } async function runAll() { if (running) { log('🟡 任务正在运行中'); return; } running = true; updateUI(); const settings = getSettings(); const tasks = []; if (settings.sign) tasks.push(['签到', taskSignIn]); if (settings.pcSearch) tasks.push(['PC 搜索', () => taskSearch('pc')]); if (settings.mobileSearch) tasks.push(['移动搜索', () => taskSearch('mobile')]); if (settings.read) tasks.push(['阅读', taskRead]); if (settings.promos) tasks.push(['活动卡片', taskPromos]); uiState.total = tasks.length; uiState.done = 0; uiState.points = 0; updateUI(); try { const token = await Auth.get(); if (!token) { log('🔴 未获得有效 Token,正在打开授权页...'); Auth.openAuthPage(); return; } log('🟢 Token 就绪'); let balance = 0; try { balance = (await Rewards.getQuota()).balance; } catch (e) {} log(`📊 初始余额:${balance}`); uiState.balance = balance; updateUI(); for (const [name, fn] of tasks) { if (!running) break; log(`▶ 开始:${name}`); try { const r = await fn(); uiState.points += r.points || 0; } catch (e) { log(`🟡 ${name}异常: ${e.message}`); } uiState.done++; updateUI(); await randSleep(); } try { balance = (await Rewards.getQuota()).balance; } catch (e) {} uiState.balance = balance; updateUI(); log(`🎉 执行完毕:今日 +${uiState.points},余额 ${balance}`); try { GM_notification({ title: 'Rewards 助手', text: `执行完毕:今日 +${uiState.points},余额 ${balance}`, timeout: 8000 }); } catch (e) {} } catch (e) { log(`🔴 任务异常: ${e.message}`); } finally { running = false; updateUI(); } } /* ============================ 诊断 ============================ */ async function dumpData() { log('🔬 正在抓取服务端真实数据...'); try { const token = await Auth.get(); if (!token) { log('🔴 无 Token,请先点「开始执行」完成授权'); return; } const q = await Rewards.getQuota(); log(` 余额 ${q.balance} | PC ${q.pc.progress}/${q.pc.max} | 移动 ${q.mobile.progress}/${q.mobile.max}`); const promos = await Rewards.getPromotions(); log(` 共 ${promos.length} 个任务:`); promos.forEach((p) => { const a = p.attributes || {}; log(` · [${a.type || '?'}] ${p.name || a.title || a.offerid}` + ` | offerid=${a.offerid || '-'} | complete=${a.complete} | ${a.progress || 0}/${a.max || 0}`); }); try { await navigator.clipboard.writeText(JSON.stringify(q.raw, null, 2)); log(' 📋 完整 JSON 已复制到剪贴板'); } catch (e) { log(' ⚠ 复制失败,请按 F12 从控制台复制'); } console.log('[Rewards助手] DAPI 原始数据', q.raw); } catch (e) { log('🔴 抓取失败: ' + e.message); } } /* ============================ UI ============================ */ function createFloatWindow() { const settings = getSettings(); GM_addStyle(` #mr-float-container{position:fixed;top:20px;right:20px;z-index:999999;width:380px; max-height:calc(100vh - 40px);display:flex;flex-direction:column; font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;color:#1d1d1f; background:rgba(255,255,255,0.9);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px); border:1px solid rgba(0,0,0,0.08);border-radius:14px;box-shadow:0 8px 40px rgba(0,0,0,0.18);overflow:hidden;} #mr-float-header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;cursor:move; background:rgba(255,255,255,0.5);border-bottom:1px solid rgba(0,0,0,0.08);} #mr-float-header .title{font-weight:600;} #mr-float-header .close{cursor:pointer;opacity:0.6;padding:2px 6px;} #mr-float-header .close:hover{opacity:1;} #mr-float-body{padding:12px 14px;overflow-y:auto;} #mr-progress-text{font-size:11px;opacity:0.75;text-align:center;margin-bottom:6px;} #mr-progress-track{height:6px;background:rgba(0,0,0,0.08);border-radius:3px;overflow:hidden;} #mr-progress-bar{height:100%;width:0;background:linear-gradient(90deg,#0078d4,#28a745);border-radius:3px;transition:width .4s;} #mr-log-area{height:150px;overflow-y:auto;font-size:11px;line-height:1.6;padding:8px;margin:8px 0; background:rgba(0,0,0,0.03);border-radius:8px;font-family:Menlo,Consolas,monospace;} #mr-stats{font-size:11px;opacity:0.8;text-align:center;padding:4px 0;} .mr-btn{display:block;width:100%;padding:8px 16px;border:none;border-radius:8px;font-size:13px; font-weight:500;cursor:pointer;background:#0078d4;color:#fff;margin-top:6px;} .mr-btn.sec{background:rgba(0,0,0,0.06);color:#1d1d1f;} .mr-row{display:flex;align-items:center;justify-content:space-between;padding:5px 0;font-size:12px;} .mr-row input[type=number]{width:60px;padding:3px 6px;border:1px solid rgba(0,0,0,0.12);border-radius:6px;font-size:12px;} .mr-row input[type=checkbox]{width:16px;height:16px;} #mr-settings{display:none;margin-top:8px;} #mr-settings.show{display:block;} `); const container = document.createElement('div'); container.id = 'mr-float-container'; container.innerHTML = `
🎯 Rewards 助手 v2.0 ✕
0 / 0 任务
💰 今日 +0 | 🏆 余额 0
`; document.body.appendChild(container); document.getElementById('mr-btn-close').onclick = () => container.remove(); document.getElementById('mr-btn-start').onclick = () => { if (running) { running = false; updateUI(); log('⏹ 已请求停止'); } else runAll(); }; document.getElementById('mr-btn-dump').onclick = () => dumpData(); document.getElementById('mr-btn-settings').onclick = () => { document.getElementById('mr-settings').classList.toggle('show'); }; document.getElementById('mr-btn-save').onclick = () => { saveSettings({ sign: document.getElementById('s-sign').checked, read: document.getElementById('s-read').checked, promos: document.getElementById('s-promos').checked, pcSearch: document.getElementById('s-pc').checked, mobileSearch: document.getElementById('s-mobile').checked, readMax: parseInt(document.getElementById('s-readmax').value) || 10, searchInterval: parseInt(document.getElementById('s-span').value) || 30, searchCap: parseInt(document.getElementById('s-cap').value) || 40, }); log('💾 设置已保存'); }; // 拖拽 const header = document.getElementById('mr-float-header'); let ox = 0, oy = 0, dragging = false; header.addEventListener('mousedown', (e) => { dragging = true; ox = e.clientX - container.getBoundingClientRect().left; oy = e.clientY - container.getBoundingClientRect().top; }); document.addEventListener('mousemove', (e) => { if (!dragging) return; container.style.left = (e.clientX - ox) + 'px'; container.style.top = (e.clientY - oy) + 'px'; container.style.right = 'auto'; }); document.addEventListener('mouseup', () => { dragging = false; }); try { GM_registerMenuCommand('🚀 立即运行 Rewards', () => runAll()); GM_registerMenuCommand('🔬 抓取 Rewards 数据', () => dumpData()); GM_registerMenuCommand('🔑 手动授权', () => Auth.openAuthPage()); GM_registerMenuCommand('🧹 清除 Token', () => { GM_setValue(KEYS.refreshToken, ''); GM_setValue(KEYS.accessToken, ''); GM_setValue(KEYS.expiresAt, 0); alert('Token 已清除,下次运行需重新授权'); }); } catch (e) {} } /* ============================ 入口 ============================ */ function main() { // 授权回调页:捕获 code 并立即换 Token if (location.hostname === 'login.live.com' && location.pathname === '/oauth20_desktop.srf') { const code = new URLSearchParams(location.search).get('code'); if (code) { Auth.exchangeCode(code).then((ok) => { if (ok) { try { GM_notification({ title: 'Rewards 助手', text: '授权成功!可关闭此页' }); } catch (e) {} setTimeout(() => { location.href = 'https://rewards.bing.com/'; }, 1200); } }); } return; } if (location.hostname === 'cn.bing.com' || location.hostname === 'www.bing.com') { // 搜索页不建悬浮窗,避免干扰 return; } if (location.hostname === 'rewards.bing.com') { const init = () => { if (document.body) { createFloatWindow(); log('助手已就绪。首次使用请点「▶ 开始执行」完成授权。'); log('点「🔬 抓取 Rewards 数据」可查看服务端真实任务清单。'); } else setTimeout(init, 300); }; if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); } } main(); })();