// ==UserScript== // @name Microsoft Rewards 全能自动化助手 // @namespace https://scriptcat.org/zh-CN/ // @version 1.0.2 // @description 自动完成Bing Rewards每日任务:每日活动、App签到、阅读赚取、PC搜索、视觉搜索。支持双主题悬浮窗、自定义任务顺序、随机执行与自动完成检测。 // @author YourName // @license MIT // @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 * // @run-at document-end // ==/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', }; const DEFAULT_SETTINGS = { theme: 'win11', searchStayMin: 2, readStayMin: 2, maxSearches: 30, randomOrder: false, // ★ 新增:随机任务顺序 enableDailySet: true, enableAppCheckin: true, enableAppRead: true, enableMoreActivities: true, enablePCSearch: true, 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 shuffleArray(arr) { const a = [...arr]; for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; } 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 授权 ==================== 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` ); } 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); } } 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, }); }); } 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 ==================== function apiRequest(method, path, token, body = null) { 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 }, }); } // ==================== 任务执行引擎 ==================== class TaskRunner { constructor() { this.running = false; this.token = null; this.settings = getSettings(); this.baseOrder = getTaskOrder(); // ★ 若用户勾选"随机执行顺序",打乱一次(本次执行内固定顺序) this.taskOrder = this.settings.randomOrder ? shuffleArray(this.baseOrder) : [...this.baseOrder]; this.stats = { completed: 0, failed: 0, points: 0 }; this.openedWindows = []; this.allDone = false; // ★ 所有任务已完成标记 } stop() { this.running = false; this.closeAllWindows(); } closeAllWindows() { this.openedWindows.forEach((w) => { try { if (w && !w.closed) w.close(); } catch (e) {} }); this.openedWindows = []; } async wait(ms) { const end = Date.now() + ms; while (Date.now() < end) { if (!this.running) return false; await sleep(Math.min(200, end - Date.now())); } return this.running; } openTracked(url) { const w = window.open(url, '_blank'); if (w) this.openedWindows.push(w); return w; } async init() { this.token = await getValidToken(); if (!this.token) { log('❌ 无有效Token,正在打开授权页...'); window.open(buildAuthUrl(), '_blank'); return false; } log('✅ Token有效,准备执行任务'); return true; } async runAll() { this.running = true; const ok = await this.init(); if (!ok) { this.running = false; this.closeAllWindows(); return; } // 打印本次执行顺序 const enabledTasks = this.taskOrder.filter((t) => { const k = 'enable' + t.charAt(0).toUpperCase() + t.slice(1); return this.settings[k]; }); log(`📋 本次执行顺序:${enabledTasks.map((t) => TASK_LABELS[t]).join(' → ')}` + (this.settings.randomOrder ? '(随机)' : '')); for (const taskId of this.taskOrder) { if (!this.running) break; const enabledKey = 'enable' + taskId.charAt(0).toUpperCase() + taskId.slice(1); if (!this.settings[enabledKey]) continue; log(`▶ 开始执行:${TASK_LABELS[taskId]}`); try { await this.executeTask(taskId); } catch (e) { log(`⚠ ${TASK_LABELS[taskId]} 异常: ${e.message}`); } uiState.stats = this.stats; updateUI(); if (!this.running) break; await this.wait(2000); } if (this.running) { // ★ 全部任务检查后判断是否已完成 await this.checkAllCompleted(); if (this.allDone) { log('🎉 检测到所有Rewards任务已完成,自动停止'); GM_notification({ text: `所有任务已完成!本次执行:成功 ${this.stats.completed},失败 ${this.stats.failed}`, title: 'Rewards助手 - 完成', }); } else { log('🎉 本轮任务执行完毕'); } } else { log('⏹ 任务已被用户停止'); } this.running = false; this.closeAllWindows(); } // ★ 检查所有Rewards任务是否已完成(用于自动停止判断) async checkAllCompleted() { try { const data = await fetchUserData(this.token); const promos = data.promotions || []; // 统计未完成任务 const pending = promos.filter((p) => { const attr = p.attributes || {}; if (attr.complete) return false; // 已完成 // 只关注我们支持的任务类型 return ['urlreward', 'checkin', 'msnreadearn', 'search'].includes(attr.type) || !!p.destination; }); this.allDone = pending.length === 0; if (this.allDone) { log(' ✓ 所有任务均已标记完成'); } else { log(` ℹ 仍有 ${pending.length} 个未完成任务`); } } catch (e) { log(' ⚠ 完成状态检查失败: ' + e.message); this.allDone = false; } } 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 ); if (promos.length === 0) { log(' - 每日任务已完成'); return; } for (const p of promos) { if (!this.running) return; 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++; } if (!await this.wait(rand(2000, 4000))) return; } } 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签到已完成或不存在'); } 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) return; const url = r.destination || r.attributes?.destination; if (url) { this.openTracked(url); log(` 📖 阅读停留 ${this.settings.readStayMin} 分钟...`); if (!await this.wait(this.settings.readStayMin * 60 * 1000)) return; } try { await completeActivity(this.token, r.attributes?.offerid); this.stats.completed++; log(` ✓ 阅读完成: ${r.name || '文章'}`); } catch (e) { this.stats.failed++; log(` ⚠ 阅读失败: ${e.message}`); } if (!await this.wait(rand(3000, 6000))) return; } 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) return; const url = a.destination || a.attributes?.destination; if (url) { this.openTracked(url); if (!await this.wait(60 * 1000)) return; } try { await completeActivity(this.token, a.attributes?.offerid); this.stats.completed++; log(` ✓ 完成活动: ${a.name || a.attributes?.offerid}`); } catch (e) { this.stats.failed++; } if (!await this.wait(rand(3000, 5000))) return; } } 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); if (remaining === 0) { log(' - PC搜索已完成'); return; } log(` - PC搜索: ${progress}/${max},还需 ${remaining} 次`); const queries = await getSearchQueries(remaining); for (let i = 0; i < queries.length; i++) { if (!this.running) return; const q = queries[i]; log(` 🔍 搜索 (${i + 1}/${queries.length}): "${q}"`); this.openTracked(`https://cn.bing.com/search?q=${encodeURIComponent(q)}&form=QBLH`); this.stats.completed++; if (i < queries.length - 1) { log(` ⏳ 停留 ${this.settings.searchStayMin} 分钟后继续...`); if (!await this.wait(this.settings.searchStayMin * 60 * 1000)) return; } } } async doVisualSearch() { const queries = ['猫', '风景', '科技', '美食', '旅行']; for (const q of queries.slice(0, 3)) { if (!this.running) return; this.openTracked(`https://cn.bing.com/images/search?q=${encodeURIComponent(q)}`); log(` ✓ 视觉搜索: ${q}`); if (!await this.wait(5000)) return; } } } // ==================== 搜索热词 ==================== async function getSearchQueries(count) { const queries = []; 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) => { 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 } }; let currentRunner = null; 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-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="checkbox"] { accent-color: #0078d4; width: 16px; height: 16px; } .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; } .mr-random-row { padding: 8px 10px; margin: 4px 0; background: ${isDark ? 'rgba(0,120,212,0.15)' : 'rgba(0,120,212,0.08)'}; border: 1px dashed ${isDark ? 'rgba(0,120,212,0.4)' : 'rgba(0,120,212,0.3)'}; border-radius: 6px; } .mr-random-row label { font-size: 12px; font-weight: 500; } .mr-random-row .hint { font-size: 10px; opacity: 0.55; margin-top: 2px; } `); const container = document.createElement('div'); container.id = 'mr-float-container'; container.innerHTML = `