// ==UserScript==
// @name Microsoft Bing Rewards每日任务脚本
// @namespace https://greasyfork.org/users/1362311
// @version 4.0.8
// @description 自动完成微软积分每日搜索任务
// @author honguangli
// @license MIT
// @match https://www.bing.com/*
// @match https://cn.bing.com/*
// @match https://rewards.bing.com/*
// @icon https://www.bing.com/favicon.ico
// @run-at document-end
// @grant GM_registerMenuCommand
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @connect api.gumengya.com
// ==/UserScript==
(function() {
'use strict';
const keywordApi = 'https://api.gumengya.com/Api/'; // 词条接口
const rewardHost = 'https://rewards.bing.com'; // 积分页面
const searchHost = 'https://www.bing.com/search'; // 搜索页面
const pathnames = ['/', '/search']; // 触发搜索页面
const searchTimes = 40; // 自动搜索次数
const searchDelaySecondsMin = 15; // 每次搜索最小延迟时间,单位秒
const searchDelaySecondsMax = 30; // 每次搜索最大延迟时间,单位秒
const searchDelaySecondsFirst = 3; // 首次搜索延迟时间,单位秒,设置为0时立即触发
const closeTaskCardDelaySeconds = 0; // 搜索完成后弹窗自动关闭延迟时间,单位秒,设置为0时需手动关闭
const searchSafeDelayTimes = 4; // 每搜索n次后触发长暂停
const searchSafeDelaySeconds = 0 * 60; // 每次触发长暂停时长,单位秒
const clickDelaySecondsFirst = 3; // 首次搜索延迟时间,单位秒
const startBtn = true; // 是否在搜索框附近插入任务启动按钮
const startBtnText = '获取积分'; // 任务启动按钮文本
const dailyBtn = true; // 是否在搜索框附近插入获取每日活动积分按钮
const dailyBtnText = '获取积分2'; // 获取每日活动积分按钮文本
// 搜索词条库
const searchKeySource = [
{ name: '百度', action: 'BaiduHot' },
{ name: '抖音', action: 'DouYinHot' },
{ name: '搜狗', action: 'SoGouHot' },
{ name: '360', action: 'SoHot' },
{ name: '微博', action: 'WeiBoHot' },
{ name: '知乎', action: 'ZhiHuHot' },
{ name: '今日头条', action: 'TouTiaoHot' },
{ name: '快手', action: 'KuaiShouHot' }
];
const timeKey = 'time'; // 时间戳
const runDailyActivityKey = 'runDaily'; // 触发每日活动任务
const runSearchKey = 'runSearch'; // 触发搜索任务
const countKey = 'count'; // 计数器
const pointsKey = 'points'; // 当日初始积分
const searchPointsKey = 'searchPoints'; // 当日搜索任务初始积分
const keywordsKey = 'search'; // 搜索词条
const searchParamKey = 'param'; // 搜索参数
let scrollIntervalId = null; // 模拟浏览定时器id
let delayIntervalId = null; // 搜索任务倒计时定时器id
GM_addStyle('#reward-task { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(0, 0, 0, .2); z-index: 99999; }');
GM_addStyle('#reward-task > .reward-task-content { max-width: 460px; margin: calc(50vh - 32px) auto 0; padding: 20px; background-color: #ffffff; border: 1px solid #e4e7ed; border-radius: 20px; color: #303133; overflow: hidden; transition: 0.3s; box-shadow: 0px 0px 12px rgba(0,0,0,0.12); }');
GM_addStyle('#reward-task > .reward-task-content > div { display: flex; flex-flow: row wrap; align-items: center; column-gap: 12px; }');
GM_addStyle('#reward-task > .reward-task-content > div > .item { width: 160px; padding: 7px 0; }');
GM_addStyle('#reward-task > .reward-task-content > div > .tips { padding: 7px 0; color: #E6A23C; }');
GM_addStyle('#reward-task > .reward-task-content > div > .btn-wrap { flex-grow: 1; text-align: right; }');
GM_addStyle('.reward-task-btn { display: inline-block; line-height: 1; white-space: nowrap; cursor: pointer; background: #fff; border: 1px solid #dcdfe6; -webkit-appearance: none; text-align: center; -webkit-box-sizing: border-box; box-sizing: border-box; outline: 0; margin: 0; -webkit-transition: .1s; transition: .1s; font-weight: 500; padding: 8px 16px; font-size: 14px; border-radius: 4px; color: #606266; background-color: #ffffff; border: 1px solid #dcdfe6; border-color: #dcdfe6; }');
GM_addStyle('.reward-task-btn.warning { color: #fff; background-color: #ebb563; border-color: #ebb563; }');
GM_addStyle('#ScopeRow { margin-top: 48px; }');
// 注册菜单
const registerMenuCommand = () => {
// 启动搜索任务
GM_registerMenuCommand('获取积分', () => {
start();
});
// 启动搜索任务,含每日活动任务
GM_registerMenuCommand('获取积分2(含每日活动)', () => {
navigateToRewardPage();
});
// 停止任务
GM_registerMenuCommand('停止', () => {
stop();
removeTaskCard();
});
};
// 启动搜索任务
const start = () => {
GM_setValue(countKey, 1);
GM_setValue(searchPointsKey, -1);
search();
};
// 结束搜索任务
const stop = () => {
GM_setValue(countKey, 0);
GM_setValue(searchPointsKey, -1);
clearInterval(scrollIntervalId);
};
// 跳转积分页面并运行每日活动任务
const navigateToRewardPage = () => {
GM_setValue(runDailyActivityKey, 1);
location.href = rewardHost;
};
// 跳转搜索页面并运行搜索任务
const navigateToSearchPage = () => {
GM_setValue(runSearchKey, 1);
location.href = searchHost;
};
// 自动点击完成每日活动积分
const autoClickRewardActivity = () => {
insertActivityTaskCard( () => {
navigateToSearchPage();
});
};
// 搜索
const search = () => {
const count = GM_getValue(countKey);
if (!count || count <= 0 || count > searchTimes + 1) {
stop();
return;
}
// 延迟时间
const delay = count === 1 ? searchDelaySecondsFirst : Math.floor(Math.random() * (searchDelaySecondsMax - searchDelaySecondsMin + 1)) + searchDelaySecondsMin + (count % searchSafeDelayTimes !== 1 ? 0 : searchSafeDelaySeconds);
// 添加任务进度卡片
insertTaskCard(count - 1, delay, () => {
// 获取词条
getSearchInfo().then( keyword => {
const queryInput = document.getElementById('sb_form_q');
const param = `?q=${ keyword }&form=${ Math.random() > 0.4 ? 'QBLH' : 'QBRE' }&sp=-1&lq=0&pq=${ queryInput ? queryInput.value || '' : '' }&sc=0-0&qs=n&sk=&cvid=${ generateRandomString(32) }&ghsh=0&ghacc=0&ghpl=`;
// 更新计数器
GM_setValue(countKey, count+1);
// 更新搜索参数
GM_setValue(searchParamKey, param);
// 触发搜索
location.href = searchHost + param;
}).catch( err => {
stop();
removeTaskCard();
alert('获取词条失败', err.message);
});
});
if (count > searchTimes) {
return;
}
// 模拟浏览网页
pretendHuman();
};
// 获取词条
const getSearchInfo = () => {
return new Promise((resolve, reject) => {
const today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
let source = Math.floor(Math.random() * searchKeySource.length);
const saveConfig = GM_getValue(keywordsKey);
// 当日缓存的搜索词还未用完
if (saveConfig && saveConfig.time === today.getTime()) {
if (saveConfig.keywords.length > 0) {
const keyword = saveConfig.keywords[0];
saveConfig.keywords.splice(0, 1);
GM_setValue(keywordsKey, saveConfig);
resolve(keyword);
return;
} else {
source = (saveConfig.source + 1) % searchKeySource.length;
}
}
// 获取新词条
// 支持重试
const queryKeyword = (config, retry) => {
GM_xmlhttpRequest({
method: 'GET',
url: keywordApi + searchKeySource[config.source].action,
headers: {
"Content-Type": "application/json"
},
responseType: 'json',
onload: response => {
if (response.status !== 200 || !response.response) {
reject(new Error('调用Api失败'));
return;
}
if (response.response.code !== 200) {
reject(new Error(response.response.msg));
return;
}
config.keywords = Array.from(new Set(response.response.data.map(item => searchKeySource[config.source].name + ' ' + item.title + ' ' + generateRandomString(3))));
config.keywords.splice(50);
const keyword = config.keywords[0];
config.keywords.splice(0, 1);
GM_setValue(keywordsKey, config);
resolve(keyword);
},
onerror: err => {
if (retry > 0) {
queryKeyword({
time: config.time,
source: (config.source + 1) % searchKeySource.length,
keywords: [],
}, retry-1);
return;
}
reject(err);
},
});
};
// 获取新词条
queryKeyword({
time: today.getTime(),
source: source,
keywords: [],
}, searchKeySource.length - 1);
});
};
// 插入启动按钮
const insertStartBtn = () => {
if (document.getElementById('reward-task-start')) {
document.getElementById('reward-task-start').remove();
}
if (!startBtn) {
return;
}
// 获取搜索表单
const queryForm = document.getElementById('sb_form');
// 添加启动按钮
const btn = document.createElement('button');
btn.appendChild(document.createTextNode(startBtnText));
btn.setAttribute('id', 'reward-task-start');
btn.setAttribute('type', 'button');
btn.classList.add('reward-task-btn');
btn.style.setProperty('margin', '8px');
btn.style.setProperty('padding', '8px 24px');
btn.style.setProperty('border-radius', '24px');
btn.onclick = () => {
start();
};
// 首页表单会重置组件内容,需要等待几秒后再插入
setTimeout( () => {
queryForm.appendChild(btn);
}, location.pathname !== '/' ? 0 : 5000);
};
// 插入启动按钮2
const insertDailyBtn = () => {
if (document.getElementById('reward-task-daily')) {
document.getElementById('reward-task-daily').remove();
}
if (!dailyBtn) {
return;
}
// 获取搜索表单
const queryForm = document.getElementById('sb_form');
// 添加启动按钮2
const btn = document.createElement('button');
btn.appendChild(document.createTextNode(dailyBtnText));
btn.setAttribute('id', 'reward-task-daily');
btn.setAttribute('type', 'button');
btn.classList.add('reward-task-btn');
btn.style.setProperty('margin', '8px');
btn.style.setProperty('padding', '8px 24px');
btn.style.setProperty('border-radius', '24px');
btn.onclick = () => {
navigateToRewardPage();
};
// 首页表单会重置组件内容,需要等待几秒后再插入
setTimeout( () => {
queryForm.appendChild(btn);
}, location.pathname !== '/' ? 0 : 5000);
};
// 插入搜索任务卡片
const insertTaskCard = (times, delay, finish) => {
removeTaskCard();
// 添加搜索任务卡片
const h = `<div id="reward-task">
<div class="reward-task-content">
<div>
<p id="reward-points" class="item">当前积分:${ getCurrPoints() }</p>
</div>
<div>
<p id="task-points" class="item">本次获得积分:${ getTaskPoints() }</p>
<p id="task-today-points" class="item">当日获得积分:${ getTodayPoints() }</p>
</div>
<div>
<p class="item">进度:${ times } / ${ searchTimes }</p>
<p id="reward-task-delay" class="item">${ times >= searchTimes ? `已完成${ closeTaskCardDelaySeconds > 0 ? ',' + closeTaskCardDelaySeconds + '秒后自动关闭' : '' }` : `等待时间:${ delay } 秒 ` }</p>
<div class="btn-wrap">${ times >= searchTimes ? '<button id="reward-task-cancel" type="button" class="reward-task-btn warning">关闭</button>' : '<button id="reward-task-stop" type="button" class="reward-task-btn warning">停止</button>' }</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeEnd', h);
// 停止按钮
const btnStop = document.querySelector('#reward-task-stop');
if (btnStop) {
btnStop.onclick = () => {
stop();
removeTaskCard();
};
}
// 关闭按钮
const btnCancel = document.querySelector('#reward-task-cancel');
if (btnCancel) {
btnCancel.onclick = () => {
stop();
removeTaskCard();
};
}
// 任务完成,自动延迟关闭任务窗口
if (times >= searchTimes && closeTaskCardDelaySeconds > 0) {
setTimeout( () => {
removeTaskCard();
}, closeTaskCardDelaySeconds * 1000);
}
// 倒计时
// 任务完成后再执行10次以便获取积分信息
let remainDelay = times >= searchTimes ? 10 : delay;
delayIntervalId = setInterval(() => {
remainDelay--;
if (remainDelay < 0) {
clearInterval(delayIntervalId);
stop();
return;
}
// 更新积分
const domCurrPoints = document.getElementById('reward-points');
const domTaskPoints = document.getElementById('task-points');
const domTaskTodayPoints = document.getElementById('task-today-points');
domCurrPoints.innerText = `当前积分:${ getCurrPoints() }`;
domTaskPoints.innerText = `本次获得积分:${ getTaskPoints() }`;
domTaskTodayPoints.innerText = `当日获得积分:${ getTodayPoints() }`;
// 任务已完成
if (times >= searchTimes) {
return;
}
// 倒计时完成
if (remainDelay === 0) {
clearInterval(delayIntervalId);
finish();
return;
}
// 更新倒计时
const domDelay = document.getElementById('reward-task-delay');
if (!domDelay) {
return;
}
domDelay.innerText = `等待时间:${ remainDelay } 秒`;
}, 1000);
};
// 插入积分活动任务卡片
const insertActivityTaskCard = (finish) => {
removeTaskCard();
// 每日活动
const daily = {
todo: [],
total: 0,
finish: 0,
};
const dailyActivitys = document.querySelectorAll('mee-rewards-daily-set-section mee-card.ng-scope.c-card:not([disabled=disabled]) a.ds-card-sec');
for (let i =0; i < dailyActivitys.length; i++) {
daily.total++;
if (dailyActivitys[i].querySelectorAll('.mee-icon.mee-icon-AddMedium').length > 0) {
daily.todo.push(dailyActivitys[i]);
} else {
daily.finish++;
}
}
// 更多活动
const more = {
todo: [],
total: 0,
finish: 0,
};
const moreActivitys = document.querySelectorAll('mee-rewards-more-activities-card mee-card.ng-scope.c-card:not([disabled=disabled]) a.ds-card-sec');
for (let i =0; i < moreActivitys.length; i++) {
more.total++;
if (moreActivitys[i].querySelectorAll('.mee-icon.mee-icon-AddMedium').length > 0) {
more.todo.push(moreActivitys[i]);
} else {
more.finish++;
}
}
// 没有待完成的任务
if (daily.todo.length === 0 && more.todo.length === 0) {
//finish();
//return;
}
// 倒计时
let delay = more.todo.length + clickDelaySecondsFirst;
// 每日活动只在12点后触发才可获得积分
const today = new Date();
if (today.getHours() >= 12) {
delay += daily.todo.length;
}
// 添加搜索任务卡片
const h = `<div id="reward-task">
<div class="reward-task-content">
<div>
<p id="reward-points" class="item">当前积分:${ getCurrPoints() }</p>
</div>
<div>
<p id="task-points" class="item">本次获得积分:${ getTaskPoints() }</p>
<p id="task-today-points" class="item">当日获得积分:${ getTodayPoints() }</p>
</div>
<div>
<p id="daily-progress" class="item">每日任务:${ daily.finish } / ${ daily.total }</p>
<p id="more-progress" class="item">更多任务:${ more.finish } / ${ more.total }</p>
</div>
<div>
<p id="reward-task-delay" class="item">等待时间:${ delay } 秒</p>
<div class="btn-wrap"><button id="reward-task-stop" type="button" class="reward-task-btn warning">停止</button></div>
</div>
<div>
<p class="tips">注意:每日任务必须在12:00后执行才能获得积分!</p>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeEnd', h);
// 停止按钮
const btnStop = document.querySelector('#reward-task-stop');
if (btnStop) {
btnStop.onclick = () => {
stop();
removeTaskCard();
};
}
// 倒计时
let remainDelay = delay;
delayIntervalId = setInterval(() => {
remainDelay--;
if (remainDelay < 0) {
clearInterval(delayIntervalId);
finish();
return;
}
// 更新积分
const domCurrPoints = document.getElementById('reward-points');
const domTaskPoints = document.getElementById('task-points');
const domTaskTodayPoints = document.getElementById('task-today-points');
domCurrPoints.innerText = `当前积分:${ getCurrPoints() }`;
domTaskPoints.innerText = `本次获得积分:${ getTaskPoints() }`;
domTaskTodayPoints.innerText = `当日获得积分:${ getTodayPoints() }`;
// 更新倒计时
const domDelay = document.getElementById('reward-task-delay');
if (!domDelay) {
return;
}
domDelay.innerText = `等待时间:${ remainDelay } 秒`;
// 首次延迟时间
if (remainDelay > delay - clickDelaySecondsFirst - 1) {
return;
}
// 更新任务进度
const domDailyProgress = document.getElementById('daily-progress');
const domMoreProgress = document.getElementById('more-progress');
const index = delay - remainDelay - clickDelaySecondsFirst - 1;
if (today.getHours() >= 12) {
if (index < daily.todo.length) {
daily.todo[index].click();
daily.finish++;
domDailyProgress.innerText = `每日任务:${ daily.finish } / ${ daily.total }`;
} else if (index - daily.todo.length < more.todo.length) {
more.todo[index - daily.todo.length].click();
more.finish++;
domMoreProgress.innerText = `更多任务:${ more.finish } / ${ more.total }`;
}
} else {
more.tody[index].click();
more.finish++;
domMoreProgress.innerText = `更多任务:${ more.finish } / ${ more.total }`;
}
}, 1000);
};
// 移除搜索任务卡片
const removeTaskCard = () => {
clearInterval(delayIntervalId);
if (!document.getElementById('reward-task')) {
return;
}
document.getElementById('reward-task').remove();
};
// 模拟浏览网页
const pretendHuman = () => {
clearInterval(scrollIntervalId);
window.scrollTo({
top: 0,
behavior: 'smooth'
});
// 启用定时器缓慢滑动到底部,期间随机触发停留和向上滑动,完成滑动到底部后再次滑动到顶部后停止滑动
scrollIntervalId = setInterval(() => {
if (document.documentElement.scrollTop >= document.documentElement.scrollHeight - document.documentElement.clientHeight) {
clearInterval(scrollIntervalId);
window.scrollTo({
top: 0,
behavior: 'smooth'
});
return;
}
const number = Math.floor(Math.random() * 10) + 1;
if (number < 3) {
window.scrollTo({
top: document.documentElement.scrollTop - 200,
behavior: 'smooth'
});
} else if (number > 5) {
window.scrollTo({
top: document.documentElement.scrollTop + 100,
behavior: 'smooth'
});
}
}, 500);
};
// 获取当前积分
// 兼容PC端搜索页、积分页、移动端搜索页
const getCurrPoints = () => {
let pointsStr = '';
const searchPagePointsWrap = document.querySelector('#rh_rwm .points-container');
const rewardPagePointsWrap = document.querySelector('#balanceToolTipDiv.textAndIcon mee-rewards-counter-animation.ng-isolate-scope');
const mobilePagePointsWrap = document.querySelector('#fly_id_rc');
if (searchPagePointsWrap) {
// PC端搜索页
// 需要判断当前显示积分是否已完成计数器动画效果,根据完成情况从不同渠道获取积分值
if (!searchPagePointsWrap.classList.contains('balance-animation')) {
pointsStr = searchPagePointsWrap.innerText.trim();
} else {
pointsStr = document.documentElement.style.getPropertyValue('--rw-gp-balance-to');
}
} else if (rewardPagePointsWrap) {
// 积分页
// 需要判断当前显示积分是否已完成计数器动画效果,需完成后才可获取积分值
const span = document.querySelector('#balanceToolTipDiv.textAndIcon mee-rewards-counter-animation.ng-isolate-scope span');
if (span) {
const v1 = rewardPagePointsWrap.innerText.trim().replace(/\D/g, '');
const v2 = span.getAttribute('aria-label').trim().replace(/\D/g, '');
pointsStr = v1 === v2 ? v1 : '';
}
} else if (mobilePagePointsWrap) {
// 移动端搜索页
pointsStr = mobilePagePointsWrap.innerText.trim();
// 关闭菜单
const menuClose = document.querySelector('#HBFlyoutClose');
if (menuClose) {
menuClose.click();
}
} else {
// 尝试 移动端搜索页 开启菜单
const menuOpen = document.querySelector('#mHamburger');
if (menuOpen) {
menuOpen.click();
}
}
const points = parseInt(pointsStr);
if (isNaN(points)) {
return null;
}
return points;
};
// 计算已取得积分
const getTaskPoints = () => {
const currPoints = getCurrPoints();
if (currPoints === null) {
return 0;
}
let startPoints = GM_getValue(searchPointsKey);
if (startPoints === -1) {
GM_setValue(searchPointsKey, currPoints);
return 0;
}
return currPoints - startPoints;
};
// 计算当日已取得积分
const getTodayPoints = () => {
const currPoints = getCurrPoints();
if (currPoints === null) {
return 0;
}
const today = new Date();
today.setHours(0);
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
let startPoints = GM_getValue(pointsKey);
if (GM_getValue(timeKey) !== today.getTime()) {
GM_setValue(timeKey, today.getTime());
GM_setValue(pointsKey, currPoints);
return 0;
}
return currPoints - startPoints;
};
// 生成指定长度随机字符串
const generateRandomString = length => {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
};
// 注册菜单
registerMenuCommand();
// 积分页支持自动完成每日活动
if ('https://' + location.host === rewardHost) {
const run = GM_getValue(runDailyActivityKey);
if (run === 1) {
autoClickRewardActivity();
}
GM_setValue(runDailyActivityKey, 0);
return;
}
// 搜索页支持自动完成每日搜索
if (pathnames.includes(location.pathname)) {
insertStartBtn();
insertDailyBtn();
// 判断是否正在运行搜索任务
const searchParam = GM_getValue(searchParamKey);
if (decodeURIComponent(location.search) === searchParam) {
search();
return;
}
// 判断是否触发搜索任务
const run = GM_getValue(runSearchKey);
if (run === 1) {
start();
}
GM_setValue(runSearchKey, 0);
return;
}
})();