1.5 inital (#234)

* 1.4.12

* Update README.md

* Update package.json

* Update package.json

* 1.5 initial

- Added parallel mode (experimental, likely no Docker supported)
- Added chalk for clearer logging
- Added support for "SearchOnBing" Activities
- Added more configurable options for certain things
- Redone some of the popup/banner clicking for searching (Redo the entire "popup" clicking, so they're more specifically targeted)
- Axios proxy is now optional in the config
- Fingerprint saving is now optional for desktop and mobile

There needs to be many changes for Docker support, including parallel, the new config settings and general testing!

This is still highly experimental, if you use Docker or want something more stable, use the version before this commit!

* Add queries.json to build

* fix(Login): update URL within authorization loop to reflect current page (#210)

* Many changes

- Updated Packages
- Fixed mobile searches erroring out for dashboard  data
- Reworked "bad page" detection
- Catching more errors
- Reworked the search and "close tabs"
- More fixes to the login
- Fixed to paralell and clustering, thanks to @AariaX

* Docker 1.5 preliminary support (#211)

* Basic docker functionality for 1.5

Preliminary docker support for 1.5. Requires headless=true, clusters=1

* Tidy up timezone, add TZ to compose file

Minor changes that should improve timezone handling, and (hopefully) improve scheduling function

* updated readme to simplify and clarify docker instructions

also removed env vars from table

* Fix syntax for cron

* Fix scheduling, add .gitattributes to normalize line endings

fixed line endings caused by Windows in crontab.template and run_daily.sh, which were breaking cron and script execution in the Docker container.

* Removed unnecessary scheduling key from config.json

This key isn't necessary for docker or the base script.

* Basic docker functionality for 1.5

Preliminary docker support for 1.5. Requires headless=true, clusters=1

Tidied up timezone, add TZ to compose file

Minor changes that should improve timezone handling, and (hopefully) improve scheduling function

updated readme to simplify and clarify docker instructions

also removed env vars from table

Fixed syntax for cron

Fixed scheduling, add .gitattributes to normalize line endings

Fixed line endings caused by Windows in crontab.template and run_daily.sh, which were breaking cron and script execution in the Docker container.

Removed unnecessary scheduling key from config.json

This key isn't necessary for docker or the base script.

* Improve scheduling handling, show logs in console

Fixes scheduling when RUN_ON_START=true, and fixes scheduled runs not appearing in docker logs.

* Update compose.yaml

revert service and container name, revert volumes for better generalization, add tips to environment to set scheduling, timezone and whether to run on container start

* Update README.md

proper container name

Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com>

---------

Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com>

* Fixes

- Reworked some of the point counting
- Reverted back to the "playwright" package
- Fixed error throw for emailPrefill

* Update config.json

* Add pre-build script

* Update package.json

* Handle 2FA in parallel mode (#219)

* catch error in reloadBadPage (#220)

* Use pre-build and simplify dockerfile (#218)

This uses the new pre-build script included in package.json to handle deps greatly simplifying the dockerfile.

* Small improvements

* Small fixes

- Fixed log spam for "Waiting for authorization"
- Increased wait from 2 to 5 seconds
- Increased search to "safer" values for default

* Experimenting with selectors

Seeing #223 I want to try if this is a good new addition, since for most user this SHOULD work just as good as clicking the entire box.

* More stuff

- Added ability to exclude logs by their function name
- Now caching config settings

* fix: don't retry on 0 (#228)

* Improvements

- Check if searches for mobile are enabled before creating the new page in the browser
- Return message if mobile search data cannot be found
- Added more selectors for coupons

* Improve Popup Dismissal

- Now executes in Parallel
- Respects a timeout of 1 second

---------

Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com>
Co-authored-by: mgrimace <55518507+mgrimace@users.noreply.github.com>
This commit is contained in:
Netsky
2025-02-15 16:14:47 +01:00
committed by GitHub
parent d1f4364e18
commit 849406c44f
37 changed files with 937 additions and 586 deletions

View File

@@ -1,4 +1,4 @@
import { Page } from 'playwright'
import { Page } from 'rebrowser-playwright'
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
@@ -18,12 +18,12 @@ export class Workers {
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
if (!activitiesUncompleted.length) {
this.bot.log('DAILY-SET', 'All Daily Set" items have already been completed')
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All Daily Set" items have already been completed')
return
}
// Solve Activities
this.bot.log('DAILY-SET', 'Started solving "Daily Set" items')
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'Started solving "Daily Set" items')
await this.solveActivities(page, activitiesUncompleted)
@@ -32,27 +32,34 @@ export class Workers {
// Always return to the homepage if not already
await this.bot.browser.func.goHome(page)
this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have been completed')
}
// Punch Card
async doPunchCard(page: Page, data: DashboardData) {
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion?.complete) ?? [] // Only return uncompleted punch cards
const punchCardsUncompleted = data.punchCards?.filter(x => x.parentPromotion && !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
if (!punchCardsUncompleted.length) {
this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Cards" have already been completed')
return
}
for (const punchCard of punchCardsUncompleted) {
// Ensure parentPromotion exists before proceeding
if (!punchCard.parentPromotion?.title) {
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Skipped punchcard "${punchCard.name}" | Reason: Parent promotion is missing!`, 'warn')
continue
}
// Get latest page for each card
page = await this.bot.browser.utils.getLatestTab(page)
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
// Solve Activities
this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
// Got to punch card index page in a new tab
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
@@ -72,10 +79,10 @@ export class Workers {
await this.bot.browser.func.goHome(page)
}
this.bot.log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
}
this.bot.log('PUNCH-CARD', 'All "Punch Card" items have been completed')
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Card" items have been completed')
}
// More Promotions
@@ -90,12 +97,12 @@ export class Workers {
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0 && x.exclusiveLockedFeatureStatus !== 'locked') ?? []
if (!activitiesUncompleted.length) {
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
return
}
// Solve Activities
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" items')
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'Started solving "More Promotions" items')
page = await this.bot.browser.utils.getLatestTab(page)
@@ -106,7 +113,7 @@ export class Workers {
// Always return to the homepage if not already
await this.bot.browser.func.goHome(page)
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
}
// Solve all the different types of activities
@@ -132,28 +139,22 @@ export class Workers {
}
let selector = `[data-bi-id^="${activity.offerId}"]`
let selector = `[data-bi-id^="${activity.offerId}"] .pointLink:not(.contentContainer .pointLink)`
if (punchCard) {
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
} else if (activity.name.toLowerCase().includes('membercenter')) {
selector = `[data-bi-id^="${activity.name}"]`
} else if (activity.name.toLowerCase().includes('membercenter') || activity.name.toLowerCase().includes('exploreonbing')) {
selector = `[data-bi-id^="${activity.name}"] .pointLink:not(.contentContainer .pointLink)`
}
// Click element, it will be opened in a new tab
await activityPage.click(selector)
// Select the new activity page
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
// Wait for the new tab to fully load, ignore error.
/*
Due to common false timeout on this function, we're ignoring the error regardless, if it worked then it's faster,
if it didn't then it gave enough time for the page to load.
*/
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
await this.bot.utils.wait(5000)
await this.bot.utils.wait(2000)
switch (activity.promotionType) {
// Quiz (Poll, Quiz or ABC)
@@ -163,23 +164,31 @@ export class Workers {
case 10:
// Normal poll
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
this.bot.log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doPoll(activityPage)
} else { // ABC
this.bot.log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doABC(activityPage)
}
break
// This Or That Quiz (Usually 50 points)
case 50:
this.bot.log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doThisOrThat(activityPage)
break
// Quizzes are usually 30-40 points
default:
this.bot.log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doQuiz(activityPage)
break
}
@@ -187,14 +196,24 @@ export class Workers {
// UrlReward (Visit)
case 'urlreward':
this.bot.log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
await this.bot.activities.doUrlReward(activityPage)
// Search on Bing are subtypes of "urlreward"
if (activity.name.toLowerCase().includes('exploreonbing')) {
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "SearchOnBing" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doSearchOnBing(activityPage, activity)
} else {
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
await activityPage.click(selector)
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
await this.bot.activities.doUrlReward(activityPage)
}
break
// Misc, Usually UrlReward Type
// Unsupported types
default:
this.bot.log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
await this.bot.activities.doUrlReward(activityPage)
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Skipped activity "${activity.title}" | Reason: Unsupported type: "${activity.promotionType}"!`, 'warn')
break
}
@@ -202,7 +221,7 @@ export class Workers {
await this.bot.utils.wait(2000)
} catch (error) {
this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error')
this.bot.log(this.bot.isMobile, 'ACTIVITY', 'An error occurred:' + error, 'error')
}
}