From 40029fce06057b57f969004c15037e6c57ddf56f Mon Sep 17 00:00:00 2001 From: phanex Date: Thu, 17 Sep 2026 20:07:31 +0200 Subject: [PATCH] fix(rewards): add support for new /earn dashboard and Explore on Bing activities (fixes #603) --- src/browser/ReactFunc.ts | 41 +++++++++--- src/functions/activities/api/UrlReward.ts | 6 +- src/functions/activities/rewards/DailySet.ts | 22 ++++++- .../activities/rewards/MorePromotions.ts | 16 +++++ .../rewards/PromotionActivityRunner.ts | 2 +- .../activities/search/SearchOnBingShared.ts | 2 +- .../bing-search-activity-queries.json | 63 +++++++++++++++++++ 7 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/browser/ReactFunc.ts b/src/browser/ReactFunc.ts index 01529d9b..51d9d846 100644 --- a/src/browser/ReactFunc.ts +++ b/src/browser/ReactFunc.ts @@ -291,12 +291,28 @@ export default class ReactFunc { // Never try future-dated offers, lol const reportable = !!hash && !isCompleted && !isLocked && (date === null || date <= today) + const isExploreOnBing = offerId.toLowerCase().includes('exploreonbing') + const points = (obj.points as number) ?? (obj.pointProgressMax as number) ?? (isExploreOnBing ? 10 : 0) + let title = (obj.title as string) ?? '' + let description = (obj.description as string) ?? '' + if (isExploreOnBing && !title) { + if (offerId.includes('airline')) { title = 'Take off soon'; description = 'Search on Bing to book affordable flights' } + else if (offerId.includes('airport')) { title = 'Park with ease'; description = 'Search on Bing to reserve airport parking' } + else if (offerId.includes('flower')) { title = 'Send a smile'; description = 'Search on Bing for fast and fresh flower delivery options' } + else if (offerId.includes('streaming')) { title = 'Stream your favorites'; description = 'Search on Bing to find the best streaming platforms and bundles' } + else if (offerId.includes('credit')) { title = 'Know your score'; description = 'Search on Bing to check your credit report and raise your score' } + else if (offerId.includes('health')) { title = 'Feeling symptoms?'; description = 'Search on Bing for a health condition to learn about the symptoms' } + else if (offerId.includes('recipe')) { title = 'Learn a new recipe'; description = 'Search on Bing for new recipes' } + else if (offerId.includes('videogames')) { title = 'Explore videogames'; description = 'Search on Bing to explore videogames' } + else { title = offerId } + } + const candidate: ParsedOffer = { offerId, hash, - title: (obj.title as string) ?? '', - description: (obj.description as string) ?? '', - points: (obj.points as number) ?? (obj.pointProgressMax as number) ?? 0, + title, + description, + points, promotionSubtype: (obj.promotionSubtype as string | null) ?? null, destination: (obj.destination as string) ?? (obj.destinationUrl as string) ?? '', isCompleted, @@ -720,26 +736,31 @@ export default class ReactFunc { const { id, at } = anchors[k]! if (!this.isParentQuestId(id)) continue - const next = anchors[k + 1]?.at ?? combined.length - const region = combined.slice(at, Math.min(next, at + 3000)) + let next = combined.length + for (let j = k + 1; j < anchors.length; j++) { + if (anchors[j]!.id !== id && this.isParentQuestId(anchors[j]!.id)) { + next = anchors[j]!.at + break + } + } + const region = combined.slice(at, Math.min(next, at + 4000)) const title = region.match(/"alt":"((?:[^"\\]|\\.)*)"/)?.[1] ?? region.match(/"title":"((?:[^"\\]|\\.)*)"/)?.[1] ?? '' - const pointsMatch = region.match(/\["\+","([\d,]+)"\]/) + const pointsMatch = region.match(/\["(?:|\+)","([\d,]+)"\]/) const points = pointsMatch ? Number(pointsMatch[1]!.replace(/,/g, '')) : 0 - const taskM = region.match(/(\d+)\s*\/\s*(\d+)\s*tasks/) + const taskM = region.match(/(\d+)\s*(?:\/|\b(?:von|of|sur|de|z|iz|із|з)\b)\s*(\d+)/i) const complete = !!taskM && Number(taskM[1]) >= Number(taskM[2]!) && Number(taskM[2]) > 0 - // First wins for title/points const prev = byId.get(id) byId.set(id, { offerId: id, title: prev?.title || title, - pointProgressMax: prev?.pointProgressMax || points, - complete: prev?.complete || complete + pointProgressMax: Math.max(prev?.pointProgressMax || 0, points), + complete: (prev && prev.complete) || complete }) } diff --git a/src/functions/activities/api/UrlReward.ts b/src/functions/activities/api/UrlReward.ts index edc76785..4049fac9 100644 --- a/src/functions/activities/api/UrlReward.ts +++ b/src/functions/activities/api/UrlReward.ts @@ -74,9 +74,9 @@ export class UrlReward extends BaseActivity { } ], { - url: URLs.rewards.dashboard, - referer: URLs.rewards.dashboard, - routerStateTree: this.bot.browser.react.routerStateTree('dashboard') + url: URLs.rewards.earn, + referer: URLs.rewards.earn, + routerStateTree: this.bot.browser.react.routerStateTree('earn') } ) diff --git a/src/functions/activities/rewards/DailySet.ts b/src/functions/activities/rewards/DailySet.ts index c16c4202..3e7bcd81 100644 --- a/src/functions/activities/rewards/DailySet.ts +++ b/src/functions/activities/rewards/DailySet.ts @@ -1,13 +1,31 @@ import { BaseActivity } from '../BaseActivity' -import type { DashboardData } from '../../../interface/DashboardData' +import type { BasePromotion, DashboardData } from '../../../interface/DashboardData' import { PromotionActivityRunner } from './PromotionActivityRunner' export class DailySet extends BaseActivity { public async run(data: DashboardData): Promise { const today = this.bot.utils.getFormattedDate() - const pending = + let pending: BasePromotion[] = data.dashboard.dailySetPromotions[today]?.filter(item => !item.complete && item.pointProgressMax > 0) ?? [] + if (!pending.length && this.bot.reactSnapshot?.offers) { + const reactDailySet = this.bot.reactSnapshot.offers.filter( + o => o.offerId.toLowerCase().includes('dailyset') && !o.isCompleted && o.points > 0 && o.reportable + ) + if (reactDailySet.length) { + pending = reactDailySet.map(o => ({ + offerId: o.offerId, + title: o.title || o.offerId, + complete: o.isCompleted, + pointProgressMax: o.points, + promotionType: 'urlreward', + destinationUrl: o.destination, + hash: o.hash, + activityType: o.activityType ?? 11 + } as unknown as BasePromotion)) + } + } + if (!pending.length) { this.bot.logger.info(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have already been completed') return diff --git a/src/functions/activities/rewards/MorePromotions.ts b/src/functions/activities/rewards/MorePromotions.ts index 81284a66..9cfabcc7 100644 --- a/src/functions/activities/rewards/MorePromotions.ts +++ b/src/functions/activities/rewards/MorePromotions.ts @@ -14,6 +14,22 @@ export class MorePromotions extends BaseActivity { .map(promotion => [promotion.offerId, promotion as BasePromotion] as const) ).values() ] + const existingIds = new Set(promotions.map(p => p.offerId)) + for (const offer of this.bot.reactSnapshot?.reportable ?? []) { + if (offer.offerId.toLowerCase().includes('dailyset') || existingIds.has(offer.offerId)) continue + promotions.push({ + offerId: offer.offerId, + title: offer.title || offer.offerId, + complete: offer.isCompleted, + pointProgressMax: offer.points, + promotionType: 'urlreward', + destinationUrl: offer.destination, + hash: offer.hash, + activityType: offer.activityType ?? 11, + priority: 1 + } as unknown as BasePromotion) + existingIds.add(offer.offerId) + } const pending = promotions.filter(promotion => this.isActionable(promotion)) if (!pending.length) { diff --git a/src/functions/activities/rewards/PromotionActivityRunner.ts b/src/functions/activities/rewards/PromotionActivityRunner.ts index 46728342..b42db933 100644 --- a/src/functions/activities/rewards/PromotionActivityRunner.ts +++ b/src/functions/activities/rewards/PromotionActivityRunner.ts @@ -43,7 +43,7 @@ export class PromotionActivityRunner extends BaseActivity { return } - const isSearchOnBing = name.includes('exploreonbing') + const isSearchOnBing = name.includes('exploreonbing') || offerId.toLowerCase().includes('exploreonbing') if (isSearchOnBing && !this.bot.config.activities.searchOnBing) { this.logDisabled('SearchOnBing', offerId) return diff --git a/src/functions/activities/search/SearchOnBingShared.ts b/src/functions/activities/search/SearchOnBingShared.ts index 363b1bc7..73fad456 100644 --- a/src/functions/activities/search/SearchOnBingShared.ts +++ b/src/functions/activities/search/SearchOnBingShared.ts @@ -133,7 +133,7 @@ function extractSearchTerm(description: string): string { return description .trim() .replace( - /^\s*(?:search(?:\s+on\s+bing|\s+bing|\s+the\s+web)?\s+for|look\s+up|find|explore|discover)\b[\s:]+/i, + /^\s*(?:search(?:\s+on\s+bing|\s+bing|\s+the\s+web)?\s+(?:for|to)|look\s+up|find|explore|discover)\b[\s:]+/i, '' ) .replace(/^["'“”‘’]+|["'“”‘’]+$/g, '') diff --git a/src/functions/bing-search-activity-queries.json b/src/functions/bing-search-activity-queries.json index 7babdb90..45e87f36 100644 --- a/src/functions/bing-search-activity-queries.json +++ b/src/functions/bing-search-activity-queries.json @@ -1003,5 +1003,68 @@ "cheap airport parking near me", "long stay airport parking" ] + }, + { + "title": "Take off soon", + "queries": [ + "cheap flights to london", + "book affordable flights", + "airline tickets deals", + "cheap airline tickets" + ] + }, + { + "title": "Send a smile", + "queries": [ + "flower delivery near me", + "fresh flower delivery", + "send flowers online", + "fast flower delivery" + ] + }, + { + "title": "Stream your favorites", + "queries": [ + "best streaming platforms", + "streaming services comparison", + "top streaming services", + "streaming bundles" + ] + }, + { + "title": "Know your score", + "queries": [ + "free credit report", + "check credit score free", + "how to raise credit score", + "credit score check" + ] + }, + { + "title": "Feeling symptoms?", + "queries": [ + "health condition symptoms", + "common cold symptoms", + "flu symptoms checker", + "health symptoms" + ] + }, + { + "title": "Learn a new recipe", + "queries": [ + "easy dinner recipes", + "healthy lunch recipes", + "quick dessert recipes", + "new recipes" + ] + }, + { + "title": "Explore videogames", + "queries": [ + "best new video games", + "top video games 2026", + "popular video games", + "explore video games" + ] } ]