]*>\s*\/\s*([\d,]+)\s*<\/span>/i)
|| searchRowMatch[1].match(/([\d,]+)\s*\/\s*([\d,]+)/))
: null;
if (searchHtmlMatch) {
pcCur = toNum(searchHtmlMatch[1]);
pcMax = toNum(searchHtmlMatch[2]);
searchQuotaFound = pcMax > 0;
todayDetails.push({
title: '必应搜索',
points: pcCur,
max: pcMax
});
Utils.log("🔍", `页面表格配额: PC ${pcCur}/${pcMax}`);
}
// 方法3: 从RSC数据中解析搜索进度
const searchRscMatch = clean.match(/"combinedSearch":\{[^}]*"progress":(\d+)[^}]*"max":(\d+)/);
if (searchRscMatch && !todayDetails.some(d => d.title === '必应搜索')) {
pcCur = toNum(searchRscMatch[1]);
pcMax = toNum(searchRscMatch[2]);
searchQuotaFound = pcMax > 0;
todayDetails.push({
title: '必应搜索',
points: pcCur,
max: pcMax
});
}
// 添加dailyOffer到今日明细
if (dailyOffer > 0) {
todayDetails.push({ title: '优惠', points: dailyOffer });
}
// 匹配其他活动(如"优惠")
const otherActivityRegex = /([^<]+)<\/p><\/div>
]*>(\d+)<\/div>/g;
let otherMatch;
while ((otherMatch = otherActivityRegex.exec(clean)) !== null) {
const title = otherMatch[1];
const points = parseInt(otherMatch[2]);
if (points > 0 && !todayDetails.some(d => d.title === title)) {
todayDetails.push({ title, points });
}
}
// 解析历史积分
const history = {
month: 0,
year: 0,
lifetime: 0
};
// 方法1: 从RSC数据中解析历史积分
const historyRscMatch = clean.match(/"pointsHistory":\{[^}]*"thisMonth":\{"earn":(\d+)[^}]*"thisYear":\{"earn":(\d+)[^}]*"lifetime":\{"earn":(\d+)/);
if (historyRscMatch) {
history.month = parseInt(historyRscMatch[1]);
history.year = parseInt(historyRscMatch[2]);
history.lifetime = parseInt(historyRscMatch[3]);
} else {
// 方法2: 从HTML中解析历史积分
const monthHtmlMatch = clean.match(/本月.*?(\d[\d,]*)<\/div>/);
const yearHtmlMatch = clean.match(/今年.*?(\d[\d,]*)<\/div>/);
const lifetimeHtmlMatch = clean.match(/生存期.*?(\d[\d,]*)<\/div>/);
// JSON格式
const monthJsonMatch = clean.match(/"monthlyPoints":(\d+)/);
const yearJsonMatch = clean.match(/"yearlyPoints":(\d+)/);
const lifetimeJsonMatch = clean.match(/"lifetimePoints":(\d+)/);
if (monthHtmlMatch) {
history.month = parseInt(monthHtmlMatch[1].replace(/,/g, ''));
} else if (monthJsonMatch) {
history.month = parseInt(monthJsonMatch[1]);
}
if (yearHtmlMatch) {
history.year = parseInt(yearHtmlMatch[1].replace(/,/g, ''));
} else if (yearJsonMatch) {
history.year = parseInt(yearJsonMatch[1]);
}
if (lifetimeHtmlMatch) {
history.lifetime = parseInt(lifetimeHtmlMatch[1].replace(/,/g, ''));
} else if (lifetimeJsonMatch) {
history.lifetime = parseInt(lifetimeJsonMatch[1]);
}
}
if (!searchQuotaFound) {
const userInfoResult = await this.getSearchQuotaFromUserInfo();
if (userInfoResult) {
Utils.log("🟢", "页面未命中搜索配额,使用 getuserinfo 兜底");
return userInfoResult;
}
}
if (!searchQuotaFound && RewardsAuto.state.token) {
const apiResult = await this.getSearchQuotaFromAPI();
if (apiResult) {
Utils.log("🟢", "页面未命中搜索配额,使用 DAPI 兜底");
return apiResult;
}
}
return {
balance,
pc: { progress: pcCur, max: pcMax },
mobile: { progress: mobCur, max: mobMax },
dailyOffer,
todayDetails,
history
};
} catch (e) {
if (attempt < maxRetries) {
await Utils.delay(3210);
continue;
}
Utils.log("🔴", `仪表盘获取失败: ${e.message}`);
return false;
}
}
return false;
},
async signApp() {
const region = GM_getValue("Config.lock", true) ? "cn" : RewardsAuto.state.region.toLowerCase();
try {
const res = await this.withTokenRetry(token => Utils.xhr({
method: "POST",
url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities",
headers: {
"content-type": "application/json; charset=UTF-8",
"user-agent": RewardsAuto.ua.app,
"authorization": `Bearer ${token}`,
"x-rewards-appid": RewardsAuto.appConfig.rewardsAppId,
"x-rewards-ismobile": "true",
"x-rewards-country": region,
"x-rewards-partnerid": "startapp",
"x-rewards-flights": "rwgobig"
},
data: JSON.stringify({
amount: 1, id: Utils.getRandomUUID(),
type: 103,
country: region,
channel: RewardsAuto.appConfig.channel
})
}));
if (Utils.isJSON(res)) {
const data = JSON.parse(res);
const response = data.response || {};
if (response.activity) return Number(response.activity.p || response.activity.points || 0);
if (response.isDuplicate || response.activity === null) return 0;
Utils.log("🟡", `App签入响应未确认: ${String(res).slice(0, 120)}`);
}
} catch (e) {
Utils.log("🔴", `App签入失败: ${e.message}`);
}
return -1;
},
async getRequestVerificationToken(pageUrl = "https://rewards.bing.com/") {
try {
const html = await Utils.xhr({
url: pageUrl,
headers: {
"user-agent": RewardsAuto.ua.pc,
"referer": "https://rewards.bing.com/"
},
anonymous: false
});
const tokenMatch = html.match(/name=["']__RequestVerificationToken["'][^>]*value=["']([^"']+)["']/i)
|| html.match(/RequestVerificationToken.*?value=["']([^"']+)["']/i)
|| html.match(/"verificationToken"\s*:\s*"([^"]+)"/i)
|| html.match(/"__RequestVerificationToken"\s*:\s*"([^"]+)"/i);
return tokenMatch ? tokenMatch[1].replace(/&/g, "&") : "";
} catch (e) {
Utils.log("🟡", `活动Token获取失败: ${e.message}`);
return "";
}
},
async reportActivity(offerId, hash, referer = "https://rewards.bing.com/") {
const token = await this.getRequestVerificationToken(referer);
const params = new URLSearchParams({
id: offerId,
hash: hash || "1",
activityAmount: "1"
});
if (token) params.set("__RequestVerificationToken", token);
const headers = {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"user-agent": RewardsAuto.ua.pc,
"referer": referer,
"origin": "https://rewards.bing.com",
"x-requested-with": "XMLHttpRequest"
};
if (token) headers["RequestVerificationToken"] = token;
return await Utils.xhr({
method: "POST",
url: "https://rewards.bing.com/api/reportactivity?X-Requested-With=XMLHttpRequest",
headers,
data: params.toString(),
anonymous: false
});
},
async signPC() {
try {
const res = await this.reportActivity("Gamification_DailyCheckIn", "1", "https://rewards.bing.com/");
if (Utils.isJSON(res)) {
const data = JSON.parse(res);
return Number(data.points || data.response?.activity?.p || 0);
}
} catch (e) {
Utils.log("🟡", `PC签入失败: ${e.message}`);
}
return -1;
},
async appActivity(type, offerid) {
const region = GM_getValue("Config.lock", true) ? "cn" : RewardsAuto.state.region.toLowerCase();
const body = {
amount: 1,
country: region,
id: Utils.getRandomUUID(),
type: type,
channel: RewardsAuto.appConfig.channel
};
if (offerid) {
body.attributes = { offerid: offerid };
}
try {
const res = await this.withTokenRetry(token => Utils.xhr({
method: "POST",
url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities",
headers: {
"content-type": "application/json; charset=UTF-8",
"user-agent": RewardsAuto.ua.app,
"authorization": `Bearer ${token}`,
"x-rewards-appid": RewardsAuto.appConfig.rewardsAppId,
"x-rewards-ismobile": "true",
"x-rewards-country": region
},
data: JSON.stringify(body)
}));
if (Utils.isJSON(res)) {
const data = JSON.parse(res);
const points = data.response?.activity?.p || 0;
const isDuplicate = data.response?.isDuplicate || false;
const balance = data.response?.balance || 0;
return { points, isDuplicate, balance };
}
} catch (e) {
Utils.log("🔴", `App活动失败(${offerid}): ${e.message}`);
}
return null;
},
async getReadProgress() {
try {
const res = await this.withTokenRetry(token => Utils.xhr({
url: "https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613",
headers: {
"content-type": "application/json; charset=UTF-8",
"user-agent": RewardsAuto.ua.app,
"authorization": `Bearer ${token}`,
"x-rewards-appid": RewardsAuto.appConfig.rewardsAppId,
"x-rewards-ismobile": "true"
}
}));
if (Utils.isJSON(res)) {
const promos = JSON.parse(res).response?.promotions || [];
const task = promos.find(x => x.attributes?.offerid === RewardsAuto.appConfig.offerIds.readArticle);
if (task && task.attributes) return { progress: parseInt(task.attributes.progress) || 0, max: parseInt(task.attributes.max) || 30 };
}
} catch (e) {
Utils.log("🔴", `阅读进度获取失败: ${e.message}`);
}
return false;
},
async getSearchQuotaFromUserInfo() {
try {
const res = await Utils.xhr({
url: `https://rewards.bing.com/api/getuserinfo?type=1&X-Requested-With=XMLHttpRequest&_=${Date.now()}`,
headers: {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"user-agent": RewardsAuto.ua.pc,
"referer": "https://rewards.bing.com/",
"x-requested-with": "XMLHttpRequest"
},
anonymous: false
});
if (!Utils.isJSON(res)) return false;
const data = JSON.parse(res);
const dashboard = data.dashboard || data;
const userStatus = dashboard.userStatus || {};
const counters = userStatus.counters || {};
const sumCounter = items => {
if (!Array.isArray(items)) return { progress: 0, max: 0 };
return items.reduce((acc, item) => {
acc.progress += Number(item.pointProgress || 0);
acc.max += Number(item.pointProgressMax || item.pointMax || 0);
return acc;
}, { progress: 0, max: 0 });
};
const pc = sumCounter(counters.pcSearch);
const mobile = sumCounter(counters.mobileSearch);
if (pc.max === 0 && mobile.max === 0) return false;
const balance = Number(userStatus.availablePoints || dashboard.availablePoints || 0);
Utils.log("📊", `getuserinfo查询: PC ${pc.progress}/${pc.max}, Mobile ${mobile.progress}/${mobile.max}, 积分 ${balance}`);
return {
balance,
pc,
mobile,
dailyOffer: 0,
todayDetails: pc.max > 0 ? [{ title: "必应搜索", points: pc.progress, max: pc.max }] : [],
history: null
};
} catch (e) {
if (GM_getValue("Config.debugDAPI", false)) {
Utils.log("🟡", `getuserinfo 查询失败: ${e.message}`);
}
return false;
}
},
// 查不到 counters 或配额 0/0 时返回 false,回退到 HTML 解析
async getSearchQuotaFromAPI() {
try {
const res = await this.withTokenRetry(token => Utils.xhr({
url: "https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613",
headers: {
"content-type": "application/json; charset=UTF-8",
"user-agent": RewardsAuto.ua.app,
"authorization": `Bearer ${token}`,
"x-rewards-appid": RewardsAuto.appConfig.rewardsAppId,
"x-rewards-ismobile": "true"
}
}));
if (Utils.isJSON(res)) {
const data = JSON.parse(res);
const response = data.response || {};
const promos = response.promotions || [];
if (GM_getValue("Config.debugDAPI", false)) {
const promoNames = promos.map(p => p.name || p.attributes?.offerid || "?").join(", ");
Utils.log("🔵", `DAPI promotions(${promos.length}): ${promoNames}`);
for (let i = 0; i < Math.min(promos.length, 5); i++) {
const p = promos[i];
const attrs = p.attributes || {};
const attrStr = Object.entries(attrs).map(([k, v]) => `${k}=${v}`).join(", ").slice(0, 300);
Utils.log("🔵", `DAPI promo[${i}] ${p.name}: ${attrStr}`);
}
}
const counters = response.counters || response.userStatus?.counters;
if (!counters) {
if (GM_getValue("Config.debugDAPI", false)) {
Utils.log("🟡", "DAPI 未返回 counters,回退到页面解析");
}
return false;
}
let pcCur = 0, pcMax = 0;
if (counters?.pcSearch && counters.pcSearch.length > 0) {
pcCur = counters.pcSearch[0].pointProgress || 0;
pcMax = counters.pcSearch[0].pointProgressMax || 0;
}
let mobCur = 0, mobMax = 0;
if (counters?.mobileSearch && counters.mobileSearch.length > 0) {
mobCur = counters.mobileSearch[0].pointProgress || 0;
mobMax = counters.mobileSearch[0].pointProgressMax || 0;
}
if (pcMax === 0 && mobMax === 0) {
if (GM_getValue("Config.debugDAPI", false)) {
Utils.log("🟡", "DAPI 返回配额 0/0,回退到页面解析");
}
return false;
}
const balance = response.balance || response.userStatus?.availablePoints || 0;
Utils.log("📊", `DAPI查询: PC ${pcCur}/${pcMax}, Mobile ${mobCur}/${mobMax}, 积分 ${balance}`);
return {
balance,
pc: { progress: pcCur, max: pcMax },
mobile: { progress: mobCur, max: mobMax },
dailyOffer: 0,
todayDetails: [],
history: null
};
}
} catch (e) {
Utils.log("🟡", `DAPI查询失败: ${e.message}`);
}
return false;
},
// 执行阅读
async doRead() {
const region = GM_getValue("Config.lock", true) ? "cn" : RewardsAuto.state.region.toLowerCase();
try {
await this.withTokenRetry(token => Utils.xhr({
method: "POST",
url: "https://prod.rewardsplatform.microsoft.com/dapi/me/activities",
headers: {
"content-type": "application/json; charset=UTF-8",
"user-agent": RewardsAuto.ua.app,
"authorization": `Bearer ${token}`,
"x-rewards-appid": RewardsAuto.appConfig.rewardsAppId,
"x-rewards-ismobile": "true",
"x-rewards-country": region
},
data: JSON.stringify({
amount: 1, country: region, id: Utils.getRandomUUID(),
type: 101, attributes: { offerid: RewardsAuto.appConfig.offerIds.readArticle }
})
}));
return true;
} catch (e) {
Utils.log("🔴", `阅读请求失败: ${e.message}`);
return false;
}
},
// 多层级解析:activityCards → promotionCards → 全局扫描 → HTML data 属性
async discoverCards() {
const cards = [];
const seenCardKeys = new Set();
try {
const html = await Utils.xhr({ url: "https://rewards.bing.com/earn" });
if (!html) { Utils.log("🔴", "earn 页面返回空"); return cards; }
// unescape 版本供 RSC 解析使用
const clean = html.replace(/\\"/g, '"');
// ---------- 动态提取 next-action(供 claimCard 使用) ----------
const naMatch = html.match(/name":"next-action"[^}]*"value":"([a-f0-9]{40,})"/)
|| html.match(/next-action["']\s*:\s*["']([a-f0-9]{40,})["']/)
|| clean.match(/"next-action"[^"]*"([a-f0-9]{40,})"/);
if (naMatch) {
RewardsAuto._nextAction = naMatch[1];
Utils.log("🟢", `动态 next-action: ${naMatch[1].slice(0, 12)}…`);
}
const pushCard = (card) => {
if (!card || !card.offerId || !card.hash || card.points <= 0) return;
const key = `${card.offerId}:${card.hash}`;
if (seenCardKeys.has(key)) return;
seenCardKeys.add(key);
cards.push(card);
};
const inferKind = (offerId, title = "") => {
const text = `${offerId} ${title}`;
if (/quiz|trivia/i.test(text)) return "quiz";
if (/puzzle/i.test(text)) return "puzzle";
if (/image/i.test(text)) return "image_creator";
if (/explore|search/i.test(text)) return "explore_search";
if (/dailyset|daily/i.test(text)) return "daily";
if (/streak/i.test(text)) return "streak";
return "open_only";
};
// ---------- 辅助:从字符串提取单张卡片对象 ----------
const parseCard = (obj) => {
const offerIdMatch = obj.match(/"offerId":"([^"]+)"/i)
|| obj.match(/"offerid":"([^"]+)"/i)
|| obj.match(/"offer_id":"([^"]+)"/i);
const hashMatch = obj.match(/"hash":"([^"]+)"/)
|| obj.match(/"activityId":"([^"]+)"/)
|| obj.match(/"id":"([^"]+)"/);
if (!offerIdMatch || !hashMatch) return null;
const offerId = offerIdMatch[1];
const hash = hashMatch[1];
const pointsMatch = obj.match(/"points":(\d+)/);
const isCompletedMatch = obj.match(/"isCompleted":(true|false)/i)
|| obj.match(/"completed":(true|false)/i)
|| obj.match(/"state":"(completed|CLAIMED)"/i);
const titleMatch = obj.match(/"title":"([^"]+)"/)
|| obj.match(/"name":"([^"]+)"/)
|| obj.match(/"displayName":"([^"]+)"/);
const points = pointsMatch ? parseInt(pointsMatch[1]) : 0;
const isCompleted = isCompletedMatch
? (isCompletedMatch[1] === "true" || isCompletedMatch[1] === "completed" || isCompletedMatch[1] === "CLAIMED")
: false;
if (isCompleted) return null;
const title = titleMatch ? titleMatch[1] : "";
const skip = RewardsAuto.skipPatterns.some(p =>
title.toLowerCase().includes(p.toLowerCase()) ||
offerId.toLowerCase().includes(p.toLowerCase())
);
if (skip) return null;
return { title, points, offerId, hash, kind: inferKind(offerId, title) };
};
// ---------- 方法0: getuserinfo 结构化数据 ----------
try {
const userInfo = await Utils.xhr({
url: `https://rewards.bing.com/api/getuserinfo?type=1&X-Requested-With=XMLHttpRequest&_=${Date.now()}`,
headers: {
"user-agent": RewardsAuto.ua.pc,
"referer": "https://rewards.bing.com/",
"x-requested-with": "XMLHttpRequest"
},
anonymous: false
});
if (Utils.isJSON(userInfo)) {
const data = JSON.parse(userInfo);
const dashboard = data.dashboard || data;
const now = new Date();
const todayKeys = new Set([
`${String(now.getMonth() + 1).padStart(2, "0")}/${String(now.getDate()).padStart(2, "0")}/${now.getFullYear()}`,
`${now.getMonth() + 1}/${now.getDate()}/${now.getFullYear()}`
]);
const normalizeDashboardCard = (item, kind) => {
if (!item) return null;
const offerId = item.offerId || item.offerid || item.id || item.name;
const hash = item.hash || item.activityId;
const title = item.title || item.name || item.description || "";
const points = Number(item.points ?? item.pointProgressMax ?? item.max ?? 0);
const doneMax = Number(item.pointProgressMax || 0);
const doneCur = Number(item.pointProgress || 0);
const isCompleted = item.isCompleted || item.complete || item.completed || (doneMax > 0 && doneCur >= doneMax);
if (!offerId || !hash || points <= 0 || isCompleted) return null;
const skip = RewardsAuto.skipPatterns.some(p =>
title.toLowerCase().includes(p.toLowerCase()) ||
offerId.toLowerCase().includes(p.toLowerCase())
);
if (skip) return null;
return {
title,
points,
offerId,
hash,
kind: kind || inferKind(offerId, title),
source: "getuserinfo",
url: "https://rewards.bing.com/"
};
};
const dailySetPromotions = dashboard.dailySetPromotions || {};
for (const dateKey of todayKeys) {
const dailyItems = dailySetPromotions[dateKey];
if (Array.isArray(dailyItems)) {
for (const item of dailyItems) pushCard(normalizeDashboardCard(item, "daily"));
}
}
const morePromotions = dashboard.morePromotions || dashboard.promotions || [];
if (Array.isArray(morePromotions)) {
for (const item of morePromotions) pushCard(normalizeDashboardCard(item));
}
if (cards.length > 0) {
Utils.log("🧩", `getuserinfo 命中 ${cards.length} 个活动卡片`);
}
}
} catch (e) {
Utils.log("🟡", `getuserinfo 活动解析跳过: ${e.message}`);
}
// ---------- 方法1: activityCards 数组 ----------
const m1 = clean.match(/"activityCards":\[([\s\S]*?)\](?=,"|,"[a-z]|}$)/i);
if (m1) {
Utils.log("🧩", "命中 activityCards 数组");
const cardObjRegex = /\{[^{}]*\}/g;
let m;
while ((m = cardObjRegex.exec(m1[1])) !== null) {
const card = parseCard(m[0]);
pushCard(card);
}
}
// ---------- 方法2: promotionCards / promotions 数组 ----------
if (cards.length === 0) {
const m2 = clean.match(/"(?:promotionCards|promotions|dailySet|cards)":\[([\s\S]*?)\](?=,"|,"[a-z]|}$)/i);
if (m2) {
Utils.log("🧩", "命中 promotionCards/promotions 数组");
const cardObjRegex = /\{[^{}]*\}/g;
let m;
while ((m = cardObjRegex.exec(m2[1])) !== null) {
const card = parseCard(m[0]);
pushCard(card);
}
}
}
// ---------- 方法3: 全局扫描含 offerId+hash 的对象(平衡括号匹配) ----------
if (cards.length === 0) {
Utils.log("🧩", "全局扫描 RSC payload 中的卡片对象");
const seen = new Set();
const offerIdRe = /"offerId"|"offerid"|"offer_id"/gi;
let m;
while ((m = offerIdRe.exec(clean)) !== null) {
let start = m.index;
while (start > 0 && clean[start] !== '{') start--;
if (clean[start] !== '{') continue;
let depth = 0, end = start;
for (let i = start; i < clean.length; i++) {
if (clean[i] === '{') depth++;
if (clean[i] === '}') { depth--; if (depth === 0) { end = i + 1; break; } }
}
if (depth !== 0) continue;
const obj = clean.slice(start, end);
if (!/"(?:hash|activityId|id)"\s*:/.test(obj)) continue;
const key = obj.slice(0, 80);
if (seen.has(key)) continue;
seen.add(key);
const card = parseCard(obj);
pushCard(card);
}
}
// ---------- 方法4: 从 Next.js RSC flight payload 中提取 ----------
if (cards.length === 0) {
Utils.log("🧩", "尝试解析 RSC flight payload");
// RSC 格式: 数字:{JSON}\n
const flightRegex = /\d+:(\{[\s\S]*?"(?:offerId|offerid)"[\s\S]*?\})\n/g;
let m;
const seen = new Set();
while ((m = flightRegex.exec(clean)) !== null) {
const key = m[1].slice(0, 80);
if (seen.has(key)) continue;
seen.add(key);
const card = parseCard(m[1]);
pushCard(card);
}
}
// ---------- 方法5: 从 HTML data-* 属性中提取 ----------
if (cards.length === 0) {
Utils.log("🧩", "尝试从 HTML data 属性中提取卡片");
const dataRegex = /data-offer-id="([^"]+)"[^>]*data-hash="([^"]+)"/gi;
let m;
while ((m = dataRegex.exec(html)) !== null) {
const offerId = m[1];
const hash = m[2];
const skip = RewardsAuto.skipPatterns.some(p => offerId.toLowerCase().includes(p.toLowerCase()));
if (skip) continue;
pushCard({ title: "", points: 1, offerId, hash, kind: "open_only" });
}
}
if (cards.length === 0) {
// 输出前 500 字符供调试
const snippet = clean.slice(0, 500).replace(/[\r\n]+/g, ' ');
Utils.log("🟡", `所有方法均未命中,页面前500字符: ${snippet}`);
}
} catch (e) {
Utils.log("🔴", `卡片解析失败: ${e.message}`);
}
return cards;
},
// 领取卡片奖励
async claimCard(card) {
if (card.source === "getuserinfo") {
try {
await this.reportActivity(card.offerId, card.hash, card.url || "https://rewards.bing.com/");
return true;
} catch (e) {
Utils.log("🟡", `卡片上报失败(${card.offerId}): ${e.message}`);
return false;
}
}
// 动态获取 next-action:优先使用 discoverCards 阶段提取的值,否则回退到默认
const nextAction = RewardsAuto._nextAction || "70babbc81d2724f60d29a95c03b3d739cba77cea92";
try {
await Utils.xhr({
method: "POST",
url: card.url || "https://rewards.bing.com/earn",
headers: {
"content-type": "text/plain;charset=UTF-8",
"next-action": nextAction,
"referer": card.url || "https://rewards.bing.com/earn"
},
data: JSON.stringify([card.hash, 11, { offerid: card.offerId, isPromotional: "$undefined", timezoneOffset: "-480" }])
});
return true;
} catch (e) {
Utils.log("🟡", `卡片领取失败(next-action:${nextAction.slice(0,8)}…): ${e.message}`);
return false;
}
},
async getSearchPage(query, isMobile = false) {
const mkt = GM_getValue("Config.lock", true) ? "&mkt=zh-CN" : "";
const deviceType = isMobile ? "m" : "d";
return Utils.xhr({
url: `https://${RewardsAuto.state.host}/search?q=${encodeURIComponent(query)}&form=QBLH${mkt}`,
headers: {
"user-agent": isMobile ? RewardsAuto.ua.mobile : RewardsAuto.ua.pc,
"cookie": `_Rwho=u=${deviceType}&ts=${RewardsAuto.state.dateNowStr}`,
"referer": `https://${RewardsAuto.state.host}/?form=QBLH`
}
});
},
async reportSearch(html, query, isMobile = false) {
try {
const ig = Utils.getRandomUUID();
const mkt = GM_getValue("Config.lock", true) ? "&mkt=zh-CN" : "";
const params = `q=${encodeURIComponent(query)}&form=QBLH${mkt}`;
const deviceType = isMobile ? "m" : "d";
const headers = {
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"user-agent": isMobile ? RewardsAuto.ua.mobile : RewardsAuto.ua.pc,
"referer": `https://${RewardsAuto.state.host}/?form=QBLH`,
"cookie": `_Rwho=u=${deviceType}&ts=${RewardsAuto.state.dateNowStr}`
};
await Utils.xhr({
method: "POST",
url: `https://${RewardsAuto.state.host}/rewardsapp/ncheader?ver=88888888&IID=SERP.5047&IG=${ig}&ajaxreq=1`,
headers,
data: "wb=1%3bi%3d1%3bv%3d1"
});
await Utils.xhr({
method: "POST",
url: `https://${RewardsAuto.state.host}/rewardsapp/reportActivity?IG=${ig}&IID=SERP.5047&${params}&ajaxreq=1`,
headers,
data: `url=${encodeURIComponent(`https://${RewardsAuto.state.host}/search?${params}`)}&V=web`
});
return true;
} catch (e) {
Utils.log("🟡", `搜索上报失败: ${e.message}`);
return false;
}
},
async checkRegion(retryCount = 0) {
if (!GM_getValue("Config.lock", true)) return true;
try {
const html = await Utils.xhr({ url: `https://${RewardsAuto.state.host}/` });
if (!html) {
if (retryCount < 2) {
Utils.log("🟡", `地区检测返回空,第${retryCount + 1}次重试...`);
await Utils.randomDelay(3000, 8000);
return await this.checkRegion(retryCount + 1);
}
Utils.log("🔴", "地区检测失败(无响应)");
return false;
}
const match = html.replace(/\s/g, "").match(/Region:"(.*?)"(.*?)RevIpCC:"(.*?)"/);
if (match) {
RewardsAuto.state.region = match[3].toUpperCase();
if (RewardsAuto.state.region !== "CN") {
// 获取IP详细信息(来自比尔脚本)
await this.getIPInfo();
Utils.log("🔴", `IP非大陆(${RewardsAuto.state.region}),已停止\n${RewardsAuto.state.ipInfo}`, true);
return false;
}
Utils.log("🟢", `地区检测通过: ${RewardsAuto.state.region}`);
return true;
}
// 正则未匹配到,可能是页面结构变化
if (retryCount < 2) {
Utils.log("🟡", `地区检测格式异常,第${retryCount + 1}次重试...`);
await Utils.randomDelay(3000, 8000);
return await this.checkRegion(retryCount + 1);
}
Utils.log("🔴", "地区检测失败(格式不匹配)");
return false;
} catch (e) {
if (retryCount < 2) {
Utils.log("🟡", `地区检测异常: ${e.message},第${retryCount + 1}次重试...`);
await Utils.randomDelay(3000, 8000);
return await this.checkRegion(retryCount + 1);
}
Utils.log("🔴", `地区检测失败: ${e.message}`);
return false;
}
},
async getIPInfo() {
try {
const qryResult = await Utils.xhr({
url: "https://disp-qryapi.3g.qq.com/v1/dispatch",
headers: { "referer": "https://3g.qq.com/" }
});
if (qryResult && Utils.isJSON(qryResult)) {
const resJSON = JSON.parse(qryResult);
RewardsAuto.state.ip = (resJSON.code == 0 && resJSON.extra && resJSON.extra.ip) ? resJSON.extra.ip : "";
let rawInfo = (resJSON.code == 0 && resJSON.ipInfo) ? String(resJSON.ipInfo) : "";
rawInfo = rawInfo.replace(/[#*]+/g, " ").trim();
RewardsAuto.state.ipInfo = rawInfo ? `🌏所在地区:${rawInfo}` : "";
}
} catch {
console.debug("获取附加 IP 信息失败");
}
},
async getHotSearchWord() {
const keywords = ["天气预报", "今日新闻", "体育赛事", "股票行情", "电影推荐", "科技资讯", "美食食谱", "旅游攻略", "历史上的今天", "健康常识"];
const baseWord = keywords[Utils.randomRange(0, keywords.length - 1)];
const randomSuffix = Math.random().toString(36).slice(2, 6);
let sentence = `${baseWord} ${randomSuffix}`;
if (RewardsAuto.apiConfig.mode !== "offline") {
if (RewardsAuto.apiConfig.wordIndex < 1 || RewardsAuto.apiConfig.wordList.length < 1) {
// 获取随机API配置
const apiArr = RewardsAuto.apiConfig.arr;
const lastApiIndex = parseInt(GM_getValue("Config.apiIndex", -1));
const filteredArr = apiArr.filter((_, index) => index !== lastApiIndex);
const randomIndex = Utils.randomRange(0, filteredArr.length - 1);
GM_setValue("Config.apiIndex", randomIndex);
const [apiName, apiConfig] = filteredArr[randomIndex];
RewardsAuto.apiConfig.url = apiConfig.url;
RewardsAuto.apiConfig.hot = apiConfig.hot;
try {
const hotSource = RewardsAuto.apiConfig.hot[Utils.randomRange(0, RewardsAuto.apiConfig.hot.length - 1)];
const result = await Utils.xhr({ url: RewardsAuto.apiConfig.url + hotSource });
if (result && Utils.isJSON(result)) {
const res = JSON.parse(result);
if (res.code == 200) {
RewardsAuto.apiConfig.wordIndex = 1;
RewardsAuto.apiConfig.wordList = [];
for (let i = 0; i < res.data.length; i++) {
RewardsAuto.apiConfig.wordList.push(res.data[i].title);
}
// 随机打乱数组
RewardsAuto.apiConfig.wordList.sort(() => Math.random() - 0.5);
sentence = RewardsAuto.apiConfig.wordList[RewardsAuto.apiConfig.wordIndex];
// 截断到20-32字符
sentence = sentence.substring(0, Utils.randomRange(20, 32));
return sentence;
}
}
} catch (e) {
Utils.log("🟡", `热搜词获取失败: ${e.message}`);
}
} else {
RewardsAuto.apiConfig.wordIndex++;
if (RewardsAuto.apiConfig.wordIndex > RewardsAuto.apiConfig.wordList.length - 1) {
RewardsAuto.apiConfig.wordIndex = 0;
}
sentence = RewardsAuto.apiConfig.wordList[RewardsAuto.apiConfig.wordIndex];
sentence = sentence.substring(0, Utils.randomRange(20, 32));
return sentence;
}
Utils.log("🟡", "热搜词接口异常,已使用随机搜索词");
}
return sentence;
},
async checkSearchRestricted() {
// 用服务器实际进度判断,避免本地虚增导致误判
const info = await this.getRewardsInfo();
const currentTotal = info
? info.pc.progress + info.mobile.progress
: RewardsAuto.state.pcProgress + RewardsAuto.state.mobileProgress;
const lastTotal = RewardsAuto.state.lastSearchProgress;
if (lastTotal !== -1) {
if (currentTotal === lastTotal &&
currentTotal < (RewardsAuto.state.pcMax + RewardsAuto.state.mobileMax)) {
RewardsAuto.state.restrictedTimes++;
} else {
RewardsAuto.state.restrictedTimes = 0;
}
}
RewardsAuto.state.lastSearchProgress = currentTotal;
GM_setValue("Config.lastSearchProgress", currentTotal);
GM_setValue("Config.restrictedTimes", RewardsAuto.state.restrictedTimes);
if (RewardsAuto.state.restrictedTimes >= 3) {
Utils.log("🔴", "搜索受限或账号异常,已中断今日搜索!", true);
return true;
}
return false;
}
};
const TaskManager = {
// 任务日期状态
signDate: 0, readDate: 0, promosDate: 0, searchDate: 0,
signPoint: -1, signTimes: 0, readTimes: 0, promosTimes: 0,
// 初始化任务状态
init() {
RewardsAuto.state.dateNowNum = Utils.getTodayNum();
RewardsAuto.state.dateNowStr = Utils.getTodayStr();
const tasks = GM_getValue("Config.tasks", {});
this.signDate = tasks.sign || 0;
this.readDate = tasks.read || 0;
this.promosDate = tasks.promos || 0;
this.searchDate = tasks.search || 0;
this.signPoint = GM_getValue("Config.signPoint", -1);
},
// 保存任务状态
save() {
GM_setValue("Config.tasks", {
sign: this.signDate, read: this.readDate,
promos: this.promosDate, search: this.searchDate
});
},
async doSign() {
if (!GM_getValue("Tasks.sign", true) || this.signTimes > 2) return;
if (this.signPoint >= 0 && this.signDate === RewardsAuto.state.dateNowNum) {
Utils.log("✅", `签入已完成(${this.signPoint}积分)`);
return;
}
await Utils.randomDelay();
let totalPoint = 0;
let signOk = false;
// App 端签到(静默执行,不写入通知)
const appPoint = await API.signApp();
if (appPoint >= 0) {
signOk = true;
if (appPoint > 0) {
GM_log(`📱 App签入静默成功 +${appPoint}积分`);
totalPoint += appPoint;
} else {
GM_log("📱 App签入已确认,无新增积分");
}
}
// PC 端签到
await Utils.randomDelay(3000, 8000);
const pcPoint = await API.signPC();
if (pcPoint >= 0) {
signOk = true;
Utils.log("💻", `PC签入成功!+${pcPoint}积分`);
totalPoint += pcPoint;
}
if (signOk) {
this.signPoint = totalPoint;
this.signDate = RewardsAuto.state.dateNowNum;
GM_setValue("Config.signPoint", totalPoint);
this.save();
Utils.log("🔵", `签入任务完成!总积分 +${totalPoint}`, true);
} else {
this.signTimes++;
Utils.log("🟡", `签入失败,稍后重试`);
}
},
async doRead() {
if (!GM_getValue("Tasks.read", true) || this.readTimes > 2) return;
if (this.readDate === RewardsAuto.state.dateNowNum) {
Utils.log("✅", "阅读任务已完成");
return;
}
const progress = await API.getReadProgress();
if (!progress) { this.readTimes++; return; }
const { progress: cur, max } = progress;
Utils.log("📖", `阅读进度: ${cur}/${max}`);
if (cur >= max) {
this.readDate = RewardsAuto.state.dateNowNum;
this.save();
Utils.log("✅", "阅读任务已完成");
return;
}
let successCount = 0;
for (let i = cur; i < max; i++) {
const ok = await API.doRead();
if (!ok) { Utils.log("🟡", `阅读第 ${i + 1} 篇失败,中止`); break; }
successCount++;
Utils.log("📖", `阅读文章 ${i + 1}/${max}`);
await Utils.randomDelay(3000, 8000);
}
if (successCount === 0) {
this.readTimes++;
Utils.log("🟡", "阅读全部失败,稍后重试");
return;
}
// 二次验证:确认服务器端进度真的满了
const verify = await API.getReadProgress();
if (verify && verify.progress >= verify.max) {
this.readDate = RewardsAuto.state.dateNowNum;
this.save();
Utils.log("🔵", "阅读任务完成!", true);
} else {
this.readTimes++;
Utils.log("🟡", `阅读已执行但未完成,下次继续`);
}
},
async doPromos() {
if (!GM_getValue("Tasks.promos", true) || this.promosTimes > 2) return;
if (this.promosDate === RewardsAuto.state.dateNowNum) {
Utils.log("✅", "活动卡片已完成");
return;
}
Utils.log("🧩", "扫描活动卡片...");
const cards = await API.discoverCards();
if (cards.length === 0) {
this.promosDate = RewardsAuto.state.dateNowNum;
this.save();
Utils.log("✅", "无新活动卡片");
return;
}
Utils.log("🧩", `发现 ${cards.length} 个卡片`);
let ok = 0, fail = 0;
for (const card of cards) {
Utils.log(" ", `[${card.kind}] ${card.title} +${card.points}p`);
// Quiz 任务需要单独处理(可选开启)
if (card.kind === "quiz" && !GM_getValue("Tasks.quiz", true)) continue;
// 【防封号】领取卡片前随机延迟
await Utils.randomDelay(3000, 8000);
const result = await API.claimCard(card);
result ? ok++ : fail++;
}
this.promosDate = RewardsAuto.state.dateNowNum;
this.save();
Utils.log("🔵", `活动完成: ${ok}成功/${fail}失败`, true);
},
async doSearch() {
if (!GM_getValue("Tasks.search", true)) return;
const info = await API.getRewardsInfo();
if (!info) { Utils.log("🔴", "无法获取积分信息"); return; }
RewardsAuto.state.pcProgress = info.pc.progress;
RewardsAuto.state.pcMax = info.pc.max;
RewardsAuto.state.mobileProgress = info.mobile.progress;
RewardsAuto.state.mobileMax = info.mobile.max;
Utils.log("🔍", `配额: PC ${info.pc.progress}/${info.pc.max}, Mobile ${info.mobile.progress}/${info.mobile.max}`);
if (info.mobile.max === 0) {
Utils.log("ℹ️", "你的账号没有移动端搜索配额,将只执行 PC 端搜索");
}
const hasMobile = info.mobile.max > 0;
const pcDone = info.pc.progress >= info.pc.max;
const mobileDone = !hasMobile || info.mobile.progress >= info.mobile.max;
if (pcDone && mobileDone) {
this.searchDate = RewardsAuto.state.dateNowNum;
this.save();
const mobileReport = hasMobile ? `\n📱 移动端: ${info.mobile.progress}/${info.mobile.max}` : "";
Utils.log("✅", `搜索配额已满\n💻 PC端: ${info.pc.progress}/${info.pc.max}${mobileReport}`);
return;
}
if (this.searchDate === RewardsAuto.state.dateNowNum) {
Utils.log("🟡", "搜索配额未满,继续执行搜索任务");
this.searchDate = 0;
}
const isRestricted = await API.checkSearchRestricted();
if (isRestricted) {
this.searchDate = RewardsAuto.state.dateNowNum;
this.save();
return;
}
const limit = Utils.randomRange(4, 7);
const canSearchMobile = RewardsAuto.state.mobileMax > 0;
for (let i = 0; i < limit; i++) {
if (RewardsAuto.state.pcProgress >= RewardsAuto.state.pcMax &&
(!canSearchMobile || RewardsAuto.state.mobileProgress >= RewardsAuto.state.mobileMax)) break;
let isMobile = false;
if (canSearchMobile && RewardsAuto.state.mobileProgress < RewardsAuto.state.mobileMax) {
isMobile = Math.random() > 0.6;
}
let query;
if (RewardsAuto.apiConfig.mode !== "offline") {
query = await API.getHotSearchWord();
} else {
query = RewardsAuto.searchPool[Utils.randomRange(0, RewardsAuto.searchPool.length - 1)];
}
Utils.log("🔍", `[${isMobile?"Mobile":"PC"}] 搜索 ${i+1}/${limit}: ${query}`);
try {
const html = await API.getSearchPage(query, isMobile);
if (html) {
await API.reportSearch(html, query, isMobile);
if (isMobile) RewardsAuto.state.mobileProgress += 3;
else RewardsAuto.state.pcProgress += 3;
}
} catch (e) {
Utils.log("🟡", `搜索失败: ${e.message}`);
}
const span = Number(GM_getValue("Config.span", 30));
const wait = Utils.randomRange((span-15)*1000, (span+15)*1000);
Utils.log("⏳", `等待 ${wait/1000}秒`);
await Utils.delay(wait);
}
const finalInfo = await API.getRewardsInfo();
if (finalInfo) {
const hasMobile = finalInfo.mobile.max > 0;
const pcDone = finalInfo.pc.progress >= finalInfo.pc.max;
const mobileDone = !hasMobile || finalInfo.mobile.progress >= finalInfo.mobile.max;
if (pcDone && mobileDone) {
this.searchDate = RewardsAuto.state.dateNowNum;
this.save();
RewardsAuto.state.restrictedTimes = 0;
GM_setValue("Config.restrictedTimes", 0);
GM_setValue("Config.lastSearchProgress", -1);
const mobileReport = hasMobile ? `\n📱 移动端: ${finalInfo.mobile.progress}/${finalInfo.mobile.max}` : "";
Utils.log("🔵", `🔍 搜索任务完成!\n💻 PC端: ${finalInfo.pc.progress}/${finalInfo.pc.max}${mobileReport}`, true);
} else {
const mobileReport = hasMobile ? `\n📱 移动端: ${finalInfo.mobile.progress}/${finalInfo.mobile.max}` : "";
Utils.log("🟡", `搜索已执行,配额未满\n💻 PC端: ${finalInfo.pc.progress}/${finalInfo.pc.max}${mobileReport}`);
// 不设置searchDate,下次运行继续搜索
}
} else {
Utils.log("🟡", "搜索已执行,但无法获取最终配额状态");
}
},
async doDailySet() {
const today = Utils.getTodayNum();
const processedKey = "Config.dailySetProcessed";
let processed = GM_getValue(processedKey, []);
if (processed.length > 0 && processed[0]?.date !== today) processed = [];
const processedIds = new Set(processed.map(p => p.offerId));
Utils.log("📅", `开始执行每日活动(已处理 ${processedIds.size} 个)...`);
await Utils.randomDelay(3000, 8000);
try {
const clickedCount = await this.clickDailySetLinks(processedIds);
if (clickedCount > 0) {
GM_setValue(processedKey, processed);
Utils.log("🔵", `每日活动完成,点击了 ${clickedCount} 个活动`, true);
} else {
Utils.log("🟡", "未找到可点击的每日活动链接");
}
} catch (e) {
Utils.log("🔴", `每日活动执行异常: ${e.message}`);
}
},
async clickDailySetLinks(processedIds) {
let clickCount = 0;
// 多种选择器匹配活动链接(适配不同页面版本)
const selectors = [
'a[href*="rnoreward=1"][target="_blank"]', // 旧版
'a[href*="/earn/"][target="_blank"]', // 新版通用
'a[data-bi-id][href*="earn"]', // 带数据属性
'a[data-rac][href*="earn"]', // RAC 属性
'a[href*="promotional"]', // 活动链接
'.daily-set a[href]', // 每日活动容器
'.promo-card a[href]', // 活动卡片
'a[href*="offerid"]', // 带 offerid 的链接
];
let allLinks = [];
for (const selector of selectors) {
try {
const links = document.querySelectorAll(selector);
if (links.length > 0) {
allLinks = [...links];
Utils.log("📅", `使用选择器 "${selector}" 找到 ${links.length} 个活动链接`);
break;
}
} catch {}
}
// 如果上述选择器都没找到,尝试更宽泛的匹配
if (allLinks.length === 0) {
const allAnchors = document.querySelectorAll('a[href]');
for (const anchor of allAnchors) {
const href = anchor.href || "";
const text = anchor.textContent || "";
// 匹配包含 earn、promotional 或 rnoreward 的链接,且不是已完成的
if ((href.includes("/earn/") || href.includes("promotional") || href.includes("offerid") || href.includes("rnoreward")) &&
!text.includes("已完成") && !text.includes("Completed")) {
allLinks.push(anchor);
}
}
Utils.log("📅", `宽泛匹配找到 ${allLinks.length} 个活动链接`);
}
Utils.log("📅", `总共找到 ${allLinks.length} 个每日活动链接`);
for (const link of allLinks) {
// 提取 offerId 用于去重
const href = link.href || "";
const offerIdMatch = href.match(/[?&]offerid=([^&]+)/i) || href.match(/\/earn\/([^/?#]+)/);
const offerId = offerIdMatch ? decodeURIComponent(offerIdMatch[1]) : href.slice(0, 120);
if (processedIds.has(offerId)) continue;
// 检查是否已完成(多种检测方式)
const isCompleted = link.querySelector('[class*="Success"]') ||
link.querySelector('[class*="complete"]') ||
link.querySelector('[class*="Complete"]') ||
link.querySelector('[aria-label*="已完成"]') ||
link.querySelector('[aria-label*="Completed"]') ||
link.textContent.includes("已完成") ||
link.textContent.includes("Completed") ||
link.classList.contains("completed");
if (isCompleted) continue;
// 获取活动标题(多种方式)
const titleEl = link.querySelector('[class*="Body2Strong"]') ||
link.querySelector('[class*="title"]') ||
link.querySelector('[class*="Title"]') ||
link.querySelector('span') ||
link.querySelector('div');
const title = titleEl ? titleEl.textContent.trim() : "未知活动";
Utils.log("📅", `点击活动: ${title}`);
// 【防封号】点击前随机延迟 3-8 秒
await Utils.randomDelay(3000, 8000);
try {
link.click();
clickCount++;
processedIds.add(offerId);
processed.push({ date: Utils.getTodayNum(), offerId });
await Utils.randomDelay(3000, 8000);
} catch (e) {
Utils.log("🟡", `点击活动失败: ${e.message}`);
}
}
return clickCount;
},
async runAll() {
RewardsAuto.state.startTime = Utils.getTimestamp();
Utils.log("🚀", "启动全能自动化任务...");
this.init();
const regionOK = await API.checkRegion();
// Token 续期
let isTokenOK = false;
if (regionOK) {
isTokenOK = await API.renewToken();
if (!isTokenOK) {
Utils.log("🟡", "Token失败,跳过签入/阅读", true);
}
} else {
Utils.log("🟡", "地区检测失败,尝试执行搜索任务...");
}
if (regionOK && isTokenOK) {
await this.doSign();
await Utils.randomDelay();
await this.doRead();
await Utils.randomDelay();
} else if (regionOK) {
// 地区OK但token失败,尝试签入(可能失败)
await this.doSign();
await Utils.randomDelay();
}
await this.doPromos();
await Utils.randomDelay();
await this.doSearch();
Utils.log("📅", "开始执行每日活动任务...");
await Utils.randomDelay();
try {
await this.doDailySet();
} catch (e) {
Utils.log("🟡", `每日活动执行异常: ${e.message}`);
}
// 二次扫描机制(来自Python版):完成一轮任务后再次扫描新解锁的卡片
Utils.log("🔄", "二次扫描:检查是否有新解锁的卡片...");
await Utils.randomDelay(3000, 8000);
const newCards = await API.discoverCards();
if (newCards.length > 0) {
Utils.log("🧩", `二次扫描发现 ${newCards.length} 个新卡片`);
let ok = 0, fail = 0;
for (const card of newCards) {
Utils.log(" ", `[${card.kind}] ${card.title} +${card.points}p`);
if (card.kind === "quiz" && !GM_getValue("Tasks.quiz", true)) continue;
await Utils.randomDelay(3000, 8000);
const result = await API.claimCard(card);
result ? ok++ : fail++;
}
Utils.log("🔵", `二次扫描完成: ${ok}成功/${fail}失败`);
} else {
Utils.log("✅", "二次扫描:无新卡片");
}
// 任务完成汇总
const endTime = Utils.getTimestamp();
const totalTime = ((endTime - RewardsAuto.state.startTime) / 1000).toFixed(1);
const info = await API.getRewardsInfo();
if (info) {
// 构建通知消息(简洁版,用于推送)
let notifyMsg = `🎉 任务完成!\n`;
notifyMsg += `💰 当前积分: ${info.balance}\n`;
notifyMsg += `🔍 PC搜索: ${info.pc.progress}/${info.pc.max}\n`;
notifyMsg += `📱 移动搜索: ${info.mobile.progress}/${info.mobile.max}\n`;
if (info.todayDetails && info.todayDetails.length > 0) {
for (const detail of info.todayDetails) {
if (detail.title !== '必应搜索') {
notifyMsg += `✨ ${detail.title}: +${detail.points}\n`;
}
}
}
if (info.history) {
notifyMsg += `📅 本月: ${info.history.month.toLocaleString()}\n`;
}
notifyMsg += `⏱️ 用时: ${totalTime}秒`;
// 发送通知
RewardsAuto.state.sendMSG = notifyMsg;
Utils.log("🎉", notifyMsg, true);
// 构建详细日志(控制台显示)
let detailMsg = `📊 积分明细:\n`;
detailMsg += `┌─────────────────────────\n`;
detailMsg += `│ 💰 今日积分: ${info.balance}\n`;
detailMsg += `├─────────────────────────\n`;
detailMsg += `│ 🔍 搜索积分:\n`;
detailMsg += `│ 💻 PC端: ${info.pc.progress}/${info.pc.max} ${info.pc.progress >= info.pc.max ? '✅' : '⏳'}\n`;
detailMsg += `│ 📱 移动端: ${info.mobile.progress}/${info.mobile.max} ${info.mobile.progress >= info.mobile.max ? '✅' : '⏳'}\n`;
detailMsg += `│ 📊 合计: ${info.pc.progress + info.mobile.progress}/${info.pc.max + info.mobile.max}\n`;
detailMsg += `├─────────────────────────\n`;
// 今日活动明细
if (info.todayDetails && info.todayDetails.length > 0) {
detailMsg += `│ 📋 今日活动:\n`;
for (const detail of info.todayDetails) {
if (detail.max) {
detailMsg += `│ 🔍 ${detail.title}: ${detail.points}/${detail.max}\n`;
} else {
detailMsg += `│ ✨ ${detail.title}: +${detail.points}\n`;
}
}
detailMsg += `├─────────────────────────\n`;
}
// 历史积分
if (info.history) {
detailMsg += `│ 📈 历史积分:\n`;
detailMsg += `│ 📅 本月: ${info.history.month.toLocaleString()}\n`;
detailMsg += `│ 📆 今年: ${info.history.year.toLocaleString()}\n`;
detailMsg += `│ 🏆 生存期: ${info.history.lifetime.toLocaleString()}\n`;
detailMsg += `├─────────────────────────\n`;
}
detailMsg += `│ ⏱️ 用时: ${totalTime} 秒\n`;
detailMsg += `└─────────────────────────`;
Utils.log("📊", detailMsg);
} else {
Utils.log("🎉", `任务执行完成!用时 ${totalTime} 秒`, true);
}
}
};
if (location.hostname === "rewards.bing.com") {
const punchCardSelectors = [
"a[href*='punchcard']", "a[href*='quest']",
"a[data-rac][href*='earn']", "a.cursor-pointer[href]",
"a.group\\/ctrl",
"a[href*='/earn/quest/']",
"a[href*='promotional']",
"a[data-bi-id][href*='earn']",
];
const textPatterns = ["盗贼之海", "五月亮点来袭", "每日活动", "Daily Set", "限时活动", "特别活动"];
const detailTextPatterns = [
"关注赛事", "访问网站", "开始搜索",
"发现", "探索", "获取", "Learn more", "了解更多",
"Start", "Begin", "Watch", "View", "Check",
"立即开始", "立即参与", "立即前往", "立即访问",
"参加活动", "参与活动", "前往活动"
];
const clickDetailTasks = async () => {
const today = Utils.getTodayNum();
const detailStateKey = "Config.punchCardDetailState";
const detailDateKey = "Config.punchCardDetailDate";
const savedDate = GM_getValue(detailDateKey, 0);
let currentDetailState = 0;
if (savedDate === today) {
currentDetailState = GM_getValue(detailStateKey, 0);
} else {
GM_setValue(detailStateKey, 0);
GM_setValue(detailDateKey, today);
}
if (currentDetailState >= 5) {
console.log("[Rewards Auto] 详情页任务今日已全部点击完成");
return true;
}
console.log(`[Rewards Auto] 开始执行详情页任务点击,当前状态: ${currentDetailState}/5`);
// 【防封号】操作前随机延迟 2-4 秒
await Utils.randomDelay(3000, 8000);
// 查找可用的任务按钮
const enabledButtons = document.querySelectorAll(
"a[data-rac][target='_blank']:not([aria-disabled='true']):not([data-disabled='true'])"
);
const disabledButtons = document.querySelectorAll(
"a[data-rac][target='_blank'][aria-disabled='true'][data-disabled='true']"
);
console.log(`[Rewards Auto] 找到 ${enabledButtons.length} 个可用按钮,${disabledButtons.length} 个禁用按钮`);
let clickableButton = null;
// 优先查找包含特定文本的按钮
for (const btn of enabledButtons) {
const text = btn.textContent || "";
const ariaLabel = btn.getAttribute("aria-label") || "";
if (detailTextPatterns.some(pattern => text.includes(pattern) || ariaLabel.includes(pattern))) {
clickableButton = btn;
break;
}
}
// 如果没有找到特定文本的按钮,使用第一个可用按钮
if (!clickableButton && enabledButtons.length > 0) {
clickableButton = enabledButtons[0];
}
if (!clickableButton) {
console.log("[Rewards Auto] 未找到可用的任务按钮,所有任务可能已完成或需要等待解锁");
return true;
}
const buttonText = clickableButton.textContent || "未知任务";
const ariaLabel = clickableButton.getAttribute("aria-label") || buttonText;
console.log(`[Rewards Auto] 准备点击任务按钮: "${buttonText}"`);
// 【防封号】点击前随机延迟 3-8 秒
await Utils.randomDelay(3000, 8000);
try {
clickableButton.click();
console.log(`[Rewards Auto] 已点击任务按钮: "${buttonText}"`);
GM_setValue(detailStateKey, currentDetailState + 1);
GM_setValue(detailDateKey, today);
// 【防封号】点击后随机延迟 2-4 秒
await Utils.randomDelay(3000, 8000);
return true;
} catch (clickError) {
console.error(`[Rewards Auto] 点击任务按钮失败: ${clickError.message}`);
return false;
}
};
const clickPunchCards = async (depth = 0) => {
if (depth > 5) {
console.log("[Rewards Auto] 打卡递归深度超限,停止");
return;
}
const today = Utils.getTodayNum();
const stateKey = "Config.punchCardState";
const dateKey = "Config.punchCardDate";
const savedDate = GM_getValue(dateKey, 0);
let state = savedDate === today ? GM_getValue(stateKey, 0) : 0;
if (state >= 2) {
console.log("[Rewards Auto] 打卡任务已完成");
return;
}
console.log(`[Rewards Auto] 打卡任务: ${state}/2`);
// 【防封号】操作前随机延迟 3-8 秒
await Utils.randomDelay(3000, 8000);
let found = [];
for (const sel of punchCardSelectors) {
try {
found = await Utils.waitForElementsByText(sel, textPatterns, 10000);
if (found.length > 0) break;
} catch {}
}
if (found.length === 0) {
console.log("[Rewards Auto] 未找到打卡卡片");
return;
}
if (state < found.length) {
const target = found[state];
console.log(`[Rewards Auto] 点击: ${target.pattern}`);
// 【防封号】点击前随机延迟
await Utils.randomDelay();
try {
target.element.click();
GM_setValue(stateKey, state + 1);
GM_setValue(dateKey, today);
if (state + 1 < 2) {
// 【防封号】两次点击间隔 5-10 秒
await Utils.randomDelay(5000, 10000);
await clickPunchCards(depth + 1);
}
} catch (e) {
console.error(`[Rewards Auto] 点击失败: ${e.message}`);
}
}
};
const startPunchCards = () => {
setTimeout(async () => {
const path = location.pathname;
if (path.includes("/earn/quest/") || path.includes("punchcard")) {
console.log("[Rewards Auto] 检测到打卡详情页,开始执行任务点击...");
await clickDetailTasks();
} else {
console.log("[Rewards Auto] 检测到奖励主页,开始执行卡片点击...");
await clickPunchCards();
}
console.log("[Rewards Auto] 开始执行每日活动点击...");
await TaskManager.doDailySet();
}, 3000); // 延迟 3 秒等待页面渲染
};
if (document.readyState === "complete" || document.readyState === "interactive") {
startPunchCards();
} else {
document.addEventListener("DOMContentLoaded", startPunchCards);
}
}
GM_registerMenuCommand("🔑 手动授权", () => {
GM_openInTab("https://login.live.com/oauth20_authorize.srf?client_id=0000000040170455&response_type=code&scope=service::prod.rewardsplatform.microsoft.com::MBI_SSL&redirect_uri=https://login.live.com/oauth20_desktop.srf", { active: true });
});
GM_registerMenuCommand("📋 粘贴授权码", () => {
const code = prompt("粘贴授权页面跳转后的完整URL:");
if (code?.trim()) {
GM_setValue("Config.code", code.trim());
alert("已保存!");
}
});
GM_registerMenuCommand("📊 Token状态", () => {
const token = GM_getValue("Config.token", false);
const time = GM_getValue("Config.tokenTime", 0);
const days = time > 0 ? Math.floor((Utils.getTimestamp() - time) / 86400000) : "未知";
alert(`Token: ${token?"已保存":"无"}\n年龄: ${days}天\n授权码: ${GM_getValue("Config.code","")?"有":"无"}`);
});
GM_registerMenuCommand("🚀 立即运行", () => TaskManager.runAll());
// 通知接口配置菜单
GM_registerMenuCommand("🔔 配置通知接口", () => {
const configNames = [
{ key: "Notice.wework", name: "企业微信 Webhook", hint: "群机器人webhook key" },
{ key: "Notice.dingding", name: "钉钉机器人 Access Token", hint: "不加签,关键词需包含 #" },
{ key: "Notice.feishu", name: "飞书机器人 Webhook", hint: "不加签,关键词需包含 #" },
{ key: "Notice.pushme", name: "PushMe Key", hint: "push.i-i.me 推送key" },
{ key: "Notice.bark", name: "Bark Key", hint: "bark.day.app 推送key" }
];
let configStr = "🔔 通知接口配置\n";
configStr += "==================\n\n";
configNames.forEach((item, index) => {
const saved = GM_getValue(item.key, "");
configStr += `${index + 1}. ${item.name}\n`;
configStr += ` 状态: ${saved ? "✅ 已配置" : "❌ 未配置"}\n`;
configStr += ` 说明: ${item.hint}\n\n`;
});
configStr += "请输入要配置的编号 (1-5),或输入 0 清除所有配置:";
const choice = prompt(configStr);
if (!choice) return;
const num = parseInt(choice);
if (num === 0) {
if (confirm("确定要清除所有通知接口配置吗?")) {
configNames.forEach(item => GM_setValue(item.key, ""));
alert("所有通知接口配置已清除!");
}
return;
}
if (num >= 1 && num <= 5) {
const selected = configNames[num - 1];
const current = GM_getValue(selected.key, "");
const newValue = prompt(`配置 ${selected.name}\n\n当前值: ${current || "(空)"}\n\n请输入新的值:`, current);
if (newValue !== null) {
GM_setValue(selected.key, newValue.trim());
alert(`${selected.name} 已${newValue.trim() ? "配置" : "清除"}!`);
}
} else {
alert("无效的编号!");
}
});
GM_registerMenuCommand("📢 测试通知", () => {
RewardsAuto.state.sendMSG = "🧪 这是一条测试消息\n如果你看到这条消息,说明通知接口配置成功!";
Utils.log("📢", "测试通知已发送", true);
alert("测试消息已发送,请检查各通知渠道!");
});
// 浏览器通知静默开关
const updateBroMenu = () => {
const enabled = GM_getValue("Notice.bro", true);
return enabled ? "🔕 关闭浏览器通知" : "🔔 开启浏览器通知";
};
GM_registerMenuCommand(updateBroMenu(), () => {
const current = GM_getValue("Notice.bro", true);
GM_setValue("Notice.bro", !current);
alert(`浏览器通知已${!current ? "开启" : "关闭"}`);
location.reload();
});
GM_registerMenuCommand("📋 查看通知状态", () => {
const wework = GM_getValue("Notice.wework", "");
const dingding = GM_getValue("Notice.dingding", "");
const feishu = GM_getValue("Notice.feishu", "");
const pushme = GM_getValue("Notice.pushme", "");
const bark = GM_getValue("Notice.bark", "");
let status = "📊 通知接口配置状态:\n\n";
status += `企业微信: ${wework ? "✅ 已配置" : "❌ 未配置"}\n`;
status += `钉钉: ${dingding ? "✅ 已配置" : "❌ 未配置"}\n`;
status += `飞书: ${feishu ? "✅ 已配置" : "❌ 未配置"}\n`;
status += `PushMe: ${pushme ? "✅ 已配置" : "❌ 未配置"}\n`;
status += `Bark: ${bark ? "✅ 已配置" : "❌ 未配置"}\n`;
alert(status);
});
const init = () => {
TaskManager.init();
// 检查今日任务是否已完成
const isKeep = GM_getValue("Config.keep", true);
const checkDone = (enabled, date) => !enabled || date === RewardsAuto.state.dateNowNum;
const isAllDone = checkDone(GM_getValue("Tasks.sign"), TaskManager.signDate) &&
checkDone(GM_getValue("Tasks.read"), TaskManager.readDate) &&
checkDone(GM_getValue("Tasks.promos"), TaskManager.promosDate) &&
checkDone(GM_getValue("Tasks.search"), TaskManager.searchDate);
if (!isKeep && isAllDone) {
Utils.log("💤", "今日任务已全部完成");
return;
}
// 【防封号核心】随机延迟 5-95 秒启动,避免定时器特征
const delay = Utils.randomRange(5000, 95000);
Utils.log("⏳", `${delay/1000}秒后启动...`);
setTimeout(() => TaskManager.runAll(), delay);
};
// 清除可能影响搜索的 Cookie
GM_cookie("delete", { url: "https://bing.com", name: "_EDGE_S" });
init();
})();