// ==UserScript==
// @name Microsoft Bing Rewards每日任务脚本
// @namespace https://gf.qytechs.cn/users/1362311
// @version 4.0.1
// @description 自动完成微软积分每日搜索任务
// @author honguangli
// @license MIT
// @match https://www.bing.com/*
// @match https://cn.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
// ==/UserScript==
(function() {
'use strict';
const pathnames = ['/', '/search']; // 触发搜索页面
const searchTimes = 40; // 自动搜索次数
const searchDelaySecondsMin = 15; // 每次搜索最小延迟时间,单位秒
const searchDelaySecondsMax = 30; // 每次搜索最大延迟时间,单位秒
const searchDelaySecondsFirst = 3; // 首次搜索延迟时间,单位秒,设置为0时立即触发
const closeTaskCardDelaySeconds = 0; // 搜索完成后弹窗自动关闭延迟时间,单位秒,设置为0时需手动关闭
const searchKeySource = ['BaiduHot','TouTiaoHot','DouYinHot', 'WeiBoHot']; // 搜索词条库
const countKey = 'count'; // 计数器
const pointsKey = 'points'; // 积分
const keywordsKey = 'search'; // 搜索词条
const searchParamKey = 'param'; // 搜索参数
let searchTimeoutId = null; // 搜索延时器id
let scrollIntervalId = null; // 模拟浏览定时器id
let delayIntervalId = null; // 搜索任务倒计时定时器id
GM_addStyle('#reward-task { position: fixed; top: 0; left: 0; width: 100%; background-color: rgba(0, 0, 0, .2); z-index: 99999; }');
GM_addStyle('#reward-task > .reward-task-content { width: 608px; 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; align-items: center; column-gap: 20px; }');
GM_addStyle('#reward-task > .reward-task-content .reward-task-stop, #reward-task > .reward-task-content .reward-task-cancel { 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: 9px 15px; font-size: 12px; border-radius: 3px; color: #fff; background: #ebb563; border-color: #ebb563; }');
// 注册(不可用)菜单
const registerMenuCommand = () => {
// 开始
GM_registerMenuCommand('开始', function () {
start();
}, 'o');
// 停止
GM_registerMenuCommand('停止', function () {
stop();
}, 'o');
};
// 开始
const start = () => {
GM_setValue(countKey, 1);
GM_setValue(pointsKey, -1);
search();
};
// 结束
const stop = () => {
GM_setValue(keywordsKey, {});
GM_setValue(countKey, 0);
GM_setValue(pointsKey, -1);
clearTimeout(searchTimeoutId);
clearInterval(scrollIntervalId);
};
// 获取词条
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);
} else {
source = (saveConfig.source + 1) % searchKeySource.length;
}
}
// 重置配置
const config = {
time: today.getTime(),
source: source,
keywords: [],
};
// 获取新词条
fetch('https://api.gumengya.com/Api/' + searchKeySource[config.source]).then(
(response) => {
if (!response.ok) {
throw new Error('获取搜索词条失败');
}
return response.json();
}
).then(
(data) => {
if (data.code !== 200) {
throw new Error('获取搜索词条失败');
}
config.keywords = data.data.map(item => item.title);
const keyword = config.keywords[0];
config.keywords.splice(0, 1);
GM_setValue(keywordsKey, config);
resolve(keyword);
}
).catch((err) => {
reject(err);
});
});
};
// 搜索
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;
// 添加任务进度卡片
insertTaskCard(count - 1, delay);
if (count > searchTimes) {
stop();
return;
}
// 模拟浏览网页
pretendHuman();
// 获取词条
getSearchInfo().then( keyword => {
// 延时查询
searchTimeoutId = setTimeout( () => {
// 更新计数器
GM_setValue(countKey, count+1);
const queryForm = document.getElementById('sb_form');
const queryInput = document.getElementById('sb_form_q');
queryInput.value = keyword;
const data = new FormData(queryForm);
let param = 'rand=' + (Math.random() * 10000) + 1000;
for (let item of data.entries()) {
if (item[0] === 'q') {
param += '&q=' + encodeURIComponent(item[1]);
} else {
param += '&' + item[0] + '=' + item[1];
}
}
// 更新搜索参数
GM_setValue(searchParamKey, param);
// 触发搜索
//queryForm.submit();
location.href = location.origin + '/search?' + param;
}, delay * 1000);
}).catch( err => {
stop();
alert('获取搜索词条失败', err.message);
});
};
// 插入搜索任务卡片
const insertTaskCard = (times, seconds) => {
removeTaskCard();
// 添加搜索任务卡片
const h = `<div id="reward-task" style="height: ${ document.documentElement.scrollHeight }px;">
<div class="reward-task-content">
<div>
<p id="reward-points" style="width: 200px;">当前积分:${ getCurrPoints() }</p>
<p id="task-points" style="width: 200px;">已获得积分:${ getTaskPoints() }</p>
<p id="task-tips"></p>
</div>
<div>
<p style="width: 200px;">进度:${ times } / ${ searchTimes }</p>
<p id="reward-task-delay" style="width: 200px;">${ times >= searchTimes ? `已完成${ closeTaskCardDelaySeconds > 0 ? ',' + closeTaskCardDelaySeconds + '秒后自动关闭' : '' }` : `等待时间: ${ seconds } 秒 ` }</p>
<div style="flex-grow: 1; text-align: right;">${ times >= searchTimes ? '<button type="button" class="reward-task-cancel">关闭</button>' : '<button type="button" class="reward-task-stop">停止</button>' }</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeEnd', h);
const btnStop = document.querySelector('#reward-task .reward-task-stop');
if (btnStop) {
btnStop.onclick = () => {
stop();
removeTaskCard();
};
}
const btnCancel = document.querySelector('#reward-task .reward-task-cancel');
if (btnCancel) {
btnCancel.onclick = () => {
stop();
removeTaskCard();
};
}
// 搜索任务已完成
if (times >= searchTimes && closeTaskCardDelaySeconds > 0) {
setTimeout( () => {
removeTaskCard();
}, closeTaskCardDelaySeconds * 1000);
}
// 定时更新积分和倒计时
let remainDelay = seconds;
delayIntervalId = setInterval(() => {
// 获取当前积分
const domCurrPoints = document.getElementById('reward-points');
const domTaskPoints = document.getElementById('task-points');
domCurrPoints.innerText = `当前积分:${ getCurrPoints() }`;
domTaskPoints.innerText = `已获得积分:${ getTaskPoints() }`;
if (remainDelay <= 0 || times >= searchTimes) {
return;
}
const domDelay = document.getElementById('reward-task-delay');
if (!domDelay) {
return;
}
remainDelay--;
domDelay.innerText = `等待时间: ${ remainDelay } 秒`;
}, 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);
};
// 获取当前积分
const getCurrPoints = () => {
const pointsContainer = document.querySelector('#rh_rwm .points-container');
if (!pointsContainer) {
return null;
}
let pointsStr = '';
if (!pointsContainer.classList.contains('balance-animation')) {
pointsStr = pointsContainer.innerText.trim();
} else {
pointsStr = document.documentElement.style.getPropertyValue('--rw-gp-balance-to');
}
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(pointsKey);
if (startPoints === -1) {
GM_setValue(pointsKey, currPoints);
return 0;
}
return currPoints - startPoints;
};
// 仅在匹配的页面触发
if (pathnames.includes(location.pathname)) {
registerMenuCommand();
// 判断是否处于任务页面
const searchParam = GM_getValue(searchParamKey);
if (location.search.replace('?', '') === searchParam) {
search();
}
}
})();