// ==UserScript==
// @name Microsoft Bing Rewards Daily Task Script (微软必应奖励每日任务脚本)
// @version 26.5.1.1
// @description Brian 自动完成微软必应每日搜索任务,智能积累奖励积分。支持实时进度追踪、热搜关键词、随机行为模拟,安全高效获取 Bing Rewards 积分。
// @author Brian
// @match https://*.bing.com/*
// @exclude https://rewards.bing.com/*
// @license MIT
// @icon https://www.bing.com/favicon.ico
// @connect top.baidu.com
// @connect www.toutiao.com
// @connect r.inews.qq.com
// @connect m.weibo.cn
// @run-at document-end
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_log
// ==/UserScript==
'use strict';
// 配置参数
const CONFIG = {
// ==================== 脚本基础信息 ====================
// 版本号(动态从GM_info获取)
get version() {
return GM_info?.script?.version || '1.0.0';
},
// ==================== 用户可配置参数 (通过设置页面/GM_value持久化) ====================
// 搜索form参数,⚠️ 手动进入https://cn.bing.com,确保登录后执行几次搜索,根据实际地址栏的form=xxx修改
get searchFormParam() {
return GM_getValue('customSearchFormParam', 'QBLH');
},
set searchFormParam(value) {
GM_setValue('customSearchFormParam', value);
},
// 面板默认是否收缩 (true=收缩, false=展开)
get panelDefaultCollapsed() {
return GM_getValue('customPanelDefaultCollapsed', false);
},
set panelDefaultCollapsed(value) {
GM_setValue('customPanelDefaultCollapsed', value);
},
// 最大搜索次数
get maxSearches() {
return GM_getValue('customMaxSearches', 20);
},
set maxSearches(value) {
GM_setValue('customMaxSearches', value);
},
// 是否随机加词,如:人工智能发展 --> 人工1智能发z展
get randomAddSearchWords() {
return GM_getValue('customRandomAddSearchWords', false);
},
set randomAddSearchWords(value) {
GM_setValue('customRandomAddSearchWords', value);
},
// 随机加词因子,控制加词的概率(0-1之间的小数),默认为0.3即30%概率添加字符
get randomAddSearchWordsFactor() {
return GM_getValue('customRandomAddSearchWordsFactor', 0.3);
},
set randomAddSearchWordsFactor(value) {
GM_setValue('customRandomAddSearchWordsFactor', value);
},
// 是否随机截词,如:人工1智能发z展 --> 人工1智
get randomCutSearchWords() {
return GM_getValue('customRandomCutSearchWords', false);
},
set randomCutSearchWords(value) {
GM_setValue('customRandomCutSearchWords', value);
},
// 随机截词因子,控制截取的概率(0-1之间的小数),默认为0.2即20%概率截取字符
get randomCutSearchWordsFactor() {
return GM_getValue('customRandomCutSearchWordsFactor', 0.2);
},
set randomCutSearchWordsFactor(value) {
GM_setValue('customRandomCutSearchWordsFactor', value);
},
// 是否点击搜索结果链接
get clickSearchResults() {
return GM_getValue('customClickSearchResults', false);
},
set clickSearchResults(value) {
GM_setValue('customClickSearchResults', value);
},
// 暂停间隔范围:每执行多少次搜索后暂停一次的区间
get pauseIntervalMin() {
return GM_getValue('customPauseIntervalMin', 3);
},
set pauseIntervalMin(value) {
GM_setValue('customPauseIntervalMin', value);
},
get pauseIntervalMax() {
return GM_getValue('customPauseIntervalMax', 5);
},
set pauseIntervalMax(value) {
GM_setValue('customPauseIntervalMax', value);
},
// 暂停时间范围(毫秒):每次暂停的持续时间区间
get pauseTimeMin() {
return GM_getValue('customPauseTimeMin', 10 * 60 * 1000);
},
set pauseTimeMin(value) {
GM_setValue('customPauseTimeMin', value);
},
get pauseTimeMax() {
return GM_getValue('customPauseTimeMax', 30 * 60 * 1000);
},
set pauseTimeMax(value) {
GM_setValue('customPauseTimeMax', value);
},
// 搜索延迟相关配置
get minDelay() {
return GM_getValue('customMinDelay', 15 * 1000);
},
set minDelay(value) {
GM_setValue('customMinDelay', value);
},
get maxDelay() {
return GM_getValue('customMaxDelay', 30 * 1000);
},
set maxDelay(value) {
GM_setValue('customMaxDelay', value);
},
// ==================== 内部固定参数 (不建议修改) ====================
// 小数部分延迟(随机延迟的基础值)
decimalDelay: 3 * 1000,
// 网络请求超时时间(毫秒):获取热门搜索词的最大等待时间
requestTimeout: 20 * 1000,
// 启动参数标记数组
startParams: ['bingTask', 'runSearch', 'initiateSearch', 'bingSearchMode', 'autoSearch', 'startTask', 'executeSearch', 'launchSearch', 'beginSearch', 'processSearch'],
// ==================== 更新日志 (便于提取和展示) ====================
changeLog: [
{
version: '26.5.1.1',
date: '2026-04-29',
changes: [
'功能增强:新增"点击搜索结果链接"配置选项,默认关闭,用户可在设置页面自行开启,提高脚本灵活性',
'问题修复:解决随机滚动功能不生效的问题',
'布局优化:全面支持响应式设计(Responsive Design),适配不同屏幕尺寸和分辨率',
]
},
{
version: '26.4.13.1',
date: '2026-04-13',
changes: [
'UI全面重构:采用现代化设计语言,渐变图标容器、分层阴影、圆角优化,视觉体验全面升级',
'交互动画增强:展开/收起面板添加平滑旋转动画,按钮悬停/点击具备三态反馈(默认/悬停/按下)',
'布局智能适配:收起模式极致紧凑(200px),仅保留倒计时与控制按钮;展开模式信息完整(380px)',
'状态栏优化:底部三栏布局(页面状态 | 任务执行 | 版本号),信息层次清晰,实时反映运行状态',
'设置页面升级:三段式布局(固定头部+可滚动内容+固定底部),卡片式表单,动态勾选标记,交互更直观',
'图标系统优化:全面采用 Emoji 图标,展开/收起使用 CSS rotate 实现 180° 平滑翻转,无闪烁',
'搜索行为优化:随机加词/截词功能可视化配置,示例代码等宽字体高亮显示,参数调整更便捷',
'性能提升:CSS 变量主题系统,支持系统级暗色模式自动切换,动画采用 GPU 加速'
]
},
{
version: '26.2.13.1',
date: '2026-02-13',
changes: [
'搜索URL优化:调整搜索URL参数构建,增强脚本行为真实性',
'菜单优化:调整菜单顺序,提升用户体验'
]
},
{
version: '26.1.26.1',
date: '2026-01-26',
changes: [
'统一搜索配置:移除设备类型区分,采用单一最大搜索次数配置',
'动态间隔控制:暂停间隔与暂停时间采用区间随机配置,增强行为真实性',
'UI界面优化:重构状态面板样式,提升用户体验与信息展示效率',
'行为模拟优化:实现随机滚动策略,模拟真实用户浏览行为',
'参数稳定性:启动参数采用每日固定机制,增强行为一致性',
'扩展参数池:扩充启动参数标记数组,增强脚本随机性与安全性'
]
},
{
version: '26.1.15.1',
date: '2026-01-15',
changes: [
'功能增强:增加随机加词和截词功能(默认关闭)',
'参数化配置:实现启动参数标记可配置化'
]
},
{
version: '26.1.6.1',
date: '2026-01-06',
changes: [
'热词获取优化:增加多源热词接口,完善热词补充与过滤机制',
'随机算法升级:改进Fisher-Yates洗牌算法,提高随机性质量',
'版本管理:调整版本号格式为年-月-日-版本号格式'
]
},
{
version: '0.0.7',
date: '2025-12-31',
changes: [
'问题修复:修正剩余时间计算错误及下个搜索词显示异常'
]
},
{
version: '0.0.6',
date: '2025-12-24',
changes: [
'问题修复:解决面板关闭按钮失效问题',
'功能增强:增加面板显示/隐藏切换功能',
'交互优化:优化关闭按钮悬停效果',
'问题修复:修复倒计时变量引用错误'
]
},
{
version: '0.0.5',
date: '2025-10-31',
changes: [
'UI优化:改进状态面板UI设计',
'功能增强:添加精确计时器,不受页面可见性影响',
'逻辑优化:改进搜索词获取策略'
]
},
{
version: '0.0.4',
date: '2025-10-30',
changes: [
'功能增强:添加搜索任务暂停机制',
'显示优化:改进进度显示方式',
'问题修复:修复移动端搜索数量限制问题'
]
},
{
version: '0.0.3',
date: '2025-10-30',
changes: [
'逻辑优化:优化搜索URL构建逻辑',
'参数丰富:添加更多搜索参数变化',
'错误处理:改进错误处理和重试机制'
]
},
{
version: '0.0.2',
date: '2025-10-13',
changes: [
'功能新增:添加状态显示面板',
'后台支持:支持后台运行和页面活跃状态显示',
'策略优化:优化搜索词获取策略'
]
},
{
version: '0.0.1',
date: '2025-08-22',
changes: [
'功能发布:初始版本发布',
'设备兼容:支持PC和移动端自动搜索',
'进度追踪:基础进度追踪功能'
]
}
]
};
// 状态管理
const state = {
searchWords: [],
statusPanel: null,
timers: new Set(),
isRunning: false,
searchHistory: [],
countdownStartTime: 0,
countdownDuration: 0,
lastActiveTime: Date.now()
};
// 工具函数
const utils = {
// 清理所有定时器
clearAllTimers() {
state.timers.forEach(timer => {
clearTimeout(timer);
clearInterval(timer);
});
state.timers.clear();
},
// 添加定时器到管理集合
addTimer(timer) {
state.timers.add(timer);
return timer;
},
// 随机对搜索词加词,例如:人工智能发展 --> 人工1智能发z展
addRandomCharsToSearchWord(word) {
if (!CONFIG.randomAddSearchWords || !word || Math.random() > CONFIG.randomAddSearchWordsFactor) return word;
// 控制添加字符的数量,避免过度添加导致词无意义
const maxAdditions = Math.min(3, Math.floor(word.length / 3)); // 最多添加原词长度1/3的随机字符
let result = word;
for (let i = 0; i < Math.floor(Math.random() * (maxAdditions + 1)); i++) {
// 随机选择插入位置(避开开头和结尾)
const insertPos = Math.floor(Math.random() * (result.length - 1)) + 1;
// 随机选择要插入的字符
const randomChar = String.fromCharCode(
Math.random() > 0.5 ?
Math.floor(Math.random() * 10) + 48 : // 数字 0-9
Math.floor(Math.random() * 26) + 97 // 小写字母 a-z
);
result = result.slice(0, insertPos) + randomChar + result.slice(insertPos);
}
return result;
},
// 随机对搜索词进行截取,例如:人工1智能发z展 --> 人工1智
cutSearchWordRandomly(word) {
if (!CONFIG.randomCutSearchWords || !word || Math.random() > CONFIG.randomCutSearchWordsFactor) return word;
// 控制截取长度,保留至少一半的字符
const minLength = Math.max(2, Math.ceil(word.length / 2)); // 至少保留2个字符或一半字符
const maxLength = word.length; // 最大不超过原词长度
if (minLength >= maxLength) return word;
// 随机选择截取长度
const cutLength = Math.floor(Math.random() * (maxLength - minLength)) + minLength;
return word.substring(0, cutLength);
},
// 依次应用加词和截取
processSearchWord(word) {
// 先加词
let processedWord = this.addRandomCharsToSearchWord(word);
// 再截取
processedWord = this.cutSearchWordRandomly(processedWord);
return processedWord;
},
// 生成随机延迟
getRandomDelay() {
return Math.random() * (CONFIG.maxDelay - CONFIG.minDelay) + CONFIG.minDelay;
},
// 从区间内随机取暂停间隔
getRandomPauseInterval() {
return Math.floor(Math.random() * (CONFIG.pauseIntervalMax - CONFIG.pauseIntervalMin + 1)) + CONFIG.pauseIntervalMin;
},
// 从区间内随机取暂停时间
getRandomPauseTime() {
return Math.floor(Math.random() * (CONFIG.pauseTimeMax - CONFIG.pauseTimeMin + 1)) + CONFIG.pauseTimeMin;
},
// 随机选择一个启动参数(每天保持相同值)
getRandomStartParam() {
// 获取今天的日期字符串(格式:YYYY-MM-DD)
const today = new Date().toISOString().split('T')[0];
// 检查是否已经为今天选择了启动参数
const todayStartParamKey = 'todaySelectedStartParam';
const todayStartParamDateKey = 'todaySelectedStartParamDate';
// 如果存储的日期不是今天,则重新选择
if (GM_getValue(todayStartParamDateKey) !== today) {
// 随机选择一个新的启动参数
const startParam = CONFIG.startParams[Math.floor(Math.random() * CONFIG.startParams.length)];
// 存储选中的参数及其对应的日期
GM_setValue(todayStartParamKey, startParam);
GM_setValue(todayStartParamDateKey, today);
// 同时更新内存中的状态
state.selectedStartParam = startParam;
console.log(`Selected start parameter for today: ${startParam}`);
return startParam;
} else {
// 返回当天已选择的参数
const startParam = GM_getValue(todayStartParamKey);
// 更新内存中的状态
state.selectedStartParam = startParam;
console.log(`Using previously selected start parameter: ${startParam}`);
return startParam;
}
},
// 安全JSON解析
safeJsonParse(str, defaultValue = null) {
try {
return JSON.parse(str);
} catch {
return defaultValue;
}
},
// Fisher-Yates洗牌算法
shuffleArray(array) {
const result = [...array]; // 创建副本以避免修改原数组
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]]; // 交换元素
}
return result;
},
// 生成随机ID
generateId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2, 9);
},
// 获取精确的剩余时间(不受标签页激活状态影响)
getAccurateRemainingTime() {
if (!state.countdownStartTime || !state.countdownDuration) return 0;
const elapsed = Date.now() - state.countdownStartTime;
const remaining = Math.max(0, state.countdownDuration - elapsed);
return remaining / 1000; // 转换为秒
},
// 检查页面是否可见
isPageVisible() {
return !document.hidden;
},
// 页面可见性变化处理
handleVisibilityChange(callback) {
document.addEventListener('visibilitychange', () => {
state.lastActiveTime = Date.now();
if (!document.hidden) {
callback();
}
});
}
};
// 搜索词库
const SEARCH_WORDS = [
// 日常生活类
"今天天气怎么样", "附近有什么好吃的", "怎么做红烧肉", "天气预报",
"快递查询", "手机丢了怎么办", "忘记密码怎么找回", "如何办理身份证",
"地铁线路图", "公交时刻表", "医院挂号流程", "社保怎么交",
"个人所得税怎么算", "公积金提取条件", "居住证办理流程",
// 购物消费
"淘宝优惠券", "京东白条怎么用", "拼多多靠谱吗", "二手交易平台",
"哪个牌子的空调好", "冰箱怎么选", "洗衣机推荐", "扫地机器人测评",
"运动鞋品牌对比", "护肤品推荐", "化妆品正品查询",
// 美食餐饮
"附近奶茶店", "火锅底料做法", "蛋糕烘焙教程", "减肥餐食谱",
"早餐吃什么健康", "外卖平台哪个好", "咖啡机推荐", "空气炸锅食谱",
"家常菜做法", "烘焙入门教程", "日料制作", "西餐做法",
// 旅游出行
"周末去哪玩", "假期旅游攻略", "机票什么时候买便宜", "酒店比价",
"签证办理流程", "自驾游路线推荐", "背包客装备清单", "民宿预订平台",
"高铁票怎么抢", "航班延误怎么办", "旅行保险有必要吗",
// 学习工作
"Excel技巧大全", "PPT模板下载", "Python入门教程", "英语学习方法",
"考研复习资料", "公务员考试条件", "简历怎么写", "面试技巧",
"远程办公软件", "时间管理方法", "职场沟通技巧", "副业赚钱项目",
"在线课程平台", "编程学习路线", "数据分析工具",
// 娱乐休闲
"最近好看的电影", "Netflix推荐剧集", "switch游戏推荐", "Steam打折游戏",
"抖音热门视频", "B站up主推荐", "音乐播放器哪个好", "耳机音质对比",
"摄影入门教程", "吉他教学视频", "绘画学习app", "手账制作教程",
// 健康运动
"健身房怎么选", "瑜伽初学者动作", "跑步姿势纠正", "减脂增肌计划",
"失眠怎么办", "颈椎保健操", "护眼方法", "久坐危害",
"体检项目有哪些", "疫苗接种预约", "心理咨询哪里好", "中医调理方法",
// 科技数码
"WiFi信号增强方法", "电脑卡顿怎么办", "手机电池保养", "数据备份方案",
"智能家居设备推荐", "路由器怎么选", "NAS搭建教程", "云服务器价格",
"AI工具有哪些", "ChatGPT使用技巧", "VR眼镜值得买吗", "无人机航拍技巧",
// 金融理财
"基金定投策略", "股票开户流程", "理财产品对比", "信用卡积分兑换",
"房贷利率计算", "养老保险怎么交", "儿童教育金规划", "应急资金准备",
"通货膨胀影响", "黄金投资方式", "外汇交易入门", "税务筹划方法",
// 家居装修
"小户型装修灵感", "家具购买指南", "除甲醛方法", "智能家居安装",
"墙面颜色搭配", "厨房收纳技巧", "卫生间防水处理", "阳台改造方案",
"灯具选择建议", "窗帘搭配技巧", "地板材质对比", "装修公司怎么选",
// 亲子教育
"早教机构推荐", "儿童绘本清单", "学区房政策", "兴趣班选择",
"亲子游目的地", "儿童营养餐", "育儿经验分享", "家庭教育方法",
"暑假活动安排", "儿童安全常识", "青少年心理健康", "留学申请流程",
// 汽车交通
"新能源汽车补贴", "二手车估值", "驾校报名流程", "违章查询",
"车险怎么买划算", "汽车保养周期", "新能源车充电桩", "堵车路段查询",
"停车位怎么找", "共享汽车平台", "摩托车驾照考试", "电动车新国标",
// 宠物养护
"猫咪喂养指南", "狗狗训练方法", "宠物医院推荐", "猫粮品牌对比",
"宠物美容教程", "鱼缸 setup", "鸟笼清洁", "仓鼠饲养注意事项",
"宠物保险有必要吗", "流浪猫救助", "宠物寄养服务", "训犬师推荐",
// 本地生活
"附近停车场", "药店营业时间", "超市促销信息", "理发店推荐",
"洗衣店价格", "修手机的地方", "开锁电话", "搬家公司收费",
"家政保洁服务", "管道疏通电话", "家电维修", "宠物洗澡",
// 实用工具查询
"汇率换算", "单位转换", "日历农历", "黄道吉日",
"成语解释", "诗词鉴赏", "历史事件查询", "名人传记",
"地图导航", "翻译软件", "计算器在线", "单位换算器"
];
/**
* 构建搜索URL
*/
function buildSearchUrl(searchWord) {
const domain = 'https://cn.bing.com';
const form = CONFIG.searchFormParam;
const length = searchWord.length;
const hitPosition = Math.random() < 0.9 ? 0 : Math.floor(Math.random() * Math.min(length, 5)) + 1;
const sc = `${hitPosition}-${length}`;
const urlParams = new URLSearchParams({
q: searchWord,
form,
sp: -1,
lq: 0,
pq: searchWord,
sc,
qs: 'n',
sk: '',
cvid: utils.generateId(),
});
const startParam = utils.getRandomStartParam();
return `${domain}/search?${urlParams.toString()}&${startParam}=1`;
}
/**
* 创建状态面板
*/
function createStatusPanel() {
if (state.statusPanel) return state.statusPanel;
const panel = document.createElement('div');
panel.id = 'bing-rewards-panel';
// 从配置中读取默认展开/收缩状态
const defaultCollapsed = CONFIG.panelDefaultCollapsed;
state.isPanelCollapsed = defaultCollapsed;
panel.innerHTML = `
页面活跃
正在执行搜索任务...
v${GM_info.script.version}
`;
// 添加CSS动画样式
const styleElement = document.createElement('style');
styleElement.textContent = `
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.2);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
max-height: 0;
}
to {
opacity: 1;
transform: translateY(0);
max-height: 1000px;
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 响应式布局 - 主面板 */
#bing-rewards-panel {
/* 移动端适配 */
@media (max-width: 768px) {
right: 10px !important;
bottom: 20px !important;
min-width: calc(100vw - 60px) !important;
max-width: calc(100vw - 60px) !important;
border-radius: 16px !important;
}
@media (max-width: 480px) {
right: 8px !important;
bottom: 16px !important;
padding: 12px 14px !important;
min-width: calc(100vw - 46px) !important;
max-width: calc(100vw - 46px) !important;
}
/* 确保面板有最大宽度限制 */
width: auto !important;
}
/* 面板头部响应式 */
#bing-rewards-panel #panel-header {
@media (max-width: 480px) {
padding: 0 0 10px 0 !important;
gap: 6px !important;
}
}
/* 面板头部标题容器 */
#bing-rewards-panel #panel-title-container h3 {
@media (max-width: 480px) {
font-size: 14px !important;
}
}
#bing-rewards-panel #panel-title-container div {
@media (max-width: 480px) {
font-size: 10px !important;
}
}
/* 倒计时响应式 */
#bing-rewards-panel #panel-countdown {
@media (max-width: 480px) {
font-size: 11px !important;
padding: 5px 10px !important;
margin-left: 8px !important;
}
}
/* 按钮响应式 */
#bing-rewards-panel #panel-toggle-btn,
#bing-rewards-panel #panel-settings-btn,
#bing-rewards-panel #panel-close-btn {
@media (max-width: 480px) {
width: 28px !important;
height: 28px !important;
font-size: 14px !important;
}
}
/* 面板底部状态栏响应式 */
#bing-rewards-panel #panel-body > div:last-child {
@media (max-width: 480px) {
flex-wrap: wrap !important;
gap: 6px !important;
padding-top: 10px !important;
margin-top: 12px !important;
}
}
/* 进度条区域响应式 */
#bing-rewards-panel #panel-content > div {
@media (max-width: 480px) {
gap: 8px !important;
}
}
`;
document.head.appendChild(styleElement);
// 检测系统主题并应用相应的CSS变量
const isDarkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
// 定义主题变量
const themeVariables = {
light: {
'--panel-bg': '#ffffff',
'--panel-border': '#e0e0e0',
'--panel-shadow': 'rgba(0, 0, 0, 0.1)',
'--panel-primary-color': '#0067b8',
'--panel-text-primary': '#1a1a1a',
'--panel-text-secondary': '#666666',
'--panel-text-muted': '#999999',
'--panel-progress-bg': '#f0f0f0',
'--panel-success-bg': '#f0f9f0',
'--panel-success-text': '#107c10',
'--panel-warning-bg': '#fff8e6',
'--panel-warning-border': '#ffb900',
'--panel-warning-text': '#8a6900',
'--panel-info-bg': '#f0f7ff',
'--panel-info-text': '#005a9e',
'--panel-hover-bg': '#f5f5f5'
},
dark: {
'--panel-bg': '#1e1e1e',
'--panel-border': '#3f3f3f',
'--panel-shadow': 'rgba(0, 0, 0, 0.4)',
'--panel-primary-color': '#4fc3f7',
'--panel-text-primary': '#e0e0e0',
'--panel-text-secondary': '#b0b0b0',
'--panel-text-muted': '#888888',
'--panel-progress-bg': '#2d2d2d',
'--panel-success-bg': '#1a3a1a',
'--panel-success-text': '#4caf50',
'--panel-warning-bg': '#3d3520',
'--panel-warning-border': '#ffa726',
'--panel-warning-text': '#ffd54f',
'--panel-info-bg': '#1a2a3a',
'--panel-info-text': '#64b5f6',
'--panel-hover-bg': '#2a2a2a'
}
};
const theme = isDarkMode ? themeVariables.dark : themeVariables.light;
Object.assign(panel.style, {
position: 'fixed',
bottom: '50px',
right: '20px',
background: theme['--panel-bg'],
border: `1px solid ${theme['--panel-border']}`,
borderRadius: '20px',
padding: defaultCollapsed ? '16px 20px' : '24px',
boxShadow: defaultCollapsed
? `0 8px 24px ${theme['--panel-shadow']}, 0 0 0 1px ${theme['--panel-border']}30`
: `0 16px 48px ${theme['--panel-shadow']}, 0 0 0 1px ${theme['--panel-border']}40, 0 0 80px ${theme['--panel-primary-color']}10`,
zIndex: '10000',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
fontSize: '13px',
minWidth: defaultCollapsed ? '200px' : '380px',
maxWidth: '420px',
backdropFilter: 'blur(20px) saturate(180%)',
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
color: theme['--panel-text-primary'],
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
letterSpacing: '-0.2px'
});
// 设置CSS变量
Object.entries(theme).forEach(([key, value]) => {
panel.style.setProperty(key, value);
});
document.body.appendChild(panel);
state.statusPanel = panel;
// 为展开/收缩按钮添加事件监听器
const toggleBtn = document.getElementById('panel-toggle-btn');
const panelBody = document.getElementById('panel-body');
const panelHeader = document.getElementById('panel-header');
const countdownElement = document.getElementById('panel-countdown');
if (toggleBtn && panelBody && panelHeader) {
toggleBtn.addEventListener('click', () => {
state.isPanelCollapsed = !state.isPanelCollapsed;
// 同步更新 Config 的缓存值
CONFIG.panelDefaultCollapsed = state.isPanelCollapsed;
if (state.isPanelCollapsed) {
// 收缩状态 - 更紧凑的布局
panelBody.style.display = 'none';
if (countdownElement) countdownElement.style.display = '';
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = 'rotate(0deg)';
}
const titleContainer = document.getElementById('panel-title-container');
if (titleContainer) {
titleContainer.style.display = 'none';
}
toggleBtn.title = '展开面板';
panelHeader.style.paddingBottom = '0';
panel.style.padding = '16px 20px';
panel.style.minWidth = '200px';
panel.style.boxShadow = `0 8px 24px ${theme['--panel-shadow']}, 0 0 0 1px ${theme['--panel-border']}30`;
} else {
// 展开状态 - 更宽松的布局
panelBody.style.display = 'block';
if (countdownElement) countdownElement.style.display = 'none';
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = 'rotate(180deg)';
}
const titleContainer = document.getElementById('panel-title-container');
if (titleContainer) {
titleContainer.style.display = '';
}
toggleBtn.title = '收起面板';
panelHeader.style.paddingBottom = '16px';
panel.style.padding = '24px';
panel.style.minWidth = '380px';
panel.style.boxShadow = `0 16px 48px ${theme['--panel-shadow']}, 0 0 0 1px ${theme['--panel-border']}40, 0 0 80px ${theme['--panel-primary-color']}10`;
}
// 立即更新面板内容以刷新倒计时显示
updateStatusPanel();
});
// 添加悬停效果
toggleBtn.addEventListener('mouseenter', () => {
toggleBtn.style.backgroundColor = theme['--panel-hover-bg'];
toggleBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = state.isPanelCollapsed ? 'scale(1.1) rotate(0deg)' : 'scale(1.1) rotate(180deg)';
}
});
toggleBtn.addEventListener('mouseleave', () => {
toggleBtn.style.backgroundColor = 'transparent';
toggleBtn.style.boxShadow = 'none';
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = state.isPanelCollapsed ? 'scale(1) rotate(0deg)' : 'scale(1) rotate(180deg)';
}
});
toggleBtn.addEventListener('mousedown', () => {
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = state.isPanelCollapsed ? 'scale(0.95) rotate(0deg)' : 'scale(0.95) rotate(180deg)';
}
});
toggleBtn.addEventListener('mouseup', () => {
const toggleIcon = document.getElementById('toggle-icon');
if (toggleIcon) {
toggleIcon.style.transform = state.isPanelCollapsed ? 'scale(1.1) rotate(0deg)' : 'scale(1.1) rotate(180deg)';
}
});
}
// 为设置按钮添加事件监听器
const settingsBtn = document.getElementById('panel-settings-btn');
if (settingsBtn) {
settingsBtn.addEventListener('click', () => {
showSettingsDialog(theme);
});
// 添加悬停效果
settingsBtn.addEventListener('mouseenter', () => {
settingsBtn.style.backgroundColor = theme['--panel-hover-bg'];
settingsBtn.style.transform = 'scale(1.1) rotate(30deg)';
settingsBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
});
settingsBtn.addEventListener('mouseleave', () => {
settingsBtn.style.backgroundColor = 'transparent';
settingsBtn.style.transform = 'scale(1) rotate(0deg)';
settingsBtn.style.boxShadow = 'none';
});
settingsBtn.addEventListener('mousedown', () => {
settingsBtn.style.transform = 'scale(0.95) rotate(30deg)';
});
settingsBtn.addEventListener('mouseup', () => {
settingsBtn.style.transform = 'scale(1.1) rotate(30deg)';
});
}
// 为关闭按钮添加事件监听器
const closeBtn = document.getElementById('panel-close-btn');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
panel.style.display = 'none';
});
// 添加悬停效果
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.backgroundColor = '#ffebee';
closeBtn.style.color = '#f44336';
closeBtn.style.transform = 'scale(1.1) rotate(90deg)';
closeBtn.style.boxShadow = '0 2px 8px rgba(244,67,54,0.2)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.backgroundColor = 'transparent';
closeBtn.style.color = theme['--panel-text-secondary'];
closeBtn.style.transform = 'scale(1) rotate(0deg)';
closeBtn.style.boxShadow = 'none';
});
closeBtn.addEventListener('mousedown', () => {
closeBtn.style.transform = 'scale(0.95) rotate(90deg)';
});
closeBtn.addEventListener('mouseup', () => {
closeBtn.style.transform = 'scale(1.1) rotate(90deg)';
});
}
// 监听系统主题变化
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const handleThemeChange = (e) => {
const newTheme = e.matches ? themeVariables.dark : themeVariables.light;
// 更新面板背景
panel.style.background = newTheme['--panel-bg'];
panel.style.borderColor = newTheme['--panel-border'];
panel.style.boxShadow = `0 12px 32px ${newTheme['--panel-shadow']}, 0 0 0 1px ${newTheme['--panel-border']}40`;
panel.style.color = newTheme['--panel-text-primary'];
// 更新CSS变量
Object.entries(newTheme).forEach(([key, value]) => {
panel.style.setProperty(key, value);
});
// 更新按钮颜色
if (toggleBtn) {
toggleBtn.style.color = newTheme['--panel-text-secondary'];
}
if (settingsBtn) {
settingsBtn.style.color = newTheme['--panel-text-secondary'];
}
if (closeBtn) {
closeBtn.style.color = newTheme['--panel-text-secondary'];
}
// 重新渲染面板内容
updateStatusPanel();
};
// 兼容不同浏览器
if (mediaQuery.addEventListener) {
mediaQuery.addEventListener('change', handleThemeChange);
} else if (mediaQuery.addListener) {
mediaQuery.addListener(handleThemeChange);
}
}
// 监听页面可见性变化
utils.handleVisibilityChange(updateStatusPanel);
updateStatusPanel();
return panel;
}
/**
* 显示设置对话框
*/
function showSettingsDialog(theme) {
// 检查是否已存在对话框
const existingDialog = document.getElementById('settings-dialog');
if (existingDialog) {
existingDialog.remove();
}
// 每次打开对话框时重新获取最新配置值
// ⚠️ 重要: 直接使用 GM_getValue 读取最新值,而不是依赖 CONFIG 对象(可能缓存旧值)
// 这样可以确保用户看到的就是当前存储的实际配置,避免显示过期数据
const savedSearchFormParam = GM_getValue('customSearchFormParam', 'QBLH');
const savedPanelCollapsed = GM_getValue('customPanelDefaultCollapsed', false);
const savedMaxSearches = GM_getValue('customMaxSearches', 20);
const savedRandomAdd = GM_getValue('customRandomAddSearchWords', false);
const savedRandomAddFactor = GM_getValue('customRandomAddSearchWordsFactor', 0.3);
const savedRandomCut = GM_getValue('customRandomCutSearchWords', false);
const savedRandomCutFactor = GM_getValue('customRandomCutSearchWordsFactor', 0.2);
const savedClickSearchResults = GM_getValue('customClickSearchResults', false);
const savedPauseIntervalMin = GM_getValue('customPauseIntervalMin', 3);
const savedPauseIntervalMax = GM_getValue('customPauseIntervalMax', 5);
const savedPauseTimeMin = GM_getValue('customPauseTimeMin', 10 * 60 * 1000) / 60000; // 转换为分钟
const savedPauseTimeMax = GM_getValue('customPauseTimeMax', 30 * 60 * 1000) / 60000; // 转换为分钟
const savedMinDelay = GM_getValue('customMinDelay', 15 * 1000) / 1000; // 转换为秒
const savedMaxDelay = GM_getValue('customMaxDelay', 30 * 1000) / 1000; // 转换为秒
console.log('📋 加载最新设置:', {
searchFormParam: savedSearchFormParam,
panelCollapsed: savedPanelCollapsed,
maxSearches: savedMaxSearches,
randomAdd: savedRandomAdd,
randomAddFactor: savedRandomAddFactor,
randomCut: savedRandomCut,
randomCutFactor: savedRandomCutFactor,
clickSearchResults: savedClickSearchResults,
pauseInterval: `${savedPauseIntervalMin}-${savedPauseIntervalMax}`,
pauseTime: `${savedPauseTimeMin}-${savedPauseTimeMax}分钟`,
delay: `${savedMinDelay}-${savedMaxDelay}秒`
});
// 版本号(从CONFIG获取)
const currentVersion = CONFIG.version;
// 生成更新日志HTML(从CONFIG.changeLog获取)
const generateChangeLogHTML = () => {
return CONFIG.changeLog.map(item => `
V${item.version}
${item.date ? `${item.date}` : ''}
${item.changes.map(f => `- ${f}
`).join('')}
`).join('');
};
// 创建设置对话框
const dialog = document.createElement('div');
dialog.id = 'settings-dialog';
dialog.innerHTML = `
🚀
Bing Rewards 自动任务脚本
📌
版本: v${currentVersion}
👤
作者: Brian
✨
功能说明
-
🔍 自动搜索 - 自动执行必应搜索任务,获取每日积分
-
🎯 智能优化 - 支持随机加词、截词功能,模拟真实搜索行为
-
⏱️ 智能延迟 - 可配置的搜索间隔和暂停时间,避免触发风控
-
📊 进度追踪 - 实时显示任务进度和剩余时间
📜
使用条款与协议
本脚本仅供学习和个人使用。使用本脚本即表示您同意以下条款:
- 本脚本仅用于个人学习和研究目的
- 请勿用于商业用途或大规模部署
- 使用本脚本需遵守微软必应服务条款
- 作者不对使用本脚本造成的任何后果负责
- 建议合理使用,避免过度频繁操作
💬
联系与支持
如果您遇到问题或有改进建议,欢迎随时联系!
📝
更新日志
${generateChangeLogHTML()}
`;
document.body.appendChild(dialog);
// 获取所有元素
const closeBtn = document.getElementById('settings-close-btn');
const cancelBtn = document.getElementById('settings-cancel-btn');
const resetBtn = document.getElementById('settings-reset-btn');
const saveBtn = document.getElementById('settings-save-btn');
const searchFormInput = document.getElementById('search-form-param-input');
const maxSearchesInput = document.getElementById('max-searches-input');
const panelStateRadios = document.getElementsByName('panel-default-state');
const randomAddCheckbox = document.getElementById('random-add-checkbox');
const randomCutCheckbox = document.getElementById('random-cut-checkbox');
const clickSearchResultsCheckbox = document.getElementById('click-search-results-checkbox');
const randomAddFactorInput = document.getElementById('random-add-factor-input');
const randomCutFactorInput = document.getElementById('random-cut-factor-input');
const pauseIntervalMinInput = document.getElementById('pause-interval-min-input');
const pauseIntervalMaxInput = document.getElementById('pause-interval-max-input');
const pauseTimeMinInput = document.getElementById('pause-time-min-input');
const pauseTimeMaxInput = document.getElementById('pause-time-max-input');
const minDelayInput = document.getElementById('min-delay-input');
const maxDelayInput = document.getElementById('max-delay-input');
// 搜索相关元素
const searchInput = document.getElementById('settings-search-input');
const clearSearchBtn = document.getElementById('clear-search-btn');
// 配置管理相关元素
const exportConfigBtn = document.getElementById('export-config-btn');
const importConfigBtn = document.getElementById('import-config-btn');
const configFileInput = document.getElementById('config-file-input');
const closeDialog = () => {
dialog.remove();
};
closeBtn.addEventListener('click', closeDialog);
cancelBtn.addEventListener('click', closeDialog);
// 点击背景关闭
dialog.querySelector('div').addEventListener('click', (e) => {
if (e.target === dialog.querySelector('div')) {
closeDialog();
}
});
// 设置项搜索功能
const performSearch = (keyword) => {
const sections = dialog.querySelectorAll('.config-section');
let foundCount = 0;
sections.forEach(section => {
const sectionTitle = section.getAttribute('data-section');
const searchTags = section.querySelectorAll('[data-search-tags]');
let shouldShow = false;
// 检查section标题是否匹配
if (sectionTitle && sectionTitle.toLowerCase().includes(keyword.toLowerCase())) {
shouldShow = true;
}
// 检查各个设置项的搜索标签
searchTags.forEach(tagElement => {
const tags = tagElement.getAttribute('data-search-tags');
if (tags && tags.toLowerCase().includes(keyword.toLowerCase())) {
shouldShow = true;
}
});
// 检查section内的文本内容
if (!shouldShow) {
const textContent = section.textContent.toLowerCase();
if (textContent.includes(keyword.toLowerCase())) {
shouldShow = true;
}
}
if (shouldShow) {
section.style.display = 'block';
foundCount++;
// 确保匹配的section是展开状态
const content = section.querySelector('.section-content');
if (content) {
content.style.maxHeight = '1000px';
content.style.opacity = '1';
}
const toggle = section.querySelector('.section-toggle');
if (toggle) {
toggle.style.transform = 'rotate(0deg)';
}
} else {
section.style.display = 'none';
}
});
// 显示搜索结果提示
const searchResultsHint = document.getElementById('search-results-hint');
if (keyword.trim()) {
if (!searchResultsHint) {
const hint = document.createElement('div');
hint.id = 'search-results-hint';
hint.style.cssText = `
padding: 12px 16px;
background: ${theme['--panel-info-bg']};
border-radius: 10px;
margin-bottom: 16px;
font-size: 12px;
color: ${theme['--panel-text-muted']};
display: flex;
align-items: center;
gap: 8px;
`;
hint.innerHTML = `🔍找到 ${foundCount} 个匹配的设置项`;
dialog.querySelector('.dialog-content').insertBefore(hint, dialog.querySelector('.dialog-content').firstChild);
} else {
searchResultsHint.innerHTML = `🔍找到 ${foundCount} 个匹配的设置项`;
}
} else if (searchResultsHint) {
searchResultsHint.remove();
}
};
// 导航切换逻辑
const navSettingsBtn = document.getElementById('nav-settings-btn');
const navAboutBtn = document.getElementById('nav-about-btn');
const settingsContent = dialog.querySelector('.dialog-content:not(#about-content)');
const aboutContent = document.getElementById('about-content');
const searchWrapper = document.getElementById('search-wrapper');
const dialogFooter = dialog.querySelector('.dialog-footer');
const switchToSettings = () => {
navSettingsBtn.classList.add('active');
navSettingsBtn.style.background = theme['--panel-primary-color'];
navSettingsBtn.style.color = '#ffffff';
navAboutBtn.classList.remove('active');
navAboutBtn.style.background = 'transparent';
navAboutBtn.style.color = theme['--panel-text-secondary'];
settingsContent.style.display = 'block';
aboutContent.style.display = 'none';
searchWrapper.style.display = 'block';
dialogFooter.style.display = 'flex';
// 清除搜索
searchInput.value = '';
clearSearchBtn.style.display = 'none';
performSearch('');
};
const switchToAbout = () => {
navAboutBtn.classList.add('active');
navAboutBtn.style.background = theme['--panel-primary-color'];
navAboutBtn.style.color = '#ffffff';
navSettingsBtn.classList.remove('active');
navSettingsBtn.style.background = 'transparent';
navSettingsBtn.style.color = theme['--panel-text-secondary'];
aboutContent.style.display = 'block';
settingsContent.style.display = 'none';
searchWrapper.style.display = 'none';
dialogFooter.style.display = 'none';
};
navSettingsBtn.addEventListener('click', switchToSettings);
navAboutBtn.addEventListener('click', switchToAbout);
searchInput.addEventListener('input', (e) => {
const keyword = e.target.value;
performSearch(keyword);
// 显示/隐藏清除按钮
clearSearchBtn.style.display = keyword.trim() ? 'block' : 'none';
});
clearSearchBtn.addEventListener('click', () => {
searchInput.value = '';
clearSearchBtn.style.display = 'none';
performSearch('');
});
// 设置分组折叠/展开功能
const sectionHeaders = dialog.querySelectorAll('.section-header');
sectionHeaders.forEach(header => {
header.addEventListener('click', () => {
const section = header.closest('.config-section');
const content = section.querySelector('.section-content');
const toggle = section.querySelector('.section-toggle');
if (content.style.maxHeight === '0px' || !content.style.maxHeight) {
content.style.maxHeight = '1000px';
content.style.opacity = '1';
toggle.style.transform = 'rotate(0deg)';
} else {
content.style.maxHeight = '0px';
content.style.opacity = '0';
toggle.style.transform = 'rotate(-90deg)';
}
});
});
// 配置导出功能
exportConfigBtn.addEventListener('click', () => {
const config = {
searchFormParam: GM_getValue('customSearchFormParam', 'QBLH'),
panelDefaultCollapsed: GM_getValue('customPanelDefaultCollapsed', false),
maxSearches: GM_getValue('customMaxSearches', 20),
randomAddSearchWords: GM_getValue('customRandomAddSearchWords', false),
randomAddSearchWordsFactor: GM_getValue('customRandomAddSearchWordsFactor', 0.3),
randomCutSearchWords: GM_getValue('customRandomCutSearchWords', false),
randomCutSearchWordsFactor: GM_getValue('customRandomCutSearchWordsFactor', 0.2),
clickSearchResults: GM_getValue('customClickSearchResults', false),
pauseIntervalMin: GM_getValue('customPauseIntervalMin', 3),
pauseIntervalMax: GM_getValue('customPauseIntervalMax', 5),
pauseTimeMin: GM_getValue('customPauseTimeMin', 10 * 60 * 1000),
pauseTimeMax: GM_getValue('customPauseTimeMax', 30 * 60 * 1000),
minDelay: GM_getValue('customMinDelay', 15 * 1000),
maxDelay: GM_getValue('customMaxDelay', 30 * 1000),
exportTime: new Date().toISOString(),
version: CONFIG.version
};
const blob = new Blob([JSON.stringify(config, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `BingRewards_config_${new Date().toISOString().split('T')[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
GM_notification({
title: '配置导出成功',
text: '配置文件已保存到本地',
icon: '📥',
timeout: 3000
});
});
// 配置导入功能
importConfigBtn.addEventListener('click', () => {
configFileInput.click();
});
configFileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const config = JSON.parse(event.target.result);
// 确认导入
if (!confirm(`⚠️ 确认导入配置文件?\n\n这将覆盖当前所有设置。\n\n导入时间: ${config.exportTime || '未知'}\n版本: ${config.version || '未知'}`)) {
return;
}
// 保存配置
if (config.searchFormParam !== undefined) GM_setValue('customSearchFormParam', config.searchFormParam);
if (config.panelDefaultCollapsed !== undefined) GM_setValue('customPanelDefaultCollapsed', config.panelDefaultCollapsed);
if (config.maxSearches !== undefined) GM_setValue('customMaxSearches', config.maxSearches);
if (config.randomAddSearchWords !== undefined) GM_setValue('customRandomAddSearchWords', config.randomAddSearchWords);
if (config.randomAddSearchWordsFactor !== undefined) GM_setValue('customRandomAddSearchWordsFactor', config.randomAddSearchWordsFactor);
if (config.randomCutSearchWords !== undefined) GM_setValue('customRandomCutSearchWords', config.randomCutSearchWords);
if (config.randomCutSearchWordsFactor !== undefined) GM_setValue('customRandomCutSearchWordsFactor', config.randomCutSearchWordsFactor);
if (config.clickSearchResults !== undefined) GM_setValue('customClickSearchResults', config.clickSearchResults);
if (config.pauseIntervalMin !== undefined) GM_setValue('customPauseIntervalMin', config.pauseIntervalMin);
if (config.pauseIntervalMax !== undefined) GM_setValue('customPauseIntervalMax', config.pauseIntervalMax);
if (config.pauseTimeMin !== undefined) GM_setValue('customPauseTimeMin', config.pauseTimeMin);
if (config.pauseTimeMax !== undefined) GM_setValue('customPauseTimeMax', config.pauseTimeMax);
if (config.minDelay !== undefined) GM_setValue('customMinDelay', config.minDelay);
if (config.maxDelay !== undefined) GM_setValue('customMaxDelay', config.maxDelay);
GM_notification({
title: '配置导入成功',
text: '配置已成功导入,页面将刷新',
icon: '📤',
timeout: 3000
});
closeDialog();
setTimeout(() => window.location.reload(), 2000);
} catch (error) {
alert('❌ 配置文件格式错误,请确保导入的是有效的JSON配置文件');
console.error('配置导入失败:', error);
}
};
reader.readAsText(file);
});
// 添加关闭按钮悬停效果
closeBtn.addEventListener('mouseenter', () => {
closeBtn.style.backgroundColor = theme['--panel-hover-bg'];
closeBtn.style.color = theme['--panel-primary-color'];
closeBtn.style.transform = 'scale(1.1) rotate(90deg)';
});
closeBtn.addEventListener('mouseleave', () => {
closeBtn.style.backgroundColor = 'transparent';
closeBtn.style.color = theme['--panel-text-secondary'];
closeBtn.style.transform = 'scale(1) rotate(0deg)';
});
closeBtn.addEventListener('mousedown', () => {
closeBtn.style.transform = 'scale(0.95) rotate(90deg)';
});
closeBtn.addEventListener('mouseup', () => {
closeBtn.style.transform = 'scale(1.1) rotate(90deg)';
});
// 按钮悬停和点击效果
const buttons = [
{ btn: resetBtn, hoverBg: theme['--panel-hover-bg'], hoverBorder: theme['--panel-primary-color'] },
{ btn: cancelBtn, hoverBg: theme['--panel-hover-bg'], hoverBorder: theme['--panel-primary-color'] },
{ btn: saveBtn, hoverShadow: `0 8px 28px ${theme['--panel-primary-color']}70` }
];
buttons.forEach(({ btn, hoverBg, hoverBorder, hoverShadow }) => {
if (!btn) return;
btn.addEventListener('mouseenter', () => {
btn.style.transform = 'translateY(-2px)';
if (hoverBg) btn.style.backgroundColor = hoverBg;
if (hoverBorder) btn.style.borderColor = hoverBorder;
if (hoverShadow) btn.style.boxShadow = hoverShadow;
});
btn.addEventListener('mouseleave', () => {
btn.style.transform = 'translateY(0)';
if (hoverBg) btn.style.backgroundColor = btn.id === 'settings-save-btn' ? '' : 'transparent';
if (hoverBorder) btn.style.borderColor = theme['--panel-border'];
if (hoverShadow) btn.style.boxShadow = btn.id === 'settings-save-btn' ? `0 6px 20px ${theme['--panel-primary-color']}50` : 'none';
});
btn.addEventListener('mousedown', () => {
btn.style.transform = 'translateY(1px) scale(0.98)';
});
btn.addEventListener('mouseup', () => {
btn.style.transform = 'translateY(-2px)';
});
// 添加点击波纹效果
btn.addEventListener('click', function(e) {
const ripple = document.createElement('span');
const rect = this.getBoundingClientRect();
const size = Math.max(rect.width, rect.height);
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.cssText = `
position: absolute;
width: ${size}px;
height: ${size}px;
left: ${x}px;
top: ${y}px;
background: radial-gradient(circle, ${theme['--panel-primary-color']}40 0%, transparent 70%);
border-radius: 50%;
transform: scale(0);
animation: buttonRipple 0.6s ease-out;
pointer-events: none;
`;
this.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
});
});
// 输入框焦点效果
[searchFormInput, maxSearchesInput, randomAddFactorInput, randomCutFactorInput,
pauseIntervalMinInput, pauseIntervalMaxInput, pauseTimeMinInput, pauseTimeMaxInput,
minDelayInput, maxDelayInput].forEach(input => {
input.addEventListener('focus', () => {
input.style.borderColor = theme['--panel-primary-color'];
input.style.boxShadow = `0 0 0 3px ${theme['--panel-primary-color']}20`;
});
input.addEventListener('blur', () => {
input.style.borderColor = theme['--panel-border'];
input.style.boxShadow = 'none';
});
});
// Radio按钮样式更新
Array.from(panelStateRadios).forEach(radio => {
const label = radio.closest('label');
// 初始化:为已选中的 radio 添加勾选标记
if (radio.checked) {
const isExpanded = radio.value === 'expanded';
label.style.borderColor = theme['--panel-primary-color'];
label.style.background = isExpanded ? `linear-gradient(135deg,${theme['--panel-success-bg']},${theme['--panel-hover-bg']})` : `linear-gradient(135deg,${theme['--panel-info-bg']},${theme['--panel-hover-bg']})`;
const checkmark = document.createElement('div');
checkmark.className = 'checkmark';
checkmark.style.cssText = `position:absolute;top:8px;right:8px;width:20px;height:20px;border-radius:50%;background:${theme['--panel-primary-color']};display:flex;align-items:center;justify-content:center;pointer-events:none;`;
checkmark.innerHTML = '✓';
label.appendChild(checkmark);
}
radio.addEventListener('change', () => {
// 遍历所有radio,更新它们的样式和勾选标记
Array.from(panelStateRadios).forEach(r => {
const lbl = r.closest('label');
if (!lbl) return;
// 先移除旧的勾选标记
const oldCheckmark = lbl.querySelector('.checkmark');
if (oldCheckmark) {
oldCheckmark.remove();
}
if (r.checked) {
// 选中状态:更新样式并添加勾选标记
const isExpanded = r.value === 'expanded';
lbl.style.borderColor = theme['--panel-primary-color'];
lbl.style.background = isExpanded ? `linear-gradient(135deg,${theme['--panel-success-bg']},${theme['--panel-hover-bg']})` : `linear-gradient(135deg,${theme['--panel-info-bg']},${theme['--panel-hover-bg']})`;
// 添加新的勾选标记
const checkmark = document.createElement('div');
checkmark.className = 'checkmark';
checkmark.style.cssText = `position:absolute;top:8px;right:8px;width:20px;height:20px;border-radius:50%;background:${theme['--panel-primary-color']};display:flex;align-items:center;justify-content:center;pointer-events:none;`;
checkmark.innerHTML = '✓';
lbl.appendChild(checkmark);
} else {
// 未选中状态:恢复默认样式
lbl.style.borderColor = theme['--panel-border'];
lbl.style.background = theme['--panel-bg'];
}
});
});
// 添加悬停效果
label.addEventListener('mouseenter', () => {
if (!radio.checked) {
label.style.transform = 'translateY(-2px)';
label.style.boxShadow = `0 4px 12px ${theme['--panel-shadow']}`;
}
});
label.addEventListener('mouseleave', () => {
label.style.transform = 'translateY(0)';
label.style.boxShadow = 'none';
});
});
// Checkbox卡片样式更新
[randomAddCheckbox, randomCutCheckbox, clickSearchResultsCheckbox].forEach(checkbox => {
if (!checkbox) return;
const label = checkbox.closest('label');
checkbox.addEventListener('change', () => {
if (checkbox.checked) {
label.style.borderColor = theme['--panel-primary-color'];
let bgColor = theme['--panel-info-bg'];
if (checkbox.id === 'random-cut-checkbox') {
bgColor = theme['--panel-success-bg'];
} else if (checkbox.id === 'click-search-results-checkbox') {
bgColor = theme['--panel-success-bg'];
}
label.style.background = `linear-gradient(135deg,${bgColor},transparent)`;
// 添加已启用标记
let badge = label.querySelector('.badge');
if (!badge) {
badge = document.createElement('div');
badge.className = 'badge';
badge.style.cssText = `position:absolute;top:10px;right:10px;padding:3px 8px;border-radius:6px;background:${theme['--panel-primary-color']};color:#fff;font-size:10px;font-weight:700;`;
badge.textContent = '已启用';
label.appendChild(badge);
}
} else {
label.style.borderColor = theme['--panel-border'];
label.style.background = theme['--panel-hover-bg'];
// 移除已启用标记
const badge = label.querySelector('.badge');
if (badge) badge.remove();
}
});
// 添加悬停效果
label.addEventListener('mouseenter', () => {
label.style.transform = 'translateY(-2px)';
label.style.boxShadow = `0 4px 16px ${theme['--panel-shadow']}`;
});
label.addEventListener('mouseleave', () => {
label.style.transform = 'translateY(0)';
label.style.boxShadow = 'none';
});
});
// 恢复默认按钮
resetBtn.addEventListener('click', () => {
// 确认恢复默认设置
if (!confirm('⚠️ 确认恢复所有设置为默认值?\n\n此操作将清除您所有的自定义设置,请确保已备份配置。')) {
return;
}
searchFormInput.value = 'QBLH';
// 设置面板状态为展开
Array.from(panelStateRadios).forEach(r => {
r.checked = r.value === 'expanded';
r.dispatchEvent(new Event('change'));
});
maxSearchesInput.value = 20;
randomAddCheckbox.checked = false;
randomAddFactorInput.value = 0.3;
randomCutCheckbox.checked = false;
randomCutFactorInput.value = 0.2;
clickSearchResultsCheckbox.checked = false;
pauseIntervalMinInput.value = 3;
pauseIntervalMaxInput.value = 5;
pauseTimeMinInput.value = 10;
pauseTimeMaxInput.value = 30;
minDelayInput.value = 15;
maxDelayInput.value = 30;
// 触发checkbox样式更新
randomAddCheckbox.dispatchEvent(new Event('change'));
randomCutCheckbox.dispatchEvent(new Event('change'));
clickSearchResultsCheckbox.dispatchEvent(new Event('change'));
});
// 保存按钮事件
// 检测设置是否有变更
const hasChanges = () => {
return searchFormInput.value.trim() !== savedSearchFormParam ||
parseInt(maxSearchesInput.value) !== savedMaxSearches ||
(Array.from(panelStateRadios).find(r => r.checked).value === 'collapsed') !== savedPanelCollapsed ||
randomAddCheckbox.checked !== savedRandomAdd ||
parseFloat(randomAddFactorInput.value) !== savedRandomAddFactor ||
randomCutCheckbox.checked !== savedRandomCut ||
parseFloat(randomCutFactorInput.value) !== savedRandomCutFactor ||
clickSearchResultsCheckbox.checked !== savedClickSearchResults ||
parseInt(pauseIntervalMinInput.value) !== savedPauseIntervalMin ||
parseInt(pauseIntervalMaxInput.value) !== savedPauseIntervalMax ||
parseFloat(pauseTimeMinInput.value) !== savedPauseTimeMin ||
parseFloat(pauseTimeMaxInput.value) !== savedPauseTimeMax ||
parseFloat(minDelayInput.value) !== savedMinDelay ||
parseFloat(maxDelayInput.value) !== savedMaxDelay;
};
saveBtn.addEventListener('click', () => {
const searchFormParam = searchFormInput.value.trim();
const maxSearches = parseInt(maxSearchesInput.value);
const panelDefaultCollapsed = Array.from(panelStateRadios).find(r => r.checked).value === 'collapsed';
const randomAdd = randomAddCheckbox.checked;
const randomAddFactor = parseFloat(randomAddFactorInput.value);
const randomCut = randomCutCheckbox.checked;
const randomCutFactor = parseFloat(randomCutFactorInput.value);
const clickSearchResults = clickSearchResultsCheckbox.checked;
const pauseIntervalMin = parseInt(pauseIntervalMinInput.value);
const pauseIntervalMax = parseInt(pauseIntervalMaxInput.value);
const pauseTimeMin = parseFloat(pauseTimeMinInput.value) * 60 * 1000; // 转换为毫秒
const pauseTimeMax = parseFloat(pauseTimeMaxInput.value) * 60 * 1000; // 转换为毫秒
const minDelay = parseFloat(minDelayInput.value) * 1000; // 转换为毫秒
const maxDelay = parseFloat(maxDelayInput.value) * 1000; // 转换为毫秒
// 验证
if (!searchFormParam) {
alert('❌ 请输入有效的搜索表单参数!');
return;
}
if (maxSearches < 1 || maxSearches > 50) {
alert('❌ 最大搜索次数应在 1-50 之间!');
return;
}
if (randomAddFactor < 0 || randomAddFactor > 1) {
alert('❌ 加词因子应在 0-1 之间!');
return;
}
if (randomCutFactor < 0 || randomCutFactor > 1) {
alert('❌ 截词因子应在 0-1 之间!');
return;
}
if (pauseIntervalMin < 1 || pauseIntervalMax < pauseIntervalMin) {
alert('❌ 暂停间隔设置不合理!');
return;
}
if (pauseTimeMin < 60000 || pauseTimeMax < pauseTimeMin) {
alert('❌ 暂停时间设置不合理!');
return;
}
if (minDelay < 5000 || maxDelay < minDelay) {
alert('❌ 搜索延迟设置不合理!');
return;
}
// 设置变更确认机制
if (!confirm('⚠️ 确认保存设置变更?\n\n保存后页面将自动刷新以应用新配置。')) {
return;
}
// 保存所有配置
GM_setValue('customSearchFormParam', searchFormParam);
GM_setValue('customPanelDefaultCollapsed', panelDefaultCollapsed);
GM_setValue('customMaxSearches', maxSearches);
GM_setValue('customRandomAddSearchWords', randomAdd);
GM_setValue('customRandomAddSearchWordsFactor', randomAddFactor);
GM_setValue('customRandomCutSearchWords', randomCut);
GM_setValue('customRandomCutSearchWordsFactor', randomCutFactor);
GM_setValue('customClickSearchResults', clickSearchResults);
GM_setValue('customPauseIntervalMin', pauseIntervalMin);
GM_setValue('customPauseIntervalMax', pauseIntervalMax);
GM_setValue('customPauseTimeMin', pauseTimeMin);
GM_setValue('customPauseTimeMax', pauseTimeMax);
GM_setValue('customMinDelay', minDelay);
GM_setValue('customMaxDelay', maxDelay);
console.log('💾 保存设置:', {
searchFormParam,
panelDefaultCollapsed,
maxSearches,
randomAdd,
randomAddFactor,
randomCut,
randomCutFactor,
clickSearchResults,
pauseInterval: `${pauseIntervalMin}-${pauseIntervalMax}`,
pauseTime: `${pauseTimeMin/60000}-${pauseTimeMax/60000}分钟`,
delay: `${minDelay/1000}-${maxDelay/1000}秒`
});
// 显示成功提示
alert('✅ 配置已保存!确认后页面将在3秒后刷新以应用新配置...');
// 关闭对话框
closeDialog();
// 延迟刷新页面
setTimeout(() => {
window.location.reload();
}, 3000);
});
// ESC键关闭
const handleEsc = (e) => {
if (e.key === 'Escape') {
closeDialog();
document.removeEventListener('keydown', handleEsc);
}
};
document.addEventListener('keydown', handleEsc);
}
/**
* 更新状态面板
*/
function updateStatusPanel(data = {}) {
if (!state.statusPanel) return;
const taskStatus = getTaskStatus();
const content = document.getElementById('panel-content');
const pageStatus = document.getElementById('page-status');
const countdownElement = document.getElementById('panel-countdown');
const { currentWord = '', pauseTimeLeft = null } = data;
// 更新页面状态指示器
const pageStatusText = document.getElementById('page-status-text');
const taskRunningStatus = document.getElementById('task-running-status');
if (utils.isPageVisible()) {
pageStatus.innerHTML = ' 页面活跃';
pageStatus.style.color = 'var(--panel-success-text,#107c10)';
} else {
pageStatus.innerHTML = ' 后台运行';
pageStatus.style.color = 'var(--panel-text-muted,#666)';
}
// 更新任务执行状态显示
if (taskRunningStatus) {
if (state.isRunning && !pauseTimeLeft && !taskStatus.isCompleted) {
taskRunningStatus.style.display = 'flex';
} else {
taskRunningStatus.style.display = 'none';
}
}
const progress = taskStatus.overallProgress;
// 计算剩余时间(使用精确计时)
const remainingTime = utils.getAccurateRemainingTime();
// 更新收缩状态的倒计时显示
if (countdownElement) {
if (state.isPanelCollapsed) {
// 面板收缩时显示倒计时
if (taskStatus.isCompleted) {
// 任务已完成
countdownElement.textContent = '✅ 已完成';
countdownElement.style.color = 'var(--panel-success-text,#107c10)';
} else if (pauseTimeLeft !== null && pauseTimeLeft > 0) {
// 暂停中 - 显示暂停倒计时
const minutes = Math.floor(pauseTimeLeft / 60);
const seconds = Math.round(pauseTimeLeft % 60);
countdownElement.textContent = `⏸️ ${minutes}:${seconds.toString().padStart(2, '0')}`;
countdownElement.style.color = 'var(--panel-warning-text,#8a6900)';
} else if (remainingTime > 0 && state.isRunning) {
// 执行中 - 显示下次搜索倒计时
countdownElement.textContent = `⏱️ ${remainingTime.toFixed(0)}s`;
countdownElement.style.color = 'var(--panel-info-text,#005a9e)';
} else if (!state.isRunning) {
// 未运行
countdownElement.textContent = '⏹️ 已停止';
countdownElement.style.color = 'var(--panel-text-muted,#999)';
} else {
countdownElement.textContent = '';
}
} else {
// 面板展开时隐藏倒计时
countdownElement.textContent = '';
}
}
content.innerHTML = `
📊 搜索进度
${taskStatus.currentCount}/${taskStatus.maxCount}
${progress > 10 ? '' + progress + '%' : ''}
${taskStatus.isCompleted ? `
✅
今日任务已完成
` : ''}
${pauseTimeLeft !== null ? `
⏸️
暂停中
${Math.floor(pauseTimeLeft/60)}分${Math.round(pauseTimeLeft%60)}秒后继续
` : ''}
${!pauseTimeLeft && currentWord && remainingTime > 0 ? `
🔍 下个搜索词
${remainingTime.toFixed(0)}秒后
${currentWord}
` : ''}
`;
}
/**
* 获取热门搜索词
*/
async function fetchSearchKeywords() {
const cacheKey = `cache_search_words`;
const cached = GM_getValue(cacheKey);
if (cached && Date.now() - cached.time < 3600000) {
return cached.words;
}
// 定义热词API源
const sources = [
{
name: "今日头条热榜",
url: "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc",
parser: data => data.data?.map(item => item.Title?.trim()).filter(Boolean) || []
},
{
name: "微博实时热点",
url: "https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot",
parser: data => {
if (data.data.cards && data.data.cards[0].card_group) {
return data.data.cards[0].card_group
.filter(item => item.desc && !item.desc.match(/[\u4e00-\u9fa5]/) || item.desc.match(/[\u4e00-\u9fa5]/))
.map(item => item.desc)
.filter(Boolean);
}
return [];
}
},
{
name: "百度热搜",
url: "https://top.baidu.com/api/board?tab=realtime",
parser: data => data.data?.cards?.[0]?.content?.map(item => item.word) || []
},
{
name: "腾讯新闻热点",
url: "https://r.inews.qq.com/gw/event/hot_ranking_list?page_size=50",
parser: data => data.idlist?.[0]?.newslist?.map(item => item.title) || []
}
];
const allWords = new Set(); // 使用Set避免重复词
// 并行请求所有API
const promises = sources.map(source =>
new Promise(resolve => {
GM_xmlhttpRequest({
method: "GET",
url: source.url,
timeout: CONFIG.requestTimeout,
onload: res => {
if (res.status === 200) {
try {
const data = utils.safeJsonParse(res.responseText, {});
const words = source.parser(data).filter(word =>
word &&
word.length >= 2 &&
word.length <= 30 &&
!/^[0-9]+$/.test(word) // 过滤纯数字
);
GM_log(`从 ${source.name} 获取到 ${words.length} 个热词`);
resolve(words);
} catch (e) {
GM_log(`解析 ${source.name} 数据失败: ${e.message}`);
resolve([]);
}
} else {
GM_log(`${source.name} 请求失败: HTTP ${res.status}`);
resolve([]);
}
},
onerror: () => {
GM_log(`${source.name} 请求出错`);
resolve([]);
},
ontimeout: () => {
GM_log(`${source.name} 请求超时`);
resolve([]);
}
});
})
);
// 等待所有API请求完成
const results = await Promise.all(promises);
// 合并所有结果并去重
results.forEach(words => {
words.forEach(word => {
// 额外过滤条件
if (word && !allWords.has(word)) {
allWords.add(word);
}
});
});
const allWordsArray = Array.from(allWords);
GM_log(`总共获取到 ${allWordsArray.length} 个不重复的热词`);
// 如果从API获取的词不够,补充本地词库
if (allWordsArray.length < CONFIG.maxSearches) {
const remainingCount = CONFIG.maxSearches - allWordsArray.length;
const localWords = utils.shuffleArray(SEARCH_WORDS);
for (let i = 0; i < remainingCount && i < SEARCH_WORDS.length; i++) {
if (!allWords.has(localWords[i])) {
allWordsArray.push(localWords[i]);
}
}
}
// 随机打乱合并后的词库
const words = utils.shuffleArray(allWordsArray);
// 保存到缓存
GM_setValue(cacheKey, { words, time: Date.now() });
return words;
}
/**
* 获取任务状态
*/
function getTaskStatus() {
const searchCount = GM_getValue('searchCount', 0);
return {
currentCount: searchCount,
maxCount: CONFIG.maxSearches,
isCompleted: searchCount >= CONFIG.maxSearches,
overallProgress: Math.round((searchCount / CONFIG.maxSearches) * 100),
overallProgressRaw: (searchCount / CONFIG.maxSearches) * 100
};
}
/**
* 执行搜索任务
*/
async function executeSearch() {
if (state.isRunning) return;
state.isRunning = true;
createStatusPanel();
const taskStatus = getTaskStatus();
if (taskStatus.isCompleted) {
updateStatusPanel();
GM_notification({ text: "Bing Rewards 任务已完成", title: "任务完成", timeout: 3000 });
state.isRunning = false;
return;
}
// 更新标题
const title = document.querySelector('title');
if (title) title.textContent = `[${taskStatus.currentCount}/${taskStatus.maxCount}] Brian Tool...`;
// 获取搜索词
if (state.searchWords.length === 0) {
try {
state.searchWords = await fetchSearchKeywords();
} catch {
state.searchWords = utils.shuffleArray(SEARCH_WORDS);
}
}
const searchIndex = taskStatus.currentCount % state.searchWords.length;
const searchWord = state.searchWords[searchIndex];
// 对搜索词进行处理
const processedSearchWord = utils.processSearchWord(searchWord);
const delay = utils.getRandomDelay();
// 设置精确倒计时
state.countdownStartTime = Date.now();
state.countdownDuration = delay;
// 更新面板
updateStatusPanel({ currentWord: processedSearchWord });
// 使用精确计时器,不受页面可见性影响
const searchTimer = utils.addTimer(setTimeout(() => {
utils.clearAllTimers();
performSearch(processedSearchWord, taskStatus);
}, delay));
// 添加一个定期更新面板的定时器(每秒更新一次)
const updateTimer = utils.addTimer(setInterval(() => {
updateStatusPanel({ currentWord: processedSearchWord });
}, 1000));
}
/**
* 执行搜索
*/
function performSearch(searchWord, taskStatus) {
const nextCount = taskStatus.currentCount + 1;
const counterKey = 'searchCount';
GM_setValue(counterKey, nextCount);
GM_log(`搜索: ${searchWord} (${nextCount}/${taskStatus.maxCount})`);
// 重置倒计时状态
state.countdownStartTime = 0;
state.countdownDuration = 0;
// 随机暂停间隔检查
// 生成本次搜索周期内的暂停间隔(只在首次搜索时确定,之后保持不变直到完成一次完整搜索)
let currentPauseInterval = GM_getValue('currentPauseInterval', null);
if(currentPauseInterval === null) {
currentPauseInterval = utils.getRandomPauseInterval();
GM_setValue('currentPauseInterval', currentPauseInterval);
}
if (nextCount % currentPauseInterval === 0) {
// 每次暂停时生成新的随机暂停时间
const pauseTime = utils.getRandomPauseTime();
let pauseTimeLeft = pauseTime / 1000;
updateStatusPanel({ pauseTimeLeft });
// 使用精确的暂停计时
const pauseStartTime = Date.now();
const pauseTimer = utils.addTimer(setInterval(() => {
const elapsed = Date.now() - pauseStartTime;
pauseTimeLeft = Math.max(0, (pauseTime - elapsed) / 1000);
updateStatusPanel({ pauseTimeLeft });
if (pauseTimeLeft <= 0) {
utils.clearAllTimers();
// 完成暂停后,重新生成下一个暂停间隔
const newPauseInterval = utils.getRandomPauseInterval();
GM_setValue('currentPauseInterval', newPauseInterval);
window.location.href = buildSearchUrl(searchWord);
}
}, 1000));
} else {
window.location.href = buildSearchUrl(searchWord);
}
}
/**
* 页面加载完成后执行随机滚动,模拟真实用户行为
* 随机滚动多次,方向(上滑/下滑)和次数都是随机的
*/
function randomScrollAfterPageLoad() {
// 等待页面内容完全加载
setTimeout(() => {
const scrollHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
const viewportHeight = window.innerHeight;
const maxScroll = scrollHeight - viewportHeight;
// 如果页面可以滚动
if (maxScroll > 0) {
// 随机生成滚动次数(2-5次)
const scrollCount = Math.floor(Math.random() * 4) + 2;
GM_log(`开始随机滚动,总次数: ${scrollCount}次`);
// 执行多次随机滚动
let currentScroll = window.scrollY;
for (let i = 0; i < scrollCount; i++) {
setTimeout(() => {
// 随机决定滚动方向:true=下滑,false=上滑
const scrollDown = Math.random() > 0.2;
// 随机生成滚动距离(100-800px)
const scrollDistance = Math.floor(Math.random() * 700) + 100;
// 计算新的滚动位置
let newScrollPosition;
if (scrollDown) {
// 下滑:当前位置 + 随机距离,不超过最大滚动位置
newScrollPosition = Math.min(currentScroll + scrollDistance, maxScroll);
} else {
// 上滑:当前位置 - 随机距离,不小于0
newScrollPosition = Math.max(currentScroll - scrollDistance, 0);
}
// 执行滚动
window.scrollTo({
top: newScrollPosition,
behavior: 'smooth'
});
// 更新当前位置
currentScroll = newScrollPosition;
const direction = scrollDown ? '下滑' : '上滑';
GM_log(`第${i + 1}次滚动: ${direction} ${scrollDistance}px,目标位置: ${newScrollPosition}px`);
// 如果是最后一次滚动,滚动结束后检查并点击链接
if (i === scrollCount - 1) {
setTimeout(() => {
checkAndClickSearchResult();
}, 1500); // 等待滚动动画完成
}
}, i * 1000); // 每次滚动间隔1秒,模拟真实用户操作
}
} else {
// 页面无法滚动,直接检查并点击链接
checkAndClickSearchResult();
}
}, 2500); // 等待2.5秒让页面内容加载完成
}
/**
* 检查当前页面是否为搜索结果页且包含启动参数,如果是则点击搜索结果链接
*/
function checkAndClickSearchResult() {
try {
// 检查是否启用了点击搜索结果功能
if (!CONFIG.clickSearchResults) {
GM_log('未启用点击搜索结果功能,跳过点击');
return;
}
// 检查是否在搜索结果页面(使用正则表达式提高性能)
const isSearchPage = /\/search/.test(window.location.pathname) && /[?&]q=/.test(window.location.search);
if (!isSearchPage) {
GM_log('当前不是搜索结果页面,跳过点击');
return;
}
// 获取当天的启动参数并检查URL
const startParam = utils.getRandomStartParam();
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has(startParam)) {
GM_log(`URL中未包含启动参数标记: ${startParam},跳过点击`);
return;
}
GM_log(`检测到搜索结果页且包含启动参数: ${startParam},准备点击搜索结果`);
// 获取搜索结果(缓存查询结果)
const searchResults = document.querySelectorAll('li.b_algo');
// 筛选出可见的搜索结果
const visibleResults = Array.from(searchResults).filter(result => isElementVisible(result));
const visibleResultsCount = visibleResults.length;
if (visibleResultsCount === 0) {
GM_log('未找到可见的搜索结果项');
return;
}
GM_log(`找到 ${visibleResultsCount} 个可见的搜索结果`);
// 尝试多次选择搜索结果
const maxAttempts = 3;
let attempt = 0;
let targetLink = null;
while (attempt < maxAttempts && !targetLink) {
// 随机选择一个可见的搜索结果
const randomIndex = Math.floor(Math.random() * visibleResultsCount);
const selectedResult = visibleResults[randomIndex];
GM_log(`从 ${visibleResultsCount} 个可见结果中随机选择第 ${randomIndex + 1} 个结果(尝试 ${attempt + 1}/${maxAttempts})`);
// 查找可点击的链接
const link = findClickableLink(selectedResult);
// 验证链接有效性和可见性
if (link && validateLink(link) && isElementVisible(link)) {
targetLink = link;
} else {
GM_log('链接无效或不可见,尝试选择其他结果');
attempt++;
}
}
if (!targetLink) {
GM_log('未找到有效的可点击链接');
return;
}
GM_log(`找到目标链接: ${targetLink.href}`);
// 尝试打开链接
simulateHumanClick(targetLink);
} catch (error) {
GM_log(`检查并点击搜索结果时出错: ${error.message}`);
console.error('checkAndClickSearchResult error:', error);
}
}
/**
* 验证链接是否有效
* @param {HTMLAnchorElement} link - 要验证的链接元素
* @returns {boolean} - 链接是否有效
*/
function validateLink(link) {
try {
if (!link || !link.href) {
return false;
}
const url = new URL(link.href);
// 排除内部链接和无效链接
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
return false;
}
// 排除bing自身的链接(可选)
if (url.hostname.includes('bing.com')) {
return false;
}
return true;
} catch (error) {
GM_log(`验证链接时出错: ${error.message}`);
return false;
}
}
/**
* 检查元素是否在当前窗口可见
* @param {Element} element - 要检查的元素
* @returns {boolean} - 元素是否在当前窗口可见
*/
function isElementVisible(element) {
try {
if (!element) {
return false;
}
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
// 检查元素是否在视口内
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= windowHeight &&
rect.right <= windowWidth
);
} catch (error) {
GM_log(`检查元素可见性时出错: ${error.message}`);
return false;
}
}
/**
* 从搜索结果中查找可点击的链接
* @param {Element} result - 搜索结果元素
* @returns {HTMLAnchorElement|null} - 找到的链接元素或null
*/
function findClickableLink(result) {
try {
if (!result) {
GM_log('查找链接失败:搜索结果元素为null');
return null;
}
// 优先查找 h2 中的链接(主标题链接)
const h2Link = result.querySelector('h2 a');
if (h2Link && h2Link.href) {
return h2Link;
}
// 备选:查找 .tilk 类的链接
const tilkLink = result.querySelector('a.tilk');
if (tilkLink && tilkLink.href) {
return tilkLink;
}
// 备选:查找其他可能的链接
const otherLink = result.querySelector('a[href]');
if (otherLink && otherLink.href) {
return otherLink;
}
return null;
} catch (error) {
GM_log(`查找链接时出错: ${error.message}`);
return null;
}
}
/**
* 模拟人工操作点击链接
* @param {HTMLAnchorElement} link - 要点击的链接元素
*/
function simulateHumanClick(link) {
try {
// 策略1: 优先使用 ctrlKey + click 事件(最佳的后台打开方式,不会切换焦点)
GM_log('尝试使用 Ctrl+Click 在新标签页打开链接(保持原页面焦点)');
// 保存原始属性
const originalTarget = link.target;
const originalRel = link.rel;
// 设置链接属性以确保在新标签页打开且安全
link.target = '_blank';
link.rel = 'noopener noreferrer';
// 模拟 Ctrl+Click(后台打开新标签页,不会切换焦点)
// 不包含 view 参数以避免沙盒环境中的类型转换错误
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
ctrlKey: true, // 关键:模拟按住Ctrl键,实现后台打开
button: 0 // 左键
});
const eventResult = link.dispatchEvent(clickEvent);
// 恢复原始属性
link.target = originalTarget;
link.rel = originalRel;
if (eventResult) {
GM_log('已使用 Ctrl+Click 成功在后台打开链接,焦点保持在原页面');
return;
}
// 策略2: Ctrl+Click 失败,尝试 window.open 并立即恢复焦点
GM_log('Ctrl+Click 未生效,尝试 window.open 并恢复焦点');
const newWindow = window.open(link.href, '_blank', 'noopener,noreferrer');
if (newWindow) {
GM_log('已使用 window.open 打开链接,尝试保持原页面焦点');
// 立即尝试将焦点恢复到原页面
// 使用 setTimeout 确保在新窗口打开后执行
setTimeout(() => {
try {
window.focus();
// 额外的保障:如果当前页面是活动的,重新激活它
if (document.hasFocus()) {
window.blur();
window.focus();
}
} catch (e) {
// 忽略焦点设置错误,这是正常的浏览器安全限制
GM_log('无法强制设置窗口焦点,受浏览器安全策略限制');
}
}, 100);
return;
}
// 策略3: 使用临时链接作为最终兜底(这种方式通常会切换焦点)
GM_log('window.open 被阻止,使用临时链接兜底');
const tempLink = document.createElement('a');
tempLink.href = link.href;
tempLink.target = '_blank';
tempLink.rel = 'noopener noreferrer';
tempLink.style.display = 'none';
document.body.appendChild(tempLink);
tempLink.click();
document.body.removeChild(tempLink);
GM_log('已通过临时链接在新标签页打开(可能切换焦点)');
// 尝试恢复焦点(尽管可能无效)
setTimeout(() => {
try {
window.focus();
} catch (e) {
// 忽略错误
}
}, 100);
} catch (error) {
GM_log(`模拟点击时出错: ${error.message}`);
// 最终兜底:直接使用 window.open
try {
const newWindow = window.open(link.href, '_blank', 'noopener,noreferrer');
if (newWindow) {
GM_log('兜底方案:已成功在新标签页打开链接');
setTimeout(() => {
try {
window.focus();
} catch (e) {
// 忽略焦点设置错误
}
}, 100);
}
} catch (openError) {
GM_log(`所有方法均失败: ${openError.message}`);
}
}
}
/**
* 检查并启动任务
*/
function checkAndStartTask() {
const startParam = utils.getRandomStartParam();
const urlParams = new URLSearchParams(window.location.search);
// 检查是否有当天的启动参数标记
const hasStartParam = urlParams.has(startParam);
console.log(`检查并启动任务: ${startParam}`);
if (hasStartParam) {
// 有启动参数,准备执行搜索任务
setTimeout(executeSearch, 2000);
randomScrollAfterPageLoad();
console.log(`启动任务: ${startParam}`);
} else {
// createStatusPanel();
}
}
// 注册菜单命令
GM_registerMenuCommand('🚀 开始任务', () => {
GM_setValue('searchCount', 0);
// 重置当前暂停间隔值以开始新的搜索周期
GM_setValue('currentPauseInterval', utils.getRandomPauseInterval());
// 清除热词缓存,确保开始新任务时获取新的热词
GM_setValue('cache_search_words', undefined);
// 获取当天的启动参数
const startParam = utils.getRandomStartParam();
window.location.href = 'https://www.bing.com/?' + startParam + '=1';
});
GM_registerMenuCommand('⏹️ 终止任务', () => {
const taskStatus = getTaskStatus();
const counterKey = 'searchCount';
GM_setValue(counterKey, taskStatus.maxCount);
// 同时清除当前暂停间隔值
GM_setValue('currentPauseInterval', null);
utils.clearAllTimers();
state.isRunning = false;
state.countdownStartTime = 0;
state.countdownDuration = 0;
updateStatusPanel();
});
GM_registerMenuCommand('📊 查看/隐藏面板', () => {
if (!state.statusPanel) {
createStatusPanel();
} else {
const panel = state.statusPanel;
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
}
});
GM_registerMenuCommand('⚙️ 配置脚本参数', () => {
alert('请配置以下参数:\n\n1. searchFormParam: 登录Bing后手动搜索几次,从地址栏获取实际的form参数值\n2. maxSearches: 设置每日最大搜索次数\n3. 其他高级参数可根据需要调整\n\n配置完成后刷新页面开始使用。');
window.open('https://gitee.com/idbb98/microsoft-bing-rewards-daily-task-script#-%e5%ae%89%e8%a3%85%e4%b8%8e%e4%bd%bf%e7%94%a8', '_blank');
});
GM_registerMenuCommand('👨💻 关于作者', () => {
alert('作者:Brian\n版本:' + GM_info.script.version + '\n\n这是一个自动化完成微软必应每日搜索任务的脚本,帮助您轻松积累奖励积分。\n\n如果您觉得这个脚本有用,欢迎给作者点个Star!');
window.open('https://gitee.com/idbb98/microsoft-bing-rewards-daily-task-script', '_blank');
});
// 启动脚本
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkAndStartTask);
} else {
checkAndStartTask();
}