// ==UserScript== // @name Microsoft Rewards 全能自动化助手 // @namespace https://scriptcat.org/zh-CN/ // @version 1.0.0 // @description 自动完成Bing Rewards每日任务:每日活动、App签到、阅读赚取、PC搜索、视觉搜索。支持双主题悬浮窗与自定义任务顺序。 // @author YourName // @match https://cn.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 // @connect prod.rewardsplatform.microsoft.com // @connect rewards.bing.com // @connect login.live.com // @connect api.bilibili.com // @connect top.baidu.com // @connect api.zhihu.com // @connect * // @run-at document-end // @license MIT // ==/UserScript== (function () { 'use strict'; // ==================== 常量配置 ==================== const DAPI_BASE = 'https://prod.rewardsplatform.microsoft.com/dapi'; const CLIENT_ID = '0000000040170455'; const REDIRECT_URI = 'https://login.live.com/oauth20_desktop.srf'; const SCOPE = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL'; const STORAGE_KEYS = { ACCESS_TOKEN: 'mr_access_token', REFRESH_TOKEN: 'mr_refresh_token', EXPIRES_AT: 'mr_expires_at', SETTINGS: 'mr_settings', TASK_ORDER: 'mr_task_order', RUNNING: 'mr_running', }; // 默认设置 const DEFAULT_SETTINGS = { theme: 'win11', // 'win11' | 'macos' searchStayMin: 2, // PC搜索页面停留时间(分钟) readStayMin: 2, // 移动端阅读停留时间(分钟) searchInterval: [15, 30], // 搜索间隔范围(秒) maxSearches: 30, // 每日最大搜索次数 enableDailySet: true, // 每日任务 enableAppCheckin: true, // App签到 enableAppRead: true, // App阅读 enableMoreActivities: true, // 更多活动 enablePCSearch: true, // PC搜索 enableVisualSearch: true, // 视觉搜索 }; // 默认任务执行顺序 const DEFAULT_TASK_ORDER = [ 'dailySet', 'appCheckin', 'appRead', 'moreActivities', 'pcSearch', 'visualSearch', ]; const TASK_LABELS = { dailySet: '每日任务', appCheckin: 'App签到', appRead: 'App阅读', moreActivities: '更多活动', pcSearch: 'PC搜索', visualSearch: '视觉搜索', }; // ==================== 工具函数 ==================== const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; function log(msg) { console.log(`[Rewards助手] ${msg}`); // 同步到悬浮窗日志区域 const logEl = document.getElementById('mr-log-area'); if (logEl) { const line = document.createElement('div'); line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`; logEl.appendChild(line); logEl.scrollTop = logEl.scrollHeight; } } // ==================== 存储管理 ==================== function getSettings() { return { ...DEFAULT_SETTINGS, ...GM_getValue(STORAGE_KEYS.SETTINGS, {}) }; } function saveSettings(s) { GM_setValue(STORAGE_KEYS.SETTINGS, s); } function getTaskOrder() { return GM_getValue(STORAGE_KEYS.TASK_ORDER, [...DEFAULT_TASK_ORDER]); } function saveTaskOrder(o) { GM_setValue(STORAGE_KEYS.TASK_ORDER, o); } // ==================== OAuth 授权 ==================== // 参考:微软Rewards前台授权助手的设计思路 // https://scriptcat.org/zh-CN/script-show-page/3956 function buildAuthUrl() { return ( `https://login.live.com/oauth20_authorize.srf?client_id=${CLIENT_ID}` + `&scope=${encodeURIComponent(SCOPE)}` + `&response_type=code` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&display=touch` ); } // 在 oauth20_desktop.srf 页面捕获授权码 function captureAuthCode() { const url = new URL(location.href); const code = url.searchParams.get('code'); if (code) { GM_setValue('mr_auth_code', code); log('已捕获授权码,正在交换Token...'); exchangeCodeForToken(code); } } // 用授权码换取 access_token function exchangeCodeForToken(code) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://login.live.com/oauth20_token.srf', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `client_id=${CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&code=${code}` + `&grant_type=authorization_code` + `&scope=${encodeURIComponent(SCOPE)}`, onload(resp) { try { const data = JSON.parse(resp.responseText); if (data.access_token) { GM_setValue(STORAGE_KEYS.ACCESS_TOKEN, data.access_token); if (data.refresh_token) GM_setValue(STORAGE_KEYS.REFRESH_TOKEN, data.refresh_token); GM_setValue( STORAGE_KEYS.EXPIRES_AT, Date.now() + (data.expires_in || 3600) * 1000 ); log('Token获取成功'); GM_notification({ text: 'Rewards授权成功!', title: 'Rewards助手' }); resolve(data.access_token); } else { log('Token交换失败: ' + resp.responseText); reject(new Error('Token exchange failed')); } } catch (e) { reject(e); } }, onerror: reject, }); }); } // 用 refresh_token 刷新 access_token function refreshAccessToken() { const refreshToken = GM_getValue(STORAGE_KEYS.REFRESH_TOKEN, ''); if (!refreshToken) return Promise.reject(new Error('无refresh_token')); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: 'https://login.live.com/oauth20_token.srf', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, data: `client_id=${CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` + `&refresh_token=${refreshToken}` + `&grant_type=refresh_token` + `&scope=${encodeURIComponent(SCOPE)}`, onload(resp) { try { const data = JSON.parse(resp.responseText); if (data.access_token) { GM_setValue(STORAGE_KEYS.ACCESS_TOKEN, data.access_token); if (data.refresh_token) GM_setValue(STORAGE_KEYS.REFRESH_TOKEN, data.refresh_token); GM_setValue( STORAGE_KEYS.EXPIRES_AT, Date.now() + (data.expires_in || 3600) * 1000 ); resolve(data.access_token); } else { reject(new Error('刷新失败')); } } catch (e) { reject(e); } }, onerror: reject, }); }); } async function getValidToken() { let token = GM_getValue(STORAGE_KEYS.ACCESS_TOKEN, ''); const expiresAt = GM_getValue(STORAGE_KEYS.EXPIRES_AT, 0); if (!token || Date.now() > expiresAt - 60000) { try { token = await refreshAccessToken(); } catch (e) { log('Token刷新失败,需要重新授权'); return null; } } return token; } // ==================== Rewards API 封装 ==================== // 参考DAPI接口文档:https://prod.rewardsplatform.microsoft.com/dapi function apiRequest(method, path, token, body = null) { const settings = getSettings(); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method, url: `${DAPI_BASE}${path}`, headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'X-Rewards-Country': 'cn', 'X-Rewards-Language': 'zh', }, data: body ? JSON.stringify(body) : undefined, onload(resp) { if (resp.status >= 200 && resp.status < 300) { try { resolve(JSON.parse(resp.responseText)); } catch (e) { resolve({}); } } else if (resp.status === 401) { reject(new Error('AUTH_EXPIRED')); } else { reject(new Error(`API ${resp.status}`)); } }, onerror: reject, }); }); } // 获取用户数据(余额+任务列表) async function fetchUserData(token) { const resp = await apiRequest( 'GET', '/me?channel=SAAndroid&options=511', token ); return resp.response || {}; } // 完成任务(通用活动完成接口) async function completeActivity(token, offerId) { return apiRequest('POST', '/me/activities', token, { type: 101, attributes: { offerid: offerId }, }); } // 上报移动端搜索 async function reportMobileSearch(token, query) { return apiRequest('POST', '/me/activities', token, { type: 102, attributes: { query }, }); } // ==================== 任务执行引擎 ==================== class TaskRunner { constructor() { this.running = false; this.token = null; this.settings = getSettings(); this.taskOrder = getTaskOrder(); this.stats = { completed: 0, failed: 0, points: 0 }; } async init() { this.token = await getValidToken(); if (!this.token) { log('❌ 无有效Token,请先授权'); // 自动打开授权页 const authUrl = buildAuthUrl(); window.open(authUrl, '_blank'); return false; } log('✅ Token有效,准备执行任务'); return true; } async runAll() { if (this.running) return; this.running = true; this.stats = { completed: 0, failed: 0, points: 0 }; updateUI(); const ok = await this.init(); if (!ok) { this.running = false; updateUI(); return; } for (const taskId of this.taskOrder) { if (!this.running) break; if (!this.settings[`enable${taskId.charAt(0).toUpperCase() + taskId.slice(1)}`]) continue; log(`▶ 开始执行:${TASK_LABELS[taskId]}`); try { await this.executeTask(taskId); } catch (e) { log(`⚠ ${TASK_LABELS[taskId]} 执行异常: ${e.message}`); } await sleep(2000); } log('🎉 所有任务执行完毕'); this.running = false; updateUI(); } async executeTask(taskId) { switch (taskId) { case 'dailySet': await this.doDailySet(); break; case 'appCheckin': await this.doAppCheckin(); break; case 'appRead': await this.doAppRead(); break; case 'moreActivities': await this.doMoreActivities(); break; case 'pcSearch': await this.doPCSearch(); break; case 'visualSearch': await this.doVisualSearch(); break; } } // ---- 每日任务 ---- async doDailySet() { const data = await fetchUserData(this.token); const promos = (data.promotions || []).filter( (p) => p.attributes?.type === 'urlreward' && !p.attributes?.complete ); for (const p of promos) { if (!this.running) break; const offerId = p.attributes?.offerid; if (!offerId) continue; try { await completeActivity(this.token, offerId); this.stats.completed++; this.stats.points += parseInt(p.attributes?.points || '0'); log(` ✓ 完成: ${p.name || offerId}`); } catch (e) { this.stats.failed++; } await sleep(rand(2000, 4000)); } } // ---- App签到 ---- async doAppCheckin() { const data = await fetchUserData(this.token); const checkin = (data.promotions || []).find( (p) => p.attributes?.type === 'checkin' && !p.attributes?.complete ); if (checkin) { try { await completeActivity(this.token, checkin.attributes?.offerid); this.stats.completed++; log(' ✓ App签到完成'); } catch (e) { log(' ⚠ App签到失败: ' + e.message); } } else { log(' - App签到已完成或不存在'); } } // ---- App阅读 ---- async doAppRead() { const data = await fetchUserData(this.token); const reads = (data.promotions || []).filter( (p) => p.attributes?.type === 'msnreadearn' && !p.attributes?.complete ); for (const r of reads) { if (!this.running) break; try { // 模拟阅读:打开文章页面并停留 const url = r.destination || r.attributes?.destination; if (url) { await this.openAndStay(url, this.settings.readStayMin); } await completeActivity(this.token, r.attributes?.offerid); this.stats.completed++; log(` ✓ 阅读完成: ${r.name || '文章'}`); } catch (e) { this.stats.failed++; log(` ⚠ 阅读失败: ${e.message}`); } await sleep(rand(3000, 6000)); } if (reads.length === 0) log(' - 阅读任务已完成或不存在'); } // ---- 更多活动 ---- async doMoreActivities() { const data = await fetchUserData(this.token); const acts = (data.promotions || []).filter( (p) => !p.attributes?.complete && !['urlreward', 'checkin', 'msnreadearn', 'search'].includes( p.attributes?.type ) && p.destination ); for (const a of acts) { if (!this.running) break; const url = a.destination || a.attributes?.destination; if (url) { await this.openAndStay(url, 1); } try { await completeActivity(this.token, a.attributes?.offerid); this.stats.completed++; log(` ✓ 完成活动: ${a.name || a.attributes?.offerid}`); } catch (e) { this.stats.failed++; } await sleep(rand(3000, 5000)); } } // ---- PC搜索 ---- async doPCSearch() { const data = await fetchUserData(this.token); const pcPromo = (data.promotions || []).find( (p) => p.attributes?.type === 'search' && p.attributes?.classificationTag === 'PCSearch' ); if (!pcPromo) { log(' - 未找到PC搜索任务'); return; } const pcCounter = pcPromo.attributes?.pcCounter || pcPromo.attributes; const max = parseInt(pcCounter?.originalmax || pcCounter?.max || this.settings.maxSearches); const progress = parseInt(pcCounter?.progress || 0); const remaining = Math.max(0, max - progress); log(` - PC搜索: ${progress}/${max},还需 ${remaining} 次`); const queries = await getSearchQueries(remaining); for (let i = 0; i < queries.length && this.running; i++) { const q = queries[i]; await this.simulateSearch(q); this.stats.completed++; // 搜索后倒计时停留 if (i < queries.length - 1) { log(` ⏳ 等待 ${this.settings.searchStayMin} 分钟后继续...`); await sleep(this.settings.searchStayMin * 60 * 1000); } } } // ---- 视觉搜索 ---- async doVisualSearch() { // 视觉搜索通过Bing图片搜索完成 const queries = ['猫', '风景', '科技', '美食', '旅行']; for (const q of queries.slice(0, 3)) { if (!this.running) break; window.open(`https://cn.bing.com/images/search?q=${encodeURIComponent(q)}`, '_blank'); await sleep(5000); log(` ✓ 视觉搜索: ${q}`); } } // 模拟人工搜索输入 async simulateSearch(query) { log(` 🔍 搜索: "${query}"`); // 在新标签页打开搜索 const url = `https://cn.bing.com/search?q=${encodeURIComponent(query)}&form=QBLH`; window.open(url, '_blank'); // 停留一段时间模拟阅读 await sleep(rand(5000, 10000)); } // 打开页面并停留指定分钟 async openAndStay(url, minutes) { const win = window.open(url, '_blank'); await sleep(minutes * 60 * 1000); try { if (win && !win.closed) win.close(); } catch (e) {} } } // ==================== 搜索热词获取 ==================== async function getSearchQueries(count) { const queries = []; // 尝试从热搜API获取 try { const hotwords = await fetchHotwords(); queries.push(...hotwords); } catch (e) { log('热搜获取失败,使用内置词库'); } // 补充内置词库 const builtin = [ '今日新闻', '天气预报', '科技动态', '健康养生', '电影推荐', '旅游攻略', '美食菜谱', '股票行情', '人工智能', '编程教程', '历史故事', '音乐推荐', '体育赛事', '游戏攻略', '学习方法', '职场技能', '理财知识', '育儿经验', '汽车评测', '数码产品', ]; const shuffled = builtin.sort(() => Math.random() - 0.5); while (queries.length < count) { queries.push(shuffled[queries.length % shuffled.length]); } // 去重并截取 return [...new Set(queries)].slice(0, count); } async function fetchHotwords() { return new Promise((resolve, reject) => { // 使用B站热搜接口(无需鉴权) GM_xmlhttpRequest({ method: 'GET', url: 'https://api.bilibili.com/x/web-interface/search/square?limit=20', headers: { 'User-Agent': 'Mozilla/5.0' }, timeout: 8000, onload(resp) { try { const data = JSON.parse(resp.responseText); const list = data?.data?.trending?.list || []; resolve(list.map((item) => item.keyword).filter(Boolean)); } catch (e) { reject(e); } }, onerror: reject, ontimeout: reject, }); }); } // ==================== 悬浮窗 UI ==================== let uiState = { running: false, stats: { completed: 0, failed: 0, points: 0 } }; function updateUI() { const btn = document.getElementById('mr-btn-start'); if (btn) { btn.textContent = uiState.running ? '⏹ 停止' : '▶ 开始执行'; btn.style.background = uiState.running ? '#e74c3c' : '#0078d4'; } const statsEl = document.getElementById('mr-stats'); if (statsEl) { statsEl.innerHTML = `完成: ${uiState.stats.completed} | 失败: ${uiState.stats.failed} | 积分: ${uiState.stats.points}`; } } function createFloatWindow() { const settings = getSettings(); const isDark = settings.theme === 'win11'; const bg = isDark ? 'rgba(32,32,32,0.92)' : 'rgba(255,255,255,0.72)'; const textColor = isDark ? '#e0e0e0' : '#1d1d1f'; const borderColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)'; const cardBg = isDark ? 'rgba(255,255,255,0.05)' : 'rgba(255,255,255,0.5)'; // 注入样式 GM_addStyle(` #mr-float-container { position: fixed; top: 20px; right: 20px; z-index: 999999; width: 360px; font-family: "Segoe UI", -apple-system, sans-serif; font-size: 13px; color: ${textColor}; background: ${bg}; backdrop-filter: blur(30px) saturate(180%); -webkit-backdrop-filter: blur(30px) saturate(180%); border: 1px solid ${borderColor}; border-radius: 12px; box-shadow: 0 8px 32px rgba(0,0,0,0.25); overflow: hidden; transition: all 0.3s ease; user-select: none; } #mr-float-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px; cursor: move; background: ${isDark ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.4)'}; border-bottom: 1px solid ${borderColor}; } #mr-float-header .title { font-weight: 600; font-size: 14px; } #mr-float-header .close-btn { cursor: pointer; opacity: 0.6; font-size: 16px; line-height: 1; padding: 2px 6px; border-radius: 6px; } #mr-float-header .close-btn:hover { opacity: 1; background: rgba(128,128,128,0.2); } #mr-float-body { padding: 12px 14px; } #mr-log-area { height: 140px; overflow-y: auto; font-size: 11px; line-height: 1.6; padding: 8px; margin: 8px 0; background: ${cardBg}; border-radius: 8px; font-family: "Cascadia Code", "Consolas", monospace; scrollbar-width: thin; } #mr-log-area::-webkit-scrollbar { width: 4px; } #mr-log-area::-webkit-scrollbar-thumb { background: rgba(128,128,128,0.3); border-radius: 2px; } #mr-stats { font-size: 11px; opacity: 0.7; text-align: center; padding: 4px 0; } .mr-btn { display: inline-flex; align-items: center; justify-content: center; padding: 8px 16px; border: none; border-radius: 8px; font-size: 13px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; width: 100%; background: #0078d4; color: #fff; } .mr-btn:hover { filter: brightness(1.1); transform: translateY(-1px); } .mr-btn:active { transform: translateY(0); } .mr-btn-secondary { background: ${isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)'}; color: ${textColor}; margin-top: 6px; } .mr-btn-secondary:hover { background: ${isDark ? 'rgba(255,255,255,0.12)' : 'rgba(0,0,0,0.1)'}; } .mr-setting-row { display: flex; align-items: center; justify-content: space-between; padding: 6px 0; } .mr-setting-row label { font-size: 12px; opacity: 0.8; } .mr-setting-row input[type="number"] { width: 60px; padding: 4px 8px; border: 1px solid ${borderColor}; border-radius: 6px; background: ${cardBg}; color: ${textColor}; font-size: 12px; text-align: center; outline: none; } .mr-setting-row input[type="number"]:focus { border-color: #0078d4; } .mr-setting-row input[type="checkbox"] { accent-color: #0078d4; } .mr-setting-section { margin-top: 8px; } .mr-setting-section .section-title { font-size: 11px; font-weight: 600; opacity: 0.5; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; } .mr-task-order-item { display: flex; align-items: center; gap: 6px; padding: 4px 8px; margin: 2px 0; background: ${cardBg}; border-radius: 6px; font-size: 12px; cursor: grab; } .mr-task-order-item .drag-handle { opacity: 0.4; cursor: grab; } .mr-task-order-item .task-name { flex: 1; } .mr-task-order-item .toggle { font-size: 14px; cursor: pointer; } #mr-settings-panel { display: none; margin-top: 8px; } #mr-settings-panel.visible { display: block; } .mr-divider { height: 1px; background: ${borderColor}; margin: 8px 0; } `); // 构建DOM const container = document.createElement('div'); container.id = 'mr-float-container'; container.innerHTML = `