8 Commits

Author SHA1 Message Date
TheNetsky
f3fb641ecd 1.3.2
- Better generation of mobile user agents
2023-12-27 11:38:52 +01:00
TheNetsky
bca1e7c896 1.3.1
- Poll clicking will be attempted regardless of the selector being found
- Fix tabs sticking upon search error
- Updated dependencies
2023-12-20 13:03:08 +01:00
Netsky
fd7c8e36d4 Update README.md 2023-12-01 12:12:26 +01:00
Netsky
dcb0c25d46 Remove the feature of Promo Items 2023-12-01 12:02:17 +01:00
TheNetsky
a8cb5482d4 1.3.0 2023-12-01 11:49:14 +01:00
TheNetsky
28286ff9fe Pre 1.3.0 2023-11-23 14:38:58 +01:00
TheNetsky
1d6167aeca Missing await 2023-11-04 16:24:25 +01:00
TheNetsky
3b15fe19a7 1.2.5 2023-11-04 16:21:56 +01:00
17 changed files with 389 additions and 269 deletions

20
.vscode/launch.json vendored
View File

@@ -1,20 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${file}",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
]
}

View File

@@ -11,6 +11,11 @@ Under development, however mainly for personal use!
5. Run `npm run build` to build the script 5. Run `npm run build` to build the script
6. Run `npm run start` to start the built script 6. Run `npm run start` to start the built script
## Notes ##
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run chrome-kill-win` script. (Windows)
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
## Features ## ## Features ##
- [x] Multi-Account Support - [x] Multi-Account Support
- [x] Session Storing - [x] Session Storing
@@ -31,8 +36,7 @@ Under development, however mainly for personal use!
- [x] Completing Click Rewards - [x] Completing Click Rewards
- [x] Completing Polls - [x] Completing Polls
- [x] Completing Punchcards - [x] Completing Punchcards
- [ ] Solving This Or That Quiz - [x] Solving This Or That Quiz (Random)
- [x] Clicking Promotional Items
- [x] Solving ABC Quiz - [x] Solving ABC Quiz
- [ ] Completing Shopping Game - [ ] Completing Shopping Game
- [ ] Completing Gaming Tab - [ ] Completing Gaming Tab

View File

@@ -1,6 +1,6 @@
{ {
"name": "microsoft-rewards-script", "name": "microsoft-rewards-script",
"version": "1.2.4", "version": "1.3.2",
"description": "Automatically do tasks for Microsoft Rewards but in TS!", "description": "Automatically do tasks for Microsoft Rewards but in TS!",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
@@ -10,7 +10,8 @@
"build": "tsc", "build": "tsc",
"start": "node ./dist/index.js", "start": "node ./dist/index.js",
"ts-start": "ts-node ./src/index.ts", "ts-start": "ts-node ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev" "dev": "ts-node ./src/index.ts -dev",
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\""
}, },
"keywords": [ "keywords": [
"Bing Rewards", "Bing Rewards",
@@ -25,17 +26,16 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.7.0", "@typescript-eslint/eslint-plugin": "^6.7.0",
"typescript": "^5.2.2" "eslint": "^8.54.0",
"eslint-plugin-modules-newline": "^0.0.6",
"typescript": "^5.3.3"
}, },
"dependencies": { "dependencies": {
"axios": "^1.5.1", "axios": "^1.6.2",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"eslint": "^8.49.0", "fingerprint-generator": "^2.1.45",
"eslint-plugin-modules-newline": "^0.0.6", "fingerprint-injector": "^2.1.45",
"fingerprint-generator": "^2.1.42", "puppeteer": "^21.6.1",
"fingerprint-injector": "^2.1.42", "ts-node": "^10.9.2"
"puppeteer": "^21.4.1",
"puppeteer-extra": "^3.3.6",
"ts-node": "^10.9.1"
} }
} }

View File

@@ -1,8 +1,9 @@
import puppeteer from 'puppeteer-extra' import puppeteer from 'puppeteer'
import { FingerprintInjector } from 'fingerprint-injector' import { FingerprintInjector } from 'fingerprint-injector'
import { FingerprintGenerator } from 'fingerprint-generator' import { FingerprintGenerator } from 'fingerprint-generator'
import { MicrosoftRewardsBot } from '../index' import { MicrosoftRewardsBot } from '../index'
import { loadSesion } from '../util/Load'
import { AccountProxy } from '../interface/Account' import { AccountProxy } from '../interface/Account'
@@ -15,40 +16,57 @@ http://f.vision/
class Browser { class Browser {
private bot: MicrosoftRewardsBot private bot: MicrosoftRewardsBot
private usedUserAgents: string[] = []
constructor(bot: MicrosoftRewardsBot) { constructor(bot: MicrosoftRewardsBot) {
this.bot = bot this.bot = bot
} }
async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) { async createBrowser(email: string, proxy: AccountProxy) {
// const userAgent = await getUserAgent(isMobile) // const userAgent = await getUserAgent(isMobile)
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: this.bot.config.headless, headless: this.bot.config.headless ? 'new' : false,
userDataDir: await this.bot.browser.func.loadSesion(email), userDataDir: await loadSesion(this.bot.config.sessionPath, email),
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--mute-audio', '--mute-audio',
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors',
proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : '' proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : ''
] ]
}) })
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({ let fingerPrintData = new FingerprintGenerator().getFingerprint({
devices: isMobile ? ['mobile'] : ['desktop'], devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
operatingSystems: isMobile ? ['android'] : ['windows'], operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
browsers: ['edge'], browsers: ['edge']
browserListQuery: 'last 2 Edge versions'
}) })
if (this.usedUserAgents) {
while (this.usedUserAgents.includes(fingerPrintData.fingerprint.navigator.userAgent)) {
fingerPrintData = new FingerprintGenerator().getFingerprint({
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
browsers: ['edge']
})
}
}
this.usedUserAgents.push(fingerPrintData.fingerprint.navigator.userAgent)
// Modify the newPage function to attach the fingerprint // Modify the newPage function to attach the fingerprint
const originalNewPage = browser.newPage const originalNewPage = browser.newPage
browser.newPage = async function () { browser.newPage = async function () {
const page = await originalNewPage.apply(browser) const page = await originalNewPage.apply(browser)
await new FingerprintInjector().attachFingerprintToPuppeteer(page, { fingerprint, headers }) await new FingerprintInjector().attachFingerprintToPuppeteer(page, fingerPrintData)
return page return page
} }
this.bot.log('BROWSER', `Created browser with User-Agent: "${fingerPrintData.fingerprint.navigator.userAgent}"`)
return browser return browser
} }

View File

@@ -1,7 +1,5 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { CheerioAPI, load } from 'cheerio' import { CheerioAPI, load } from 'cheerio'
import fs from 'fs'
import path from 'path'
import { MicrosoftRewardsBot } from '../index' import { MicrosoftRewardsBot } from '../index'
@@ -16,11 +14,20 @@ export default class BrowserFunc {
this.bot = bot this.bot = bot
} }
async goHome(page: Page): Promise<boolean> {
/**
* Navigate the provided page to rewards homepage
* @param {Page} page Puppeteer page
*/
async goHome(page: Page) {
try { try {
const dashboardURL = new URL(this.bot.config.baseURL) const dashboardURL = new URL(this.bot.config.baseURL)
if (page.url() === dashboardURL.href) {
return
}
await page.goto(this.bot.config.baseURL) await page.goto(this.bot.config.baseURL)
const maxIterations = 5 // Maximum iterations set to 5 const maxIterations = 5 // Maximum iterations set to 5
@@ -30,7 +37,7 @@ export default class BrowserFunc {
await this.bot.browser.utils.tryDismissCookieBanner(page) await this.bot.browser.utils.tryDismissCookieBanner(page)
// Check if account is suspended // Check if account is suspended
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (isSuspended) { if (isSuspended) {
this.bot.log('GO-HOME', 'This account is suspended!', 'error') this.bot.log('GO-HOME', 'This account is suspended!', 'error')
throw new Error('Account has been suspended!') throw new Error('Account has been suspended!')
@@ -39,12 +46,14 @@ export default class BrowserFunc {
try { try {
// If activities are found, exit the loop // If activities are found, exit the loop
await page.waitForSelector('#more-activities', { timeout: 1000 }) await page.waitForSelector('#more-activities', { timeout: 1000 })
this.bot.log('GO-HOME', 'Visited homepage successfully')
break break
} catch (error) { } catch (error) {
// Continue if element is not found // Continue if element is not found
} }
// Below runs if the homepage was unable to be visited
const currentURL = new URL(page.url()) const currentURL = new URL(page.url())
if (currentURL.hostname !== dashboardURL.hostname) { if (currentURL.hostname !== dashboardURL.hostname) {
@@ -52,34 +61,37 @@ export default class BrowserFunc {
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
await page.goto(this.bot.config.baseURL) await page.goto(this.bot.config.baseURL)
} else {
this.bot.log('GO-HOME', 'Visited homepage successfully')
break
} }
await this.bot.utils.wait(5000) await this.bot.utils.wait(5000)
this.bot.log('GO-HOME', 'Visited homepage successfully')
} }
} catch (error) { } catch (error) {
console.error('An error occurred:', error) throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error')
return false
} }
return true
} }
async getDashboardData(page: Page): Promise<DashboardData> { /**
* Fetch user dashboard data
* @returns {DashboardData} Object of user bing rewards dashboard data
*/
async getDashboardData(): Promise<DashboardData> {
const dashboardURL = new URL(this.bot.config.baseURL) const dashboardURL = new URL(this.bot.config.baseURL)
const currentURL = new URL(page.url()) const currentURL = new URL(this.bot.homePage.url())
// Should never happen since tasks are opened in a new tab! // Should never happen since tasks are opened in a new tab!
if (currentURL.hostname !== dashboardURL.hostname) { if (currentURL.hostname !== dashboardURL.hostname) {
this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page') this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
await this.goHome(page) await this.goHome(this.bot.homePage)
} }
// Reload the page to get new data // Reload the page to get new data
await page.reload({ waitUntil: 'networkidle2' }) await this.bot.homePage.reload({ waitUntil: 'networkidle2' })
const scriptContent = await page.evaluate(() => { const scriptContent = await this.bot.homePage.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script')) const scripts = Array.from(document.querySelectorAll('script'))
const targetScript = scripts.find(script => script.innerText.includes('var dashboard')) const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
@@ -91,7 +103,7 @@ export default class BrowserFunc {
}) })
// Extract the dashboard object from the script content // Extract the dashboard object from the script content
const dashboardData = await page.evaluate(scriptContent => { const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
// Extract the dashboard object using regex // Extract the dashboard object using regex
const regex = /var dashboard = (\{.*?\});/s const regex = /var dashboard = (\{.*?\});/s
const match = regex.exec(scriptContent) const match = regex.exec(scriptContent)
@@ -106,47 +118,23 @@ export default class BrowserFunc {
return dashboardData return dashboardData
} }
async getQuizData(page: Page): Promise<QuizData> { /**
try { * Get search point counters
const html = await page.content() * @returns {Counters} Object of search counter data
const $ = load(html) */
async getSearchPoints(): Promise<Counters> {
const scriptContent = $('script').filter((index, element) => { const dashboardData = await this.getDashboardData() // Always fetch newest data
return $(element).text().includes('_w.rewardsQuizRenderInfo')
}).text()
if (scriptContent) {
const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
const match = regex.exec(scriptContent)
if (match && match[1]) {
const quizData = JSON.parse(match[1])
return quizData
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
}
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
}
} catch (error) {
throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
}
}
async getSearchPoints(page: Page): Promise<Counters> {
const dashboardData = await this.getDashboardData(page) // Always fetch newest data
return dashboardData.userStatus.counters return dashboardData.userStatus.counters
} }
async getEarnablePoints(data: DashboardData, page: null | Page = null): Promise<number> { /**
* Get total earnable points
* @returns {number} Total earnable points
*/
async getEarnablePoints(): Promise<number> {
try { try {
// Fetch new data if page is provided const data = await this.getDashboardData()
if (page) {
data = await this.getDashboardData(page)
}
// These only include the points from tasks that the script can complete! // These only include the points from tasks that the script can complete!
let totalEarnablePoints = 0 let totalEarnablePoints = 0
@@ -176,12 +164,13 @@ export default class BrowserFunc {
} }
} }
async getCurrentPoints(data: DashboardData, page: null | Page = null): Promise<number> { /**
* Get current point amount
* @returns {number} Current total point amount
*/
async getCurrentPoints(): Promise<number> {
try { try {
// Fetch new data if page is provided const data = await this.getDashboardData()
if (page) {
data = await this.getDashboardData(page)
}
return data.userStatus.availablePoints return data.userStatus.availablePoints
} catch (error) { } catch (error) {
@@ -189,24 +178,43 @@ export default class BrowserFunc {
} }
} }
async loadSesion(email: string): Promise<string> { /**
const sessionDir = path.join(__dirname, this.bot.config.sessionPath, email) * Parse quiz data from provided page
* @param {Page} page Puppeteer page
* @returns {QuizData} Quiz data object
*/
async getQuizData(page: Page): Promise<QuizData> {
try { try {
// Create session dir const html = await page.content()
if (!fs.existsSync(sessionDir)) { const $ = load(html)
await fs.promises.mkdir(sessionDir, { recursive: true })
const scriptContent = $('script').filter((index, element) => {
return $(element).text().includes('_w.rewardsQuizRenderInfo')
}).text()
if (scriptContent) {
const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
const match = regex.exec(scriptContent)
if (match && match[1]) {
const quizData = JSON.parse(match[1])
return quizData
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
}
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
} }
return sessionDir
} catch (error) { } catch (error) {
throw new Error(error as string) throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
} }
} }
async waitForQuizRefresh(page: Page): Promise<boolean> { async waitForQuizRefresh(page: Page): Promise<boolean> {
try { try {
await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 10_000 }) await page.waitForSelector('span.rqMCredits', { visible: true, timeout: 10_000 })
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
return true return true

View File

@@ -87,4 +87,38 @@ export default class BrowserUtil {
} }
} }
async getTabs(page: Page) {
try {
const browser = page.browser()
const pages = await browser.pages()
const homeTab = pages[1]
let homeTabURL: URL
if (!homeTab) {
throw this.bot.log('GET-TABS', 'Home tab could not be found!', 'error')
} else {
homeTabURL = new URL(homeTab.url())
if (homeTabURL.hostname !== 'rewards.bing.com') {
throw this.bot.log('GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
}
}
const workerTab = pages[2]
if (!workerTab) {
throw this.bot.log('GET-TABS', 'Worker tab could not be found!', 'error')
}
return {
homeTab: homeTab,
workerTab: workerTab
}
} catch (error) {
throw this.bot.log('GET-TABS', 'An error occurred:' + error, 'error')
}
}
} }

View File

@@ -19,9 +19,9 @@ export default class Activities {
this.bot = bot this.bot = bot
} }
doSearch = async (page: Page, data: DashboardData, mobile: boolean): Promise<void> => { doSearch = async (page: Page, data: DashboardData): Promise<void> => {
const search = new Search(this.bot) const search = new Search(this.bot)
await search.doSearch(page, data, mobile) await search.doSearch(page, data)
} }
doABC = async (page: Page): Promise<void> => { doABC = async (page: Page): Promise<void> => {

View File

@@ -20,11 +20,12 @@ export class Login {
try { try {
// Navigate to the Bing login page // Navigate to the Bing login page
await page.goto('https://login.live.com/') await page.goto('https://rewards.bing.com/signin')
const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 }).then(() => true).catch(() => false) const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
if (!isLoggedIn) { if (!isLoggedIn) {
// Check if account is locked
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false) const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false)
if (isLocked) { if (isLocked) {
this.bot.log('LOGIN', 'This account has been locked!', 'error') this.bot.log('LOGIN', 'This account has been locked!', 'error')
@@ -64,9 +65,11 @@ export class Login {
await page.type('#i0118', password) await page.type('#i0118', password)
await page.click('#idSIButton9') await page.click('#idSIButton9')
// When erroring at this stage it means a 2FA code is required
} catch (error) { } catch (error) {
this.bot.log('LOGIN', '2FA code required') this.bot.log('LOGIN', '2FA code required')
// Wait for user input
const code = await new Promise<string>((resolve) => { const code = await new Promise<string>((resolve) => {
rl.question('Enter 2FA code:\n', (input) => { rl.question('Enter 2FA code:\n', (input) => {
rl.close() rl.close()
@@ -83,13 +86,13 @@ export class Login {
const currentURL = new URL(page.url()) const currentURL = new URL(page.url())
while (currentURL.pathname !== '/' || currentURL.hostname !== 'account.microsoft.com') { while (currentURL.pathname !== '/' || currentURL.hostname !== 'rewards.bing.com') {
await this.bot.browser.utils.tryDismissAllMessages(page) await this.bot.browser.utils.tryDismissAllMessages(page)
currentURL.href = page.url() currentURL.href = page.url()
} }
// Wait for login to complete // Wait for login to complete
await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 }) await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
} }
private async checkBingLogin(page: Page): Promise<void> { private async checkBingLogin(page: Page): Promise<void> {
@@ -103,11 +106,11 @@ export class Login {
const currentUrl = new URL(page.url()) const currentUrl = new URL(page.url())
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') { if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
await this.bot.utils.wait(3000)
await this.bot.browser.utils.tryDismissBingCookieBanner(page) await this.bot.browser.utils.tryDismissBingCookieBanner(page)
const loggedIn = await this.checkBingLoginStatus(page) const loggedIn = await this.checkBingLoginStatus(page)
if (loggedIn) { // If mobile browser, skip this step
if (loggedIn || this.bot.isMobile) {
this.bot.log('LOGIN-BING', 'Bing login verification passed!') this.bot.log('LOGIN-BING', 'Bing login verification passed!')
break break
} }
@@ -123,7 +126,7 @@ export class Login {
private async checkBingLoginStatus(page: Page): Promise<boolean> { private async checkBingLoginStatus(page: Page): Promise<boolean> {
try { try {
await page.waitForSelector('#id_n', { timeout: 10_000 }) await page.waitForSelector('#id_n', { timeout: 5000 })
return true return true
} catch (error) { } catch (error) {
return false return false

View File

@@ -27,6 +27,11 @@ export class Workers {
await this.solveActivities(page, activitiesUncompleted) await this.solveActivities(page, activitiesUncompleted)
page = await this.bot.browser.utils.getLatestTab(page)
// 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('DAILY-SET', 'All "Daily Set" items have been completed')
} }
@@ -41,21 +46,31 @@ export class Workers {
} }
for (const punchCard of punchCardsUncompleted) { for (const punchCard of punchCardsUncompleted) {
// 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 const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
// Solve Activities // Solve Activities
this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`) this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
const browser = page.browser()
page = await browser.newPage()
// Got to punch card index page in a new tab // Got to punch card index page in a new tab
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL }) await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
// Wait for new page to load, max 10 seconds, however try regardless in case of error
await page.waitForNetworkIdle({ timeout: 5_000 }).catch(() => { })
await this.solveActivities(page, activitiesUncompleted, punchCard) await this.solveActivities(page, activitiesUncompleted, punchCard)
// Close the punch card index page page = await this.bot.browser.utils.getLatestTab(page)
await page.close()
const pages = await (page.browser()).pages()
if (pages.length > 3) {
await page.close()
} else {
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('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
} }
@@ -82,45 +97,63 @@ export class Workers {
// Solve Activities // Solve Activities
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item') this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
page = await this.bot.browser.utils.getLatestTab(page)
await this.solveActivities(page, activitiesUncompleted) await this.solveActivities(page, activitiesUncompleted)
page = await this.bot.browser.utils.getLatestTab(page)
// 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('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
} }
// Solve all the different types of activities // Solve all the different types of activities
private async solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) { private async solveActivities(activityPage: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
const activityInitial = activityPage.url() // Homepage for Daily/More and Index for promotions
for (const activity of activities) { for (const activity of activities) {
try { try {
// Reselect the worker page
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
const pages = await activityPage.browser().pages()
if (pages.length > 3) {
await activityPage.close()
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
}
await this.bot.utils.wait(1000)
if (activityPage.url() !== activityInitial) {
await activityPage.goto(activityInitial)
}
let selector = `[data-bi-id="${activity.offerId}"]` let selector = `[data-bi-id="${activity.offerId}"]`
if (punchCard) { if (punchCard) {
selector = await this.bot.browser.func.getPunchCardActivity(page, activity) selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
} else if (activity.name.toLowerCase().includes('membercenter')) { } else if (activity.name.toLowerCase().includes('membercenter')) {
selector = `[data-bi-id="${activity.name}"]`
// Promotion
if (activity.priority === 1) {
selector = '#promo-item'
} else {
selector = `[data-bi-id="${activity.name}"]`
}
} }
// Wait for element to load
await page.waitForSelector(selector, { timeout: 10_000 })
// Click element, it will be opened in a new tab // Click element, it will be opened in a new tab
await page.click(selector) await activityPage.click(selector)
// Cooldown
await this.bot.utils.wait(4000)
// Select the new activity page // Select the new activity page
const activityPage = await this.bot.browser.utils.getLatestTab(page) activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
// Wait for body to load // Wait for the new tab to fully load, ignore error.
await activityPage.waitForSelector('body', { timeout: 10_000 }) /*
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.waitForNetworkIdle({ timeout: 10_000 }).catch(() => { })
await this.bot.utils.wait(5000)
switch (activity.promotionType) { switch (activity.promotionType) {
// Quiz (Poll, Quiz or ABC) // Quiz (Poll, Quiz or ABC)
@@ -167,9 +200,11 @@ export class Workers {
// Cooldown // Cooldown
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
} catch (error) { } catch (error) {
this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error') this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error')
} }
} }
} }

View File

@@ -11,7 +11,7 @@ export class Poll extends Workers {
try { try {
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}` const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 }) await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
await page.click(buttonId) await page.click(buttonId)

View File

@@ -10,7 +10,7 @@ export class Quiz extends Workers {
try { try {
// Check if the quiz has been started or not // Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) { if (quizNotStarted) {
await page.click('#rqStartQuiz') await page.click('#rqStartQuiz')
} else { } else {
@@ -52,7 +52,7 @@ export class Quiz extends Workers {
} }
} }
// Other type quiz // Other type quiz, lightspeed
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) { } else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data
const correctOption = quizData.correctAnswer const correctOption = quizData.correctAnswer

View File

@@ -3,7 +3,7 @@ import axios from 'axios'
import { Workers } from '../Workers' import { Workers } from '../Workers'
import { DashboardData, DashboardImpression } from '../../interface/DashboardData' import { Counters, DashboardData } from '../../interface/DashboardData'
import { GoogleTrends } from '../../interface/GoogleDailyTrends' import { GoogleTrends } from '../../interface/GoogleDailyTrends'
import { GoogleSearch } from '../../interface/Search' import { GoogleSearch } from '../../interface/Search'
@@ -12,19 +12,16 @@ export class Search extends Workers {
private searchPageURL = 'https://bing.com' private searchPageURL = 'https://bing.com'
public async doSearch(page: Page, data: DashboardData, mobile: boolean) { public async doSearch(page: Page, data: DashboardData) {
this.bot.log('SEARCH-BING', 'Starting bing searches') this.bot.log('SEARCH-BING', 'Starting bing searches')
const mobileData = data.userStatus.counters?.mobileSearch ? data.userStatus.counters.mobileSearch[0] : null // Mobile searches page = await this.bot.browser.utils.getLatestTab(page)
const edgeData = data.userStatus.counters.pcSearch[1] as DashboardImpression // Edge searches
const genericData = data.userStatus.counters.pcSearch[0] as DashboardImpression // Normal searches
let missingPoints = (mobile && mobileData) ? let searchCounters: Counters = await this.bot.browser.func.getSearchPoints()
(mobileData.pointProgressMax - mobileData.pointProgress) : let missingPoints = this.calculatePoints(searchCounters)
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
if (missingPoints == 0) { if (missingPoints === 0) {
this.bot.log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`) this.bot.log('SEARCH-BING', `Bing searches for ${this.bot.isMobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
return return
} }
@@ -35,33 +32,23 @@ export class Search extends Workers {
// Deduplicate the search terms // Deduplicate the search terms
googleSearchQueries = [...new Set(googleSearchQueries)] googleSearchQueries = [...new Set(googleSearchQueries)]
// Open a new tab
const browser = page.browser()
const searchPage = await browser.newPage()
// Go to bing // Go to bing
await searchPage.goto(this.searchPageURL) await page.goto(this.searchPageURL)
let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search
const queries: string[] = [] const queries: string[] = []
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related)) // Mobile search doesn't seem to like related queries?
googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : queries.push(x.topic, ...x.related) })
// Loop over Google search queries // Loop over Google search queries
for (let i = 0; i < queries.length; i++) { for (let i = 0; i < queries.length; i++) {
const query = queries[i] as string const query = queries[i] as string
this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${mobile}`) this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${this.bot.isMobile}`)
const newData = await this.bingSearch(page, searchPage, query) searchCounters = await this.bingSearch(page, query)
const newMissingPoints = this.calculatePoints(searchCounters)
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = (mobile && newMobileData) ?
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
// If the new point amount is the same as before // If the new point amount is the same as before
if (newMissingPoints == missingPoints) { if (newMissingPoints == missingPoints) {
@@ -72,7 +59,7 @@ export class Search extends Workers {
missingPoints = newMissingPoints missingPoints = newMissingPoints
if (missingPoints == 0) { if (missingPoints === 0) {
break break
} }
@@ -97,16 +84,10 @@ export class Search extends Workers {
if (relatedTerms.length > 3) { if (relatedTerms.length > 3) {
// Search for the first 2 related terms // Search for the first 2 related terms
for (const term of relatedTerms.slice(1, 3)) { for (const term of relatedTerms.slice(1, 3)) {
this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`) this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${this.bot.isMobile}`)
const newData = await this.bingSearch(page, searchPage, query.topic)
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches searchCounters = await this.bingSearch(page, query.topic)
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches const newMissingPoints = this.calculatePoints(searchCounters)
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = (mobile && newMobileData) ?
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
// If the new point amount is the same as before // If the new point amount is the same as before
if (newMissingPoints == missingPoints) { if (newMissingPoints == missingPoints) {
@@ -118,7 +99,7 @@ export class Search extends Workers {
missingPoints = newMissingPoints missingPoints = newMissingPoints
// If we satisfied the searches // If we satisfied the searches
if (missingPoints == 0) { if (missingPoints === 0) {
break break
} }
@@ -135,7 +116,7 @@ export class Search extends Workers {
this.bot.log('SEARCH-BING', 'Completed searches') this.bot.log('SEARCH-BING', 'Completed searches')
} }
private async bingSearch(page: Page, searchPage: Page, query: string) { private async bingSearch(searchPage: Page, query: string) {
// Try a max of 5 times // Try a max of 5 times
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
try { try {
@@ -162,7 +143,7 @@ export class Search extends Workers {
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max))) await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
return await this.bot.browser.func.getSearchPoints(page) return await this.bot.browser.func.getSearchPoints()
} catch (error) { } catch (error) {
if (i === 5) { if (i === 5) {
@@ -173,12 +154,28 @@ export class Search extends Workers {
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error') this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn') this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
// Reset the tabs
const browser = searchPage.browser()
const tabs = await browser.pages()
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
if (tabs.length === 4) {
await lastTab.close()
} else if (tabs.length === 2) {
const newPage = await browser.newPage()
await newPage.goto(this.searchPageURL)
} else {
await lastTab.goBack()
}
await this.bot.utils.wait(4000) await this.bot.utils.wait(4000)
} }
} }
this.bot.log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error') this.bot.log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
return await this.bot.browser.func.getSearchPoints(page) return await this.bot.browser.func.getSearchPoints()
} }
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> { private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
@@ -211,7 +208,7 @@ export class Search extends Workers {
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) { for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
queryTerms.push({ queryTerms.push({
topic: topic.title.query.toLowerCase(), topic: topic.title.query.toLowerCase(),
related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase()) related: topic.relatedQueries.map(x => x.query.toLowerCase())
}) })
} }
@@ -253,9 +250,10 @@ export class Search extends Workers {
private async randomScroll(page: Page) { private async randomScroll(page: Page) {
try { try {
// Press the arrow down key to scroll // Press the arrow down key to scroll
for (let i = 0; i < this.bot.utils.randomNumber(5, 100); i++) { for (let i = 0; i < this.bot.utils.randomNumber(5, 400); i++) {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
} }
} catch (error) { } catch (error) {
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error') this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
} }
@@ -267,68 +265,52 @@ export class Search extends Workers {
await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not
// Wait for website to load
await this.bot.utils.wait(3000)
// Will get current tab if no new one is created // Will get current tab if no new one is created
let lastTab = await this.bot.browser.utils.getLatestTab(page) let lastTab = await this.bot.browser.utils.getLatestTab(page)
// Wait for the body of the new page to be loaded // Let website load, if it doesn't load within 5 sec. exit regardless
await lastTab.waitForSelector('body', { timeout: 10_000 }).catch(() => { }) await this.bot.utils.wait(5000)
// Check if the tab is closed or not let lastTabURL = new URL(lastTab.url()) // Get new tab info
if (!lastTab.isClosed()) {
let lastTabURL = new URL(lastTab.url()) // Get new tab info
// Check if the URL is different from the original one, don't loop more than 5 times. // Check if the URL is different from the original one, don't loop more than 5 times.
let i = 0 let i = 0
while (lastTabURL.href !== searchListingURL.href && i < 5) { while (lastTabURL.href !== searchListingURL.href && i < 5) {
// If hostname is still bing, (Bing images/news etc) const browser = page.browser()
if (lastTabURL.hostname == searchListingURL.hostname) { const tabs = await browser.pages()
await lastTab.goBack()
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab if (tabs.length === 4) {
await lastTab.close()
// If "goBack" didn't return to search listing (due to redirects) } else if (tabs.length === 2) {
if (lastTabURL.hostname !== searchListingURL.hostname) { const newPage = await browser.newPage()
await lastTab.goto(this.searchPageURL) await newPage.goto(searchListingURL.href)
}
} else { // No longer on bing, likely opened a new tab, close this tab } else {
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab await lastTab.goBack()
lastTabURL = new URL(lastTab.url())
const tabs = await (page.browser()).pages() // Get all tabs open
// If the browser has more than 3 tabs open, it has opened a new one, we need to close this one.
if (tabs.length > 3) {
// Make sure the page is still open!
if (!lastTab.isClosed()) {
await lastTab.close()
}
} else if (lastTabURL.href !== searchListingURL.href) {
await lastTab.goBack()
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
lastTabURL = new URL(lastTab.url())
// If "goBack" didn't return to search listing (due to redirects)
if (lastTabURL.hostname !== searchListingURL.hostname) {
await lastTab.goto(this.searchPageURL)
}
}
}
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
i++
} }
// End of loop, refresh lastPage
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
lastTabURL = new URL(lastTab.url()) // Get new tab info
i++
} }
} catch (error) { } catch (error) {
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error') this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
} }
} }
private calculatePoints(counters: Counters) {
const mobileData = counters.mobileSearch?.[0] // Mobile searches
const genericData = counters.pcSearch?.[0] // Normal searches
const edgeData = counters.pcSearch?.[1] // Edge searches
const missingPoints = (this.bot.isMobile && mobileData)
? mobileData.pointProgressMax - mobileData.pointProgress
: (edgeData ? edgeData.pointProgressMax - edgeData.pointProgress : 0)
+ (genericData ? genericData.pointProgressMax - genericData.pointProgress : 0)
return missingPoints
}
} }

View File

@@ -8,10 +8,10 @@ export class ThisOrThat extends Workers {
async doThisOrThat(page: Page) { async doThisOrThat(page: Page) {
this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat') this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
return
try { try {
// Check if the quiz has been started or not // Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) { if (quizNotStarted) {
await page.click('#rqStartQuiz') await page.click('#rqStartQuiz')
} else { } else {
@@ -22,9 +22,22 @@ export class ThisOrThat extends Workers {
// Solving // Solving
const quizData = await this.bot.browser.func.getQuizData(page) const quizData = await this.bot.browser.func.getQuizData(page)
quizData // correctAnswer property is always null? const questionsRemaining = quizData.maxQuestions - (quizData.currentQuestionNumber - 1) // Amount of questions remaining
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrthat successfully') for (let question = 0; question < questionsRemaining; question++) {
// Since there's no solving logic yet, randomly guess to complete
const buttonId = `#rqAnswerOption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
await page.click(buttonId)
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
if (!refreshSuccess) {
await page.close()
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return
}
}
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrThat successfully')
} catch (error) { } catch (error) {
await page.close() await page.close()
this.bot.log('THIS-OR-THAT', 'An error occurred:' + error, 'error') this.bot.log('THIS-OR-THAT', 'An error occurred:' + error, 'error')

View File

@@ -9,7 +9,8 @@ export class UrlReward extends Workers {
this.bot.log('URL-REWARD', 'Trying to complete UrlReward') this.bot.log('URL-REWARD', 'Trying to complete UrlReward')
try { try {
// After waiting, close the page this.bot.utils.wait(2000)
await page.close() await page.close()
this.bot.log('URL-REWARD', 'Completed the UrlReward successfully') this.bot.log('URL-REWARD', 'Completed the UrlReward successfully')

View File

@@ -1,4 +1,5 @@
import cluster from 'cluster' import cluster from 'cluster'
import { Page } from 'puppeteer'
import Browser from './browser/Browser' import Browser from './browser/Browser'
import BrowserFunc from './browser/BrowserFunc' import BrowserFunc from './browser/BrowserFunc'
@@ -24,6 +25,8 @@ export class MicrosoftRewardsBot {
func: BrowserFunc, func: BrowserFunc,
utils: BrowserUtil utils: BrowserUtil
} }
public isMobile: boolean = false
public homePage!: Page
private collectedPoints: number = 0 private collectedPoints: number = 0
private activeWorkers: number private activeWorkers: number
@@ -122,8 +125,8 @@ export class MicrosoftRewardsBot {
// Desktop // Desktop
async Desktop(account: Account) { async Desktop(account: Account) {
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, false) const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
const page = await browser.newPage() this.homePage = await browser.newPage()
let pages = await browser.pages() let pages = await browser.pages()
// If for some reason the browser initializes with more than 2 pages, close these // If for some reason the browser initializes with more than 2 pages, close these
@@ -133,51 +136,53 @@ export class MicrosoftRewardsBot {
} }
// Log into proxy // Log into proxy
await page.authenticate({ username: account.proxy.username, password: account.proxy.password }) await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
log('MAIN', 'Starting DESKTOP browser') log('MAIN', 'Starting DESKTOP browser')
// Login into MS Rewards // Login into MS Rewards, then go to rewards homepage
await this.login.login(page, account.email, account.password) await this.login.login(this.homePage, account.email, account.password)
await this.browser.func.goHome(this.homePage)
const wentHome = await this.browser.func.goHome(page) const data = await this.browser.func.getDashboardData()
if (!wentHome) {
throw log('MAIN', 'Unable to get dashboard page', 'error')
}
const data = await this.browser.func.getDashboardData(page)
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`) log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
const earnablePoints = await this.browser.func.getEarnablePoints(data) const earnablePoints = await this.browser.func.getEarnablePoints()
this.collectedPoints = earnablePoints this.collectedPoints = earnablePoints
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`) log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
// If runOnZeroPoints is false and 0 points to earn, don't continue // If runOnZeroPoints is false and 0 points to earn, don't continue
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) { if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping') log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!')
// Close desktop browser // Close desktop browser
return await browser.close() return await browser.close()
} }
// 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)
// Complete daily set // Complete daily set
if (this.config.workers.doDailySet) { if (this.config.workers.doDailySet) {
await this.workers.doDailySet(page, data) await this.workers.doDailySet(workerPage, data)
} }
// Complete more promotions // Complete more promotions
if (this.config.workers.doMorePromotions) { if (this.config.workers.doMorePromotions) {
await this.workers.doMorePromotions(page, data) await this.workers.doMorePromotions(workerPage, data)
} }
// Complete punch cards // Complete punch cards
if (this.config.workers.doPunchCards) { if (this.config.workers.doPunchCards) {
await this.workers.doPunchCard(page, data) await this.workers.doPunchCard(workerPage, data)
} }
// Do desktop searches // Do desktop searches
if (this.config.workers.doDesktopSearch) { if (this.config.workers.doDesktopSearch) {
await this.activities.doSearch(page, data, false) await this.activities.doSearch(workerPage, data)
} }
// Close desktop browser // Close desktop browser
@@ -186,8 +191,10 @@ export class MicrosoftRewardsBot {
// Mobile // Mobile
async Mobile(account: Account) { async Mobile(account: Account) {
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, true) this.isMobile = true
const page = await browser.newPage()
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
this.homePage = await browser.newPage()
let pages = await browser.pages() let pages = await browser.pages()
// If for some reason the browser initializes with more than 2 pages, close these // If for some reason the browser initializes with more than 2 pages, close these
@@ -196,32 +203,51 @@ export class MicrosoftRewardsBot {
pages = await browser.pages() pages = await browser.pages()
} }
// Log into proxy // Log into proxy
await page.authenticate({ username: account.proxy.username, password: account.proxy.password }) await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
log('MAIN', 'Starting MOBILE browser') log('MAIN', 'Starting MOBILE browser')
// Login into MS Rewards // Login into MS Rewards, then go to rewards homepage
await this.login.login(page, account.email, account.password) await this.login.login(this.homePage, account.email, account.password)
await this.browser.func.goHome(this.homePage)
await this.browser.func.goHome(page) const data = await this.browser.func.getDashboardData()
const data = await this.browser.func.getDashboardData(page)
// If no mobile searches data found, stop (Does not exist on new accounts) // If no mobile searches data found, stop (Does not exist on new accounts)
if (!data.userStatus.counters.mobileSearch) { if (!data.userStatus.counters.mobileSearch) {
log('MAIN', 'No mobile searches found, stopping') log('MAIN', 'No mobile searches found, stopping!')
// Close mobile browser // Close mobile browser
return await browser.close() return await browser.close()
} }
// 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)
// Do mobile searches // Do mobile searches
if (this.config.workers.doMobileSearch) { if (this.config.workers.doMobileSearch) {
await this.activities.doSearch(page, data, true) 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
if (mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
log('MAIN', 'Unable to complete mobile searches, bad User-Agent?, retrying...')
// Close mobile browser
await browser.close()
// Retry
await this.Mobile(account)
}
} }
// Fetch new points // Fetch new points
const earnablePoints = await this.browser.func.getEarnablePoints(data, page) const earnablePoints = await this.browser.func.getEarnablePoints()
// If the new earnable is 0, means we got all the points, else retract // If the new earnable is 0, means we got all the points, else retract
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints) this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)

View File

@@ -33,3 +33,19 @@ export function loadConfig(): Config {
throw new Error(error as string) throw new Error(error as string)
} }
} }
export async function loadSesion(sessionPath: string, email: string): Promise<string> {
try {
// Fetch path
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
// Create session dir
if (!fs.existsSync(sessionDir)) {
await fs.promises.mkdir(sessionDir, { recursive: true })
}
return sessionDir
} catch (error) {
throw new Error(error as string)
}
}

View File

@@ -1,7 +1,7 @@
import { Webhook } from './Webhook' import { Webhook } from './Webhook'
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') { export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
const currentTime = new Date().toISOString() const currentTime = new Date().toLocaleString()
let str = '' let str = ''