// ==UserScript==
// @name Get Microsoft Rewards
// @namespace http://tampermonkey.net/
// @version 1.0.1.1
// @description 微软 Rewards 助手 - 自动完成搜索、活动、签到、阅读任务,配备极简 UI 悬浮窗,一键全自动获取积分。
// @author QingJ
// @icon https://rewards.bing.com/rewardscdn/images/rewards.png
// @match https://www.bing.com/*
// @match https://cn.bing.com/*
// @match https://rewards.bing.com/*
// @match https://login.live.com/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @grant GM_cookie
// @grant GM_registerMenuCommand
// @grant GM_openInTab
// @grant GM_log
// @connect bing.com
// @connect rewards.bing.com
// @connect www.bing.com
// @connect cn.bing.com
// @connect login.live.com
// @connect prod.rewardsplatform.microsoft.com
// @connect hot.baiwumm.com
// @connect hotapi.nntool.cc
// @connect cnxiaobai.com
// @license MIT
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// ========== 配置 ==========
const CONFIG = {
pc: { minDelay: 15000, maxDelay: 30000 },
mobile: { minDelay: 20000, maxDelay: 35000 },
ua: {
pc: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.2420.81',
mobile: 'Mozilla/5.0 (Linux; Android 16; MCE16 Build/BP3A.250905.014) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Mobile Safari/537.36 EdgA/123.0.2420.102'
},
// 多个热搜API备用源
hotApis: [
{ url: 'https://hot.baiwumm.com/api/', sources: ['weibo', 'douyin', 'baidu', 'zhihu', 'toutiao'] },
{ url: 'https://hotapi.nntool.cc/', sources: ['weibo', 'douyin', 'baidu', 'toutiao', 'zhihu'] },
{ url: 'https://cnxiaobai.com/DailyHotApi/', sources: ['weibo', 'douyin', 'baidu', 'toutiao'] }
],
keywords: ["天气预报", "今日新闻", "体育赛事", "股票行情", "电影推荐", "科技资讯", "美食食谱", "旅游攻略"],
// 暂停机制配置
pause: {
enabled: true, // 是否启用暂停机制
interval: 10, // 每执行多少次搜索后暂停
duration: 15 * 60 * 1000 // 暂停时长(毫秒),15分钟
}
};
// ========== 状态 ==========
let state = {
level: 1, points: 0,
pcCur: 0, pcMax: 0,
mobileCur: 0, mobileMax: 0,
promosTotal: 0, promosDone: 0,
signDone: false, signPoints: -1,
readCur: 0, readMax: 0,
running: false,
accessToken: null,
accessTokenExpiresAt: 0,
updating: false,
// 新增:搜索进度和暂停状态
searchCount: 0, // 当前搜索计数(用于暂停判断)
isPaused: false, // 是否处于暂停状态
pauseEndTime: 0, // 暂停结束时间戳
countdownStartTime: 0, // 倒计时开始时间(精确计时)
countdownDuration: 0 // 倒计时总时长
};
let dashboard = null;
let loginCookie = '';
// ========== 进度保存/恢复 ==========
const STORAGE_KEY = 'mr_search_progress';
function saveProgress() {
const today = getDateHyphen();
const data = {
date: today,
searchCount: state.searchCount
};
GM_setValue(STORAGE_KEY, JSON.stringify(data));
}
function loadProgress() {
try {
const saved = GM_getValue(STORAGE_KEY);
if (!saved) return null;
const data = JSON.parse(saved);
// 只恢复当天的进度
if (data.date === getDateHyphen()) {
state.searchCount = data.searchCount || 0;
return data;
}
} catch (e) { }
return null;
}
async function withAccessTokenRequest(requestFn) {
let token = await getAccessToken();
if (!token) return null;
try {
return await requestFn(token);
} catch (e) {
if (e && e.status === 401) {
state.accessToken = null;
state.accessTokenExpiresAt = 0;
token = await getAccessToken({ forceRefresh: true });
if (!token) throw e;
return await requestFn(token);
}
throw e;
}
}
function resetProgress() {
state.searchCount = 0;
GM_setValue(STORAGE_KEY, '');
}
// ========== 工具函数 ==========
const sleep = ms => new Promise(r => setTimeout(r, ms));
const randomPick = arr => arr[Math.floor(Math.random() * arr.length)];
const randomRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
const uuid = () => crypto.randomUUID();
const getDateStr = () => {
const d = new Date();
return `${d.getMonth() + 1}/${d.getDate()}/${d.getFullYear()}`;
};
const getDateHyphen = () => {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
const isJSON = s => { try { JSON.parse(s); return true; } catch { return false; } };
// GM_xmlhttpRequest 封装
async function gmRequest(options) {
const retries = options.retries ?? 2;
const retryDelay = options.retryDelay ?? 1000;
let attempt = 0;
const shouldRetry = (err) => {
const status = err?.status || 0;
return status === 0 || status === 429 || status >= 500 || err?.message === 'Timeout';
};
while (true) {
try {
return await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
timeout: 20000,
...options,
onload: xhr => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(options.returnUrl ? xhr.finalUrl : xhr.responseText);
} else if (xhr.status >= 300 && xhr.status < 400) {
const loc = xhr.responseHeaders.match(/Location:\s*(.*?)\s*[\r\n]/i);
resolve(loc ? loc[1] : xhr.responseText);
} else {
const err = new Error(`HTTP ${xhr.status}`);
err.status = xhr.status;
err.responseText = xhr.responseText;
err.finalUrl = xhr.finalUrl;
reject(err);
}
},
onerror: () => {
const err = new Error('Network Error');
err.status = 0;
reject(err);
},
ontimeout: () => {
const err = new Error('Timeout');
err.status = 0;
reject(err);
}
});
});
} catch (e) {
if (attempt >= retries || !shouldRetry(e)) throw e;
const delay = retryDelay * Math.pow(2, attempt);
attempt++;
await sleep(delay + randomRange(0, 250));
}
}
}
// 获取热搜词(支持多源自动切换)
async function getHotQuery() {
// 打乱API顺序,随机选择
const apis = [...CONFIG.hotApis].sort(() => Math.random() - 0.5);
for (const api of apis) {
try {
const src = randomPick(api.sources);
const res = await gmRequest({ method: 'GET', url: api.url + src, timeout: 8000 });
const data = JSON.parse(res);
if (data.code === 200 && data.data?.length) {
const title = randomPick(data.data).title || '';
// 随机截取长度,更自然
const len = randomRange(8, 25);
return title.substring(0, len);
}
} catch { /* 尝试下一个API */ }
}
// 所有API都失败,使用本地关键词
return `${randomPick(CONFIG.keywords)} ${Math.random().toString(36).slice(2, 6)}`;
}
// Cookie 管理
function getCookies(url) {
return new Promise(resolve => {
try {
if (typeof GM_cookie === 'undefined' || !GM_cookie) {
return resolve('');
}
GM_cookie('list', { url }, (cookies) => {
if (!cookies || !Array.isArray(cookies)) return resolve('');
const str = cookies.map(c => `${c.name}=${c.value}`).join('; ');
resolve(str);
});
setTimeout(() => resolve(''), 3000);
} catch (e) {
resolve('');
}
});
}
function deleteCookie(name, host = 'bing.com') {
return new Promise(resolve => {
if (typeof GM_cookie !== 'undefined') {
GM_cookie('delete', { url: `https://${host}`, name }, resolve);
} else resolve();
});
}
// ========== 样式 (极简版) ==========
GM_addStyle(`
#mr-panel {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 2147483647;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #fff;
border: 1px solid #e0e0e0;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border-radius: 8px;
}
/* 收起状态 */
#mr-panel.collapsed {
width: 44px;
height: 44px;
border-radius: 50%;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
color: #0078d4;
box-shadow: 0 4px 16px rgba(0,120,212,0.3);
border: 1px solid #e0e0e0;
transition: all 0.2s;
}
#mr-panel.collapsed:hover { transform: scale(1.1); box-shadow: 0 6px 20px rgba(0,120,212,0.4); }
#mr-panel.collapsed svg { width: 24px; height: 24px; fill: currentColor; }
#mr-panel.collapsed #mr-container { display: none; }
/* 展开状态 */
#mr-panel:not(.collapsed) { width: 300px; }
#mr-panel:not(.collapsed) svg { display: none; }
#mr-header {
padding: 12px 16px;
border-bottom: 1px solid #eee;
display: flex;
justify-content: space-between;
align-items: center;
background: #f8f9fa;
border-radius: 8px 8px 0 0;
}
#mr-title { font-weight: 600; font-size: 14px; color: #333; }
#mr-close { cursor: pointer; color: #999; font-size: 18px; line-height: 1; }
#mr-close:hover { color: #333; }
#mr-body { padding: 16px; }
.mr-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 12px; color: #555; }
.mr-val { font-weight: 600; color: #333; }
.mr-progress-bg { height: 4px; background: #eee; border-radius: 2px; margin-bottom: 12px; overflow: hidden; }
.mr-bar { height: 100%; background: #0078d4; }
.mr-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 16px; }
.mr-btn {
border: 1px solid #d0d0d0;
background: #fff;
color: #333;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.mr-btn:hover { background: #f0f0f0; border-color: #bbb; }
.mr-btn:active { background: #e5e5e5; }
.mr-full { grid-column: span 2; background: #0078d4; color: #fff; border: none; }
.mr-full:hover { background: #006abc; }
/* Auth */
#mr-auth { margin-bottom: 12px; padding: 10px; background: #fff8e1; border: 1px solid #ffe0b2; border-radius: 4px; }
.mr-input { width: 100%; padding: 4px; border: 1px solid #ccc; font-size: 11px; margin: 4px 0; }
/* Log */
#mr-log {
margin-top: 12px;
height: 80px;
background: #fafafa;
border: 1px solid #eee;
padding: 8px;
font-size: 10px;
color: #666;
overflow-y: auto;
font-family: monospace;
}
`);
// ========== UI 结构 ==========
const panel = document.createElement('div');
panel.id = 'mr-panel';
panel.className = 'collapsed'; // 默认折叠
// 礼盒 SVG
const svgIcon = ``;
panel.innerHTML = `
${svgIcon}
等级 -
0 pts
💻 PC搜索0/0
📱 移动搜索0/0
📖 阅读任务0/0
`;
document.body.appendChild(panel);
// 元素引用
const $ = id => document.querySelector(id);
const nodes = {
panel: $('#mr-panel'),
close: $('#mr-close'),
level: $('#mr-level'),
points: $('#mr-points'),
pc: $('#mr-pc'),
pcBar: $('#mr-pc-bar'),
mob: $('#mr-mobile'),
mobBar: $('#mr-mobile-bar'),
read: $('#mr-read'),
readBar: $('#mr-read-bar'),
btnSearch: $('#btn-search'),
btnPromo: $('#btn-promo'),
valPromo: $('#val-promo'),
btnSign: $('#btn-sign'),
btnRead: $('#btn-read'),
btnAll: $('#btn-all'),
boxAuth: $('#mr-auth'),
btnAuthLink: $('#mr-auth-link'),
inAuth: $('#mr-auth-in'),
btnAuthSave: $('#mr-auth-save'),
logBox: $('#mr-log')
};
// ========== 交互逻辑 ==========
// 展开/收起
nodes.panel.onclick = (e) => {
if (nodes.panel.classList.contains('collapsed')) {
nodes.panel.classList.remove('collapsed');
}
};
nodes.close.onclick = (e) => {
e.stopPropagation();
nodes.panel.classList.add('collapsed');
};
const LOG_MAX_LINES = 200;
const log = (msg) => {
const div = document.createElement('div');
div.textContent = `[${new Date().toLocaleTimeString().slice(0, 5)}] ${msg}`;
nodes.logBox.appendChild(div);
while (nodes.logBox.childNodes.length > LOG_MAX_LINES) {
nodes.logBox.removeChild(nodes.logBox.firstChild);
}
nodes.logBox.scrollTop = nodes.logBox.scrollHeight;
};
// 授权相关
const AUTH_URL = 'https://login.live.com/oauth20_authorize.srf?client_id=0000000040170455&scope=service::prod.rewardsplatform.microsoft.com::MBI_SSL&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf';
nodes.btnAuthLink.onclick = () => window.open(AUTH_URL, '_blank');
nodes.btnAuthSave.onclick = () => {
const val = nodes.inAuth.value.trim();
const match = val.match(/M\.[\w+.]+(-\w+){4}/);
if (match) {
GM_setValue('auth_code', match[0]);
log('✅ 授权码已保存!');
nodes.boxAuth.style.display = 'none';
} else {
log('❌ 格式错误,请复制完整URL');
}
};
async function checkAuth() {
const code = GM_getValue('auth_code');
if (!code) {
nodes.boxAuth.style.display = 'block';
log('⚠️ 请先获取授权码');
return false;
}
return code;
}
// ========== 核心逻辑 (简化版引用) ==========
// 数据刷新
async function updateData() {
if (state.updating) return;
state.updating = true;
try {
const res = await gmRequest({
url: `https://rewards.bing.com/api/getuserinfo?type=1&X-Requested-With=XMLHttpRequest&_=${Date.now()}`,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Referer': 'https://rewards.bing.com/'
},
anonymous: false // 确保携带 cookie
});
if (!res) {
log('⚠️ 获取数据失败:空响应');
return;
}
let data;
try {
data = JSON.parse(res);
} catch (e) {
log('⚠️ 解析数据失败,请确保已登录 rewards.bing.com');
console.error('Response:', res.substring(0, 200));
return;
}
dashboard = data.dashboard || data;
if (!dashboard || !dashboard.userStatus) {
log('⚠️ 未获取到用户数据,请先登录 rewards.bing.com');
return;
}
const user = dashboard.userStatus || {};
// 修复等级解析:处理 "newLevel1" 格式
const rawLevel = user.levelInfo?.activeLevel || 'Level1';
state.level = parseInt(String(rawLevel).replace(/\D/g, '')) || 1;
state.points = user.availablePoints || 0;
const c = user.counters || {};
let pc = 0, pcM = 0, mob = 0, mobM = 0;
// PC搜索
if (c.pcSearch) {
c.pcSearch.forEach(i => { pc += i.pointProgress || 0; pcM += i.pointProgressMax || i.pointMax || 0 });
}
// 移动搜索
if (c.mobileSearch) {
c.mobileSearch.forEach(i => { mob += i.pointProgress || 0; mobM += i.pointProgressMax || i.pointMax || 0 });
}
// 如果移动搜索上限为0且等级>1,尝试推断 (Lv2通常是60分)
if (mobM === 0 && state.level > 1) {
mobM = 60; // 假设值
}
// 如果PC搜索上限为0,尝试推断
if (pcM === 0) {
pcM = state.level > 1 ? 150 : 90; // Lv2=150, Lv1=90
}
state.pcCur = pc; state.pcMax = pcM;
state.mobileCur = mob; state.mobileMax = mobM;
const allP = [...(dashboard.dailySetPromotions?.[getDateStr()] || []), ...(dashboard.morePromotions || [])];
state.promosTotal = allP.length;
state.promosDone = allP.filter(p => p.complete).length;
render();
log(`✓ 数据已更新: Lv.${state.level} ${state.points}pts`);
} catch (e) {
console.error('updateData error:', e);
log(`⚠️ 获取数据出错: ${e.message}`);
} finally {
state.updating = false;
}
}
function render() {
nodes.level.textContent = `Lv.${state.level}`;
nodes.points.textContent = state.points.toLocaleString();
nodes.pc.textContent = `${state.pcCur}/${state.pcMax}`;
nodes.pcBar.style.width = state.pcMax ? `${(state.pcCur / state.pcMax) * 100}%` : '0%';
nodes.mob.textContent = `${state.mobileCur}/${state.mobileMax}`;
nodes.mobBar.style.width = state.mobileMax ? `${(state.mobileCur / state.mobileMax) * 100}%` : '0%';
nodes.read.textContent = `${state.readCur}/${state.readMax}`;
nodes.readBar.style.width = state.readMax ? `${(state.readCur / state.readMax) * 100}%` : '0%';
nodes.valPromo.textContent = `${state.promosDone}/${state.promosTotal}`;
}
// Token 获取
async function getAccessToken(opts = {}) {
const forceRefresh = !!opts.forceRefresh;
const now = Date.now();
if (!forceRefresh && state.accessToken && state.accessTokenExpiresAt && now < (state.accessTokenExpiresAt - 60000)) {
return state.accessToken;
}
if (forceRefresh) {
state.accessToken = null;
state.accessTokenExpiresAt = 0;
}
const code = await checkAuth();
if (!code) return null;
let refreshToken = GM_getValue('refresh_token');
let url = refreshToken
? `https://login.live.com/oauth20_token.srf?client_id=0000000040170455&refresh_token=${refreshToken}&scope=service::prod.rewardsplatform.microsoft.com::MBI_SSL&grant_type=REFRESH_TOKEN`
: `https://login.live.com/oauth20_token.srf?client_id=0000000040170455&code=${code}&redirect_uri=https://login.live.com/oauth20_desktop.srf&grant_type=authorization_code`;
try {
const res = await gmRequest({ url });
const data = JSON.parse(res);
if (data.access_token) {
state.accessToken = data.access_token;
if (data.expires_in) {
state.accessTokenExpiresAt = Date.now() + Number(data.expires_in) * 1000;
} else {
state.accessTokenExpiresAt = 0;
}
if (data.refresh_token) GM_setValue('refresh_token', data.refresh_token);
return data.access_token;
} else if (data.error) {
log('Token失效,请重新授权');
GM_setValue('refresh_token', '');
GM_setValue('auth_code', '');
state.accessToken = null;
state.accessTokenExpiresAt = 0;
nodes.boxAuth.style.display = 'block';
}
} catch (e) { log('Auth Error: ' + e.message); }
return null;
}
// 签到
const runSign = async () => {
nodes.btnSign.disabled = true;
log('⏳ 签到中...');
try {
const res = await withAccessTokenRequest(token => gmRequest({
method: 'POST',
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Rewards-AppId': 'SAAndroid/31.4.2110003555',
'X-Rewards-IsMobile': 'true',
'X-Rewards-Country': 'cn'
},
data: JSON.stringify({
amount: 1, id: uuid(), type: 103, country: 'cn',
attributes: {}, risk_context: {}, channel: 'SAAndroid'
})
}));
if (res) {
const d = JSON.parse(res);
if (d.response?.activity) {
log(`✅ 签到成功 +${d.response.activity.p}分`);
} else {
log('⚠️ 已签到或失败');
}
}
} catch (e) { log('❌ 签到出错'); }
nodes.btnSign.disabled = false;
};
nodes.btnSign.onclick = runSign;
// 阅读
const runRead = async () => {
nodes.btnRead.disabled = true;
log('⏳ 开始阅读任务...');
try {
const info = await withAccessTokenRequest(token => gmRequest({
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613',
headers: { 'Authorization': `Bearer ${token}`, 'X-Rewards-AppId': 'SAAndroid/31.4.2110003555', 'X-Rewards-IsMobile': 'true' }
}));
if (info) {
const d = JSON.parse(info);
const p = d.response?.promotions?.find(x => x.attributes?.offerid === 'ENUS_readarticle3_30points');
if (p) {
let cur = +p.attributes.progress, max = +p.attributes.max;
state.readCur = cur; state.readMax = max; render();
if (cur >= max) { log('✅ 阅读任务已完成'); }
else {
for (let i = cur; i < max; i++) {
log(`📖 阅读文章 ${i + 1}/${max}`);
await withAccessTokenRequest(token => gmRequest({
method: 'POST',
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities',
headers: {
'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json',
'X-Rewards-AppId': 'SAAndroid/31.4.2110003555', 'X-Rewards-IsMobile': 'true', 'X-Rewards-Country': 'cn'
},
data: JSON.stringify({
amount: 1, country: 'cn', id: uuid(), type: 101, attributes: { offerid: 'ENUS_readarticle3_30points' }
})
}));
await sleep(2500);
state.readCur++; render();
}
log('✅ 阅读完成');
}
}
}
} catch (e) { log('❌ 阅读出错'); }
nodes.btnRead.disabled = false;
};
nodes.btnRead.onclick = runRead;
// 搜索 (复用逻辑)
// 获取 Reward Token (活动专用)
async function getSearchToken() {
try {
const html = await gmRequest({ url: 'https://rewards.bing.com/' });
// 尝试匹配两种常见的 Token 格式
const token = html.match(/RequestVerificationToken.*?value="([^"]+)"/)?.[1] ||
html.match(/"verificationToken":\s*"([^"]+)"/)?.[1];
return token;
} catch { return null; }
}
const runPromo = async () => {
nodes.btnPromo.disabled = true;
log('⏳ 开始执行活动...');
await updateData();
const token = await getSearchToken();
if (!token) {
log('⚠️ 未获取到活动Token,请刷新页面重试');
nodes.btnPromo.disabled = false;
return;
}
// 收集所有需要完成的任务
let taskList = [];
const today = getDateStr();
// 1. 每日任务 (Daily Set)
if (dashboard.dailySetPromotions && dashboard.dailySetPromotions[today]) {
taskList.push(...dashboard.dailySetPromotions[today]);
log(`📅 检测到 ${dashboard.dailySetPromotions[today].length} 个每日任务`);
}
// 2. 更多活动 (More Activities)
if (dashboard.morePromotions) {
taskList.push(...dashboard.morePromotions);
}
// 过滤出未完成的任务
taskList = taskList.filter(p => !p.complete && p.priority > -2 && p.exclusiveLockedFeatureStatus !== 'locked');
if (taskList.length === 0) {
log('✅ 所有活动已完成!');
nodes.btnPromo.disabled = false;
return;
}
let count = 0;
for (const p of taskList) {
try {
log(`▶️ 执行: ${p.title}`);
// 请求1: 标准 ReportActivity
await gmRequest({
method: 'POST',
url: 'https://rewards.bing.com/api/reportactivity?X-Requested-With=XMLHttpRequest',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: `id=${p.offerId}&hash=${p.hash}&activityAmount=1&__RequestVerificationToken=${token}`
});
// 请求2: V1 API (尝试以 Quiz 类型上报,有助于触发某些任务完成)
// 构造 V1 API 需要 host,通常是 www.bing.com
const v1Url = `https://www.bing.com/msrewards/api/v1/ReportActivity?ajaxreq=1`;
await gmRequest({
method: 'POST',
url: v1Url,
headers: { 'Content-Type': 'application/json' },
data: JSON.stringify({
"ActivitySubType": "quiz",
"ActivityType": "notification",
"OfferId": p.offerId,
"Channel": "Bing.Com",
"PartnerId": "BingTrivia",
"Timezone": -480
})
});
// 模拟简单交互延迟
await sleep(randomRange(1500, 3000));
count++;
} catch (e) {
log(`❌ 活动执行失败: ${e.message}`);
}
}
log(`✅ 完成尝试,共执行 ${count} 个活动`);
await updateData(); // 刷新状态
nodes.btnPromo.disabled = false;
};
nodes.btnPromo.onclick = runPromo;
const runSearch = async () => {
if (state.running) { state.running = false; nodes.btnSearch.textContent = '🔍 搜索'; return; }
state.running = true;
nodes.btnSearch.textContent = '⏹ 停止';
await updateData();
// 辅助函数:执行单次搜索并报告活动
const doSearch = async (query, isMobile) => {
const host = isMobile ? 'cn.bing.com' : 'www.bing.com';
const ua = isMobile ? CONFIG.ua.mobile : CONFIG.ua.pc;
const deviceCookie = isMobile ? `_Rwho=u=m&ts=${getDateHyphen()}` : `_Rwho=u=d&ts=${getDateHyphen()}`;
const searchUrl = `https://${host}/search?q=${encodeURIComponent(query)}&form=QBLH`;
await deleteCookie('_EDGE_S', host);
await deleteCookie('_Rwho', host);
await deleteCookie('_RwBf', host);
try {
// 执行搜索
const searchResult = await gmRequest({
url: searchUrl,
headers: {
'User-Agent': ua,
'Cookie': deviceCookie,
'Referer': `https://${host}/?form=QBLH`
}
});
// 尝试提取 IG 参数用于报告
const igMatch = searchResult.match(/,IG:"([^"]+)"/);
const ig = igMatch ? igMatch[1] : crypto.randomUUID().replace(/-/g, '').toUpperCase();
// 报告搜索活动 (关键!这是计分的核心)
const reportHeaders = {
'User-Agent': ua,
'Cookie': deviceCookie,
'Referer': searchUrl,
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
};
// ncheader 请求
await gmRequest({
method: 'POST',
url: `https://${host}/rewardsapp/ncheader?ver=88888888&IID=SERP.5047&IG=${ig}&ajaxreq=1`,
headers: reportHeaders,
data: 'wb=1%3bi%3d1%3bv%3d1'
});
// reportActivity 请求
await gmRequest({
method: 'POST',
url: `https://${host}/rewardsapp/reportActivity?IG=${ig}&IID=SERP.5047&q=${encodeURIComponent(query)}&ajaxreq=1`,
headers: reportHeaders,
data: `url=${encodeURIComponent(searchUrl)}&V=web`
});
log(`✓ ${isMobile ? '📱' : '💻'} "${query.substring(0, 15)}..."`);
} catch (e) {
log(`✗ 搜索失败: ${e.message}`);
}
};
// 精确等待函数(不受标签页切换影响)
const preciseWait = async (ms) => {
state.countdownStartTime = Date.now();
state.countdownDuration = ms;
const endTime = Date.now() + ms;
while (Date.now() < endTime && state.running) {
const remaining = Math.max(0, endTime - Date.now());
// 更新UI显示剩余时间
const secs = Math.ceil(remaining / 1000);
if (secs % 5 === 0 || secs <= 10) {
log(`⏳ 等待 ${secs} 秒...`);
}
await sleep(Math.min(1000, remaining));
}
state.countdownStartTime = 0;
state.countdownDuration = 0;
};
// 暂停检查函数
const checkPause = async () => {
if (!CONFIG.pause.enabled) return;
state.searchCount++;
saveProgress(); // 保存进度
if (state.searchCount % CONFIG.pause.interval === 0) {
state.isPaused = true;
const pauseMinutes = CONFIG.pause.duration / 60000;
log(`⏸️ 已完成 ${state.searchCount} 次搜索,暂停 ${pauseMinutes} 分钟降低风险...`);
state.pauseEndTime = Date.now() + CONFIG.pause.duration;
await preciseWait(CONFIG.pause.duration);
state.isPaused = false;
state.pauseEndTime = 0;
log(`▶️ 暂停结束,继续搜索...`);
}
};
// 加载之前的进度
loadProgress();
log(`📊 当前搜索计数: ${state.searchCount}`);
// PC Search
const pcNeed = Math.ceil((state.pcMax - state.pcCur) / 3);
if (pcNeed > 0) {
log(`💻 PC搜索 ${pcNeed}次`);
for (let i = 0; i < pcNeed && state.running; i++) {
const q = await getHotQuery();
await doSearch(q, false);
await checkPause(); // 暂停检查
if (!state.running) break;
await preciseWait(randomRange(CONFIG.pc.minDelay, CONFIG.pc.maxDelay));
if ((i + 1) % 3 === 0) await updateData();
}
}
// Mobile Search
const mobNeed = Math.ceil((state.mobileMax - state.mobileCur) / 3);
if (mobNeed > 0 && state.running) {
log(`📱 移动搜索 ${mobNeed}次`);
for (let i = 0; i < mobNeed && state.running; i++) {
const q = await getHotQuery();
await doSearch(q, true);
await checkPause(); // 暂停检查
if (!state.running) break;
await preciseWait(randomRange(CONFIG.mobile.minDelay, CONFIG.mobile.maxDelay));
if ((i + 1) % 3 === 0) await updateData();
}
}
await updateData();
state.running = false;
nodes.btnSearch.textContent = '🔍 搜索';
log('🏁 搜索结束');
saveProgress(); // 最终保存进度
};
nodes.btnSearch.onclick = runSearch;
nodes.btnAll.onclick = async () => {
log('🚀 一键执行开始');
nodes.btnAll.disabled = true;
await runSign();
await runRead();
await runPromo();
await runSearch();
nodes.btnAll.disabled = false;
};
// Init
(async () => {
try {
loginCookie = await getCookies('https://login.live.com');
await updateData();
// Try load read progress if token exists
try {
const info = await withAccessTokenRequest(token => gmRequest({
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613',
headers: { 'Authorization': `Bearer ${token}`, 'X-Rewards-AppId': 'SAAndroid/31.4.2110003555', 'X-Rewards-IsMobile': 'true' }
}));
if (info) {
const d = JSON.parse(info);
const p = d.response?.promotions?.find(x => x.attributes?.offerid === 'ENUS_readarticle3_30points');
if (p) { state.readCur = +p.attributes.progress; state.readMax = +p.attributes.max; render(); }
}
} catch { }
} catch { }
log('🌟 脚本就绪 v1.0.0');
})();
setInterval(updateData, 60000);
})();