mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-23 08:21:04 +00:00
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:
187
src/index.ts
187
src/index.ts
@@ -1,5 +1,5 @@
|
||||
import cluster from 'cluster'
|
||||
import { BrowserContext, Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import Browser from './browser/Browser'
|
||||
import BrowserFunc from './browser/BrowserFunc'
|
||||
@@ -14,6 +14,8 @@ import { Workers } from './functions/Workers'
|
||||
import Activities from './functions/Activities'
|
||||
|
||||
import { Account } from './interface/Account'
|
||||
import Axios from './util/Axios'
|
||||
|
||||
|
||||
// Main bot class
|
||||
export class MicrosoftRewardsBot {
|
||||
@@ -25,18 +27,25 @@ export class MicrosoftRewardsBot {
|
||||
func: BrowserFunc,
|
||||
utils: BrowserUtil
|
||||
}
|
||||
public isMobile: boolean = false
|
||||
public isMobile: boolean
|
||||
public homePage!: Page
|
||||
|
||||
private collectedPoints: number = 0
|
||||
private pointsCanCollect: number = 0
|
||||
private pointsInitial: number = 0
|
||||
|
||||
private activeWorkers: number
|
||||
private mobileRetryAttempts: number
|
||||
private browserFactory: Browser = new Browser(this)
|
||||
private accounts: Account[]
|
||||
private workers: Workers
|
||||
private login = new Login(this)
|
||||
private accessToken: string = ''
|
||||
|
||||
constructor() {
|
||||
//@ts-expect-error Will be initialized later
|
||||
public axios: Axios
|
||||
|
||||
constructor(isMobile: boolean) {
|
||||
this.isMobile = isMobile
|
||||
this.log = log
|
||||
|
||||
this.accounts = []
|
||||
@@ -48,6 +57,7 @@ export class MicrosoftRewardsBot {
|
||||
}
|
||||
this.config = loadConfig()
|
||||
this.activeWorkers = this.config.clusters
|
||||
this.mobileRetryAttempts = 0
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
@@ -55,7 +65,7 @@ export class MicrosoftRewardsBot {
|
||||
}
|
||||
|
||||
async run() {
|
||||
log('MAIN', `Bot started with ${this.config.clusters} clusters`)
|
||||
log('main', 'MAIN', `Bot started with ${this.config.clusters} clusters`)
|
||||
|
||||
// Only cluster when there's more than 1 cluster demanded
|
||||
if (this.config.clusters > 1) {
|
||||
@@ -65,12 +75,12 @@ export class MicrosoftRewardsBot {
|
||||
this.runWorker()
|
||||
}
|
||||
} else {
|
||||
this.runTasks(this.accounts)
|
||||
await this.runTasks(this.accounts)
|
||||
}
|
||||
}
|
||||
|
||||
private runMaster() {
|
||||
log('MAIN-PRIMARY', 'Primary process started')
|
||||
log('main', 'MAIN-PRIMARY', 'Primary process started')
|
||||
|
||||
const accountChunks = this.utils.chunkArray(this.accounts, this.config.clusters)
|
||||
|
||||
@@ -83,18 +93,18 @@ export class MicrosoftRewardsBot {
|
||||
cluster.on('exit', (worker, code) => {
|
||||
this.activeWorkers -= 1
|
||||
|
||||
log('MAIN-WORKER', `Worker ${worker.process.pid} destroyed | Code: ${code} | Active workers: ${this.activeWorkers}`, 'warn')
|
||||
log('main', 'MAIN-WORKER', `Worker ${worker.process.pid} destroyed | Code: ${code} | Active workers: ${this.activeWorkers}`, 'warn')
|
||||
|
||||
// Check if all workers have exited
|
||||
if (this.activeWorkers === 0) {
|
||||
log('MAIN-WORKER', 'All workers destroyed. Exiting main process!', 'warn')
|
||||
log('main', 'MAIN-WORKER', 'All workers destroyed. Exiting main process!', 'warn')
|
||||
process.exit(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private runWorker() {
|
||||
log('MAIN-WORKER', `Worker ${process.pid} spawned`)
|
||||
log('main', 'MAIN-WORKER', `Worker ${process.pid} spawned`)
|
||||
// Receive the chunk of accounts from the master
|
||||
process.on('message', async ({ chunk }) => {
|
||||
await this.runTasks(chunk)
|
||||
@@ -103,58 +113,68 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
private async runTasks(accounts: Account[]) {
|
||||
for (const account of accounts) {
|
||||
log('MAIN-WORKER', `Started tasks for account ${account.email}`)
|
||||
log('main', 'MAIN-WORKER', `Started tasks for account ${account.email}`)
|
||||
|
||||
// Desktop Searches, DailySet and More Promotions
|
||||
await this.Desktop(account)
|
||||
this.axios = new Axios(account.proxy)
|
||||
if (this.config.parallel) {
|
||||
await Promise.all([
|
||||
this.Desktop(account),
|
||||
(() => {
|
||||
const mobileInstance = new MicrosoftRewardsBot(true)
|
||||
mobileInstance.axios = this.axios
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, stop and try the next account
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
continue
|
||||
return mobileInstance.Mobile(account)
|
||||
})()
|
||||
])
|
||||
} else {
|
||||
this.isMobile = false
|
||||
await this.Desktop(account)
|
||||
|
||||
this.isMobile = true
|
||||
await this.Mobile(account)
|
||||
}
|
||||
|
||||
// Mobile Searches
|
||||
await this.Mobile(account)
|
||||
|
||||
log('MAIN-WORKER', `Completed tasks for account ${account.email}`)
|
||||
log('main', 'MAIN-WORKER', `Completed tasks for account ${account.email}`, 'log', 'green')
|
||||
}
|
||||
|
||||
log('MAIN-PRIMARY', 'Completed tasks for ALL accounts')
|
||||
log('MAIN-PRIMARY', 'All workers destroyed!')
|
||||
process.exit(0)
|
||||
log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green')
|
||||
process.exit()
|
||||
}
|
||||
|
||||
// Desktop
|
||||
async Desktop(account: Account) {
|
||||
this.isMobile = false
|
||||
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log('MAIN', 'Starting DESKTOP browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
||||
|
||||
this.pointsInitial = data.userStatus.availablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${this.pointsInitial}`)
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
const earnablePoints = browserEnarablePoints + appEarnablePoints
|
||||
this.collectedPoints = earnablePoints
|
||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today (Browser: ${browserEnarablePoints} points, App: ${appEarnablePoints} points)`)
|
||||
// Tally all the desktop points
|
||||
this.pointsCanCollect = browserEnarablePoints.dailySetPoints +
|
||||
browserEnarablePoints.desktopSearchPoints
|
||||
+ browserEnarablePoints.morePromotionsPoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!')
|
||||
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
|
||||
// Close desktop browser
|
||||
return await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
@@ -187,25 +207,41 @@ export class MicrosoftRewardsBot {
|
||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||
|
||||
// Close desktop browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Mobile
|
||||
async Mobile(account: Account) {
|
||||
this.isMobile = true
|
||||
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log('MAIN', 'Starting MOBILE browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
this.pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
|
||||
// Close mobile browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Do daily check in
|
||||
if (this.config.workers.doDailyCheckIn) {
|
||||
await this.activities.doDailyCheckIn(this.accessToken, data)
|
||||
@@ -216,63 +252,68 @@ export class MicrosoftRewardsBot {
|
||||
await this.activities.doReadToEarn(this.accessToken, data)
|
||||
}
|
||||
|
||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
// If no mobile searches data found, stop (Does not always exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
||||
// Retry until all points are gathered when (retryMobileSearch is enabled)
|
||||
if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
|
||||
log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...')
|
||||
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
|
||||
// Increment retry count
|
||||
this.mobileRetryAttempts++
|
||||
}
|
||||
|
||||
// Exit if retries are exhausted
|
||||
if (this.mobileRetryAttempts > this.config.searchSettings.retryMobileSearchAmount) {
|
||||
log(this.isMobile, 'MAIN', `Max retry limit of ${this.config.searchSettings.retryMobileSearchAmount} reached. Exiting retry loop`, 'warn')
|
||||
} else if (this.mobileRetryAttempts !== 0) {
|
||||
log(this.isMobile, 'MAIN', `Attempt ${this.mobileRetryAttempts}/${this.config.searchSettings.retryMobileSearchAmount}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow')
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
|
||||
// Retry
|
||||
// Create a new browser and try
|
||||
await this.Mobile(account)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn')
|
||||
}
|
||||
} else {
|
||||
log('MAIN', 'No mobile searches found!')
|
||||
}
|
||||
|
||||
// Fetch new points
|
||||
const earnablePoints = await this.browser.func.getBrowserEarnablePoints() + await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
const afterPointAmount = await this.browser.func.getCurrentPoints()
|
||||
|
||||
// If the new earnable is 0, means we got all the points, else retract
|
||||
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)
|
||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
||||
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - this.pointsInitial} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
private async closeBrowser(browser: BrowserContext, email: string) {
|
||||
// Save cookies
|
||||
await saveSessionData(this.config.sessionPath, browser, email, this.isMobile)
|
||||
|
||||
// Close browser
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const rewardsBot = new MicrosoftRewardsBot(false)
|
||||
|
||||
const bot = new MicrosoftRewardsBot()
|
||||
try {
|
||||
await rewardsBot.initialize()
|
||||
await rewardsBot.run()
|
||||
} catch (error) {
|
||||
log(false, 'MAIN-ERROR', `Error running desktop bot: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize accounts first and then start the bot
|
||||
bot.initialize().then(() => {
|
||||
bot.run()
|
||||
// Start the bots
|
||||
main().catch(error => {
|
||||
log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error')
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user