// ==UserScript==
// @name Bing Rewards Lite (Win & Mac)
// @version 1.3
// @description 自动搜索热榜词刷积分(桌面 40 / 手机 30);兼容新版 Bing / Copilot Search
// ✨ 多源热词:vvhan 热搜聚合 + 微博实时 + 百度热榜(自动容错)
// @match https://*.bing.com/*
// @match https://*.bing.cn/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_setTimeout
// @grant GM_clearTimeout
// @grant GM_setInterval
// @grant GM_clearInterval
// @connect api.vvhan.com
// @connect api-hot.imsyy.top
// @license MIT
// @namespace https://greasyfork.org/users/737649
// ==/UserScript==
(function () {
/* ===== 基本配置 ===== */
const ID = 'bru_panel';
const VER = '1.3';
const MAX = { pc: 40, ph: 30 };
const TYPE = { min: 35, max: 80 };
const SCROLL_TIMES = 4;
const FALL = ['今日新闻', '天气预报', '电影票房', '体育比分', '股票行情'];
/* ===== 热榜接口(按优先级) ===== */
const HOT_APIS = [
'https://api.vvhan.com/api/hotlist/all', // 聚合
'https://api.vvhan.com/api/hotlist/wbHot', // 微博
'https://api-hot.imsyy.top/baidu?num=50' // 百度
];
/* ===== 兼容新版 Bing 选择器 ===== */
const SELECTORS = ['#sb_form_q','.b_searchbox','input[name="q"]','#searchboxinput','textarea[name="q"]'];
/* ===== 通用工具 ===== */
const tSet =(typeof GM_setTimeout ==='function'?GM_setTimeout :setTimeout);
const tClr =(typeof GM_clearTimeout==='function'?GM_clearTimeout:clearTimeout);
const iSet =(typeof GM_setInterval ==='function'?GM_setInterval :setInterval);
const iClr =(typeof GM_clearInterval==='function'?GM_clearInterval:clearInterval);
const z2=n=> (n<10?'0':'')+n;
/* ===== 状态记录 ===== */
const today = new Date().toISOString().slice(0,10);
const def = {date:today,pc:0,ph:0,running:true};
let rec = GM_getValue('bru_lite',def);
if(rec.date!==today){rec={...def};GM_setValue('bru_lite',rec);}
const mobile=/mobile|android|iphone|ipad|touch/i.test(navigator.userAgent);
const key = mobile?'ph':'pc';
const limit = mobile?MAX.ph:MAX.pc;
/* ===== 热榜词 ===== */
let HOT = [];
async function fetchHot(){
for(const url of HOT_APIS){
try{
const json=await new Promise((ok,err)=>{
GM_xmlhttpRequest({method:'GET',url,onload:({responseText})=>{try{ok(JSON.parse(responseText));}catch(e){err(e);}},onerror:err});
});
HOT=parseHot(json);
if(HOT.length) return;
}catch{/* 继续下一个源 */}
}
HOT=FALL;
}
function parseHot(obj){
const words=[];
const seen=new Set();
const isWord=s=> typeof s==='string' && s.trim().length>0 && s.length<=40;
const skipRe=/^(微博|知乎|百度|抖音|36氪|哔哩哔哩|IT资讯|虎嗅网|豆瓣|人人都是产品经理|热搜|热榜|API)/;
function add(s){s=s.trim();if(isWord(s)&&!skipRe.test(s)&&!seen.has(s)){seen.add(s);words.push(s);}}
function walk(v){
if(Array.isArray(v)){
v.forEach(walk);
}else if(v&&typeof v==='object'){
if(v.title) add(v.title);
if(v.keyword)add(v.keyword);
if(v.name) add(v.name);
if(v.word) add(v.word);
// 深层字段
for(const k of ['list','hotList','data','hot','children','items']){
if(v[k]) walk(v[k]);
}
}
}
const root=obj?.data??obj;
walk(root);
return words;
}
/* ===== 样式 ===== */
GM_addStyle(`#${ID}{position:fixed;top:10px;right:10px;z-index:99999;width:190px;padding:10px 10px 8px 10px;font:11px/1.45 system-ui,-apple-system,BlinkMacSystemFont,sans-serif;color:#222;background:rgba(255,255,255,.55);backdrop-filter:blur(12px)saturate(1.4);border-radius:10px;box-shadow:0 4px 12px rgba(0,0,0,.18);overflow:visible;}#${ID} button{all:unset;font-size:10px;background:#3b82f6;color:#fff;border-radius:5px;padding:3px 8px;margin-left:4px;cursor:pointer}.bar{height:4px;background:#d0d0d0;border-radius:3px;overflow:hidden;margin-top:3px}.fill{height:100%;width:100%;background:linear-gradient(90deg,#00b7ff,#00ffb7,#d4ff00,#ff6600,#ff00aa,#00b7ff);background-size:400% 100%;transform-origin:left;transition:transform .6s ease;animation:flow 5s linear infinite}@keyframes flow{0%{background-position:0 0}100%{background-position:100% 0}}.err{display:none;color:#e63946;font-weight:600;margin-top:2px;font-size:10px}.decor{position:absolute;right:4px;top:46px;width:32px;height:32px;border-radius:50%;background:conic-gradient(#00b7ff,#00ffb7,#d4ff00,#ff6600,#ff00aa,#00b7ff);animation:spin 7s linear infinite,pulse 3.5s ease-in-out infinite alternate;pointer-events:none;opacity:.75;filter:blur(1px) drop-shadow(0 0 4px rgba(0,0,0,.12));}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse{from{transform:scale(.9)}to{transform:scale(1.05)}}`);
/* ===== 面板 ===== */
function buildPanel(){if(document.getElementById(ID))return;const box=document.createElement('div');box.id=ID;box.innerHTML=`<div id="drag" style="display:flex;justify-content:space-between;user-select:none;cursor:move;margin-bottom:2px"><b>BR Lite</b><div><button id="run">${rec.running?'暂停':'启动'}</button><button id="clr">清零</button></div></div><div><div>模式:<b>${mobile?'手机':'桌面'}</b></div><div>状态:<b id="sta">${rec.running?'运行中':'已暂停'}</b></div><div>下次:<b id="ctd">--</b>s</div><div>计数:<span id="num">${rec[key]}</span> / ${limit}</div><div class="bar"><div class="fill" id="fill"></div></div><div style="font-size:9px;margin-top:1px">运行:<span id="time">00:00:00</span></div><div class="err" id="err">热词获取失败!</div></div><div style="font-size:9px;text-align:right;margin-top:2px">v${VER}</div><div class="decor"></div>`;document.body.append(box);const drag=document.getElementById('drag');let d=false,sx=0,sy=0,lx=0,ly=0;drag.onmousedown=e=>{if(e.button!==0||e.target.tagName==='BUTTON')return;d=true;sx=e.clientX;sy=e.clientY;lx=box.offsetLeft;ly=box.offsetTop;document.onmousemove=ev=>{if(!d)return;box.style.left=lx+ev.clientX-sx+'px';box.style.top=ly+ev.clientY-sy+'px'};document.onmouseup=()=>{d=false;document.onmousemove=document.onmouseup=null}};document.getElementById('run').onclick=toggle;document.getElementById('clr').onclick=()=>{rec.pc=rec.ph=0;GM_setValue('bru_lite',rec);draw()}};
/* ===== UI 渲染 ===== */
const start=Date.now();
function draw(t='--'){document.getElementById('sta').textContent=rec.running?'运行中':'已暂停';document.getElementById('ctd').textContent=rec.running?t:'--';document.getElementById('num').textContent=rec[key];document.getElementById('fill').style.transform=`scaleX(${Math.min(rec[key]/limit,1)})`;const d=Date.now()-start;document.getElementById('time').textContent=`${z2(d/3600000|0)}:${z2(d%3600000/60000|0)}:${z2(d%60000/1000|0)}`};
/* ===== 小工具 ===== */
function waitMs(){const r=Math.random();if(r<.25)return(10+Math.random()*10)*1000;if(r<.75)return(20+Math.random()*20)*1000;return(40+Math.random()*40)*1000}
async function typeHuman(inp,str){inp.focus();inp.value='';for(const c of str){inp.value+=c;inp.dispatchEvent(new Event('input',{bubbles:true}));await new Promise(r=>tSet(r,TYPE.min+Math.random()*(TYPE.max-TYPE.min)))}}
function softScroll(){const h=document.body.scrollHeight-innerHeight;if(h<=0)return;for(let i=0;i<SCROLL_TIMES;i++)tSet(()=>scrollTo({top:Math.random()*h,behavior:'smooth'}),i*120)}
/* ===== 主循环 & 搜索动作 ===== */
let loopTimer=0,ctdTimer=0;
async function doSearch(){try{const kw=HOT[Math.random()*HOT.length|0];if(!kw)return;let inp=null;for(const sel of SELECTORS){inp=document.querySelector(sel);if(inp)break}
if(inp){await typeHuman(inp,kw);['keydown','keypress','keyup'].forEach(evt=>inp.dispatchEvent(new KeyboardEvent(evt,{key:'Enter',keyCode:13,which:13,bubbles:true,cancelable:true})));const form=inp.closest('form');if(form)form.submit();tSet(()=>{if(location.pathname==='/'||location.pathname==='')location.href=`https://www.bing.com/search?q=${encodeURIComponent(kw)}`;},800);rec[key]++;GM_setValue('bru_lite',rec);softScroll();}else{rec[key]++;GM_setValue('bru_lite',rec);location.href=`https://www.bing.com/search?q=${encodeURIComponent(kw)}`;}}
catch(e){console.error('[BR]',e)}}
function loop(){if(!rec.running)return;if(rec[key]>=limit){stop();return}const w=waitMs();let c=w/1000|0;draw(c);ctdTimer=iSet(()=>{if(!rec.running){iClr(ctdTimer);return}draw(--c)},1000);loopTimer=tSet(async()=>{iClr(ctdTimer);await doSearch();loop()},w)}
function startLoop(){if(rec[key]>=limit)return;rec.running=true;GM_setValue('bru_lite',rec);loop()}function stop(){rec.running=false;GM_setValue('bru_lite',rec);tClr(loopTimer);iClr(ctdTimer);draw()}function toggle(){rec.running?stop():startLoop();document.getElementById('run').textContent=rec.running?'暂停':'启动'}
/* ===== 页面加载 ===== */
(async()=>{buildPanel();await fetchHot();if(HOT===FALL)document.getElementById('err').style.display='block';draw();if(rec.running)startLoop()})();
})();