- Switched from Puppeteer to Playwright
- Fixed mobile searches not working
- Added fingerprint saving in config
- Added mobile search retry in config
This commit is contained in:
TheNetsky
2024-01-07 16:33:48 +01:00
parent f3fb641ecd
commit 9ea7f5c452
19 changed files with 199 additions and 142 deletions

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { MicrosoftRewardsBot } from '../index'

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import readline from 'readline'
import { MicrosoftRewardsBot } from '../index'
@@ -26,13 +26,13 @@ export class Login {
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', { state: 'visible', timeout: 10_000 }).then(() => true).catch(() => false)
if (isLocked) {
this.bot.log('LOGIN', 'This account has been locked!', 'error')
throw new Error('Account has been locked!')
}
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
await page.waitForSelector('#loginHeader', { state: 'visible', timeout: 10_000 })
await this.execLogin(page, email, password)
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
@@ -59,7 +59,7 @@ export class Login {
this.bot.log('LOGIN', 'Email entered successfully')
try {
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
await this.bot.utils.wait(2000)
await page.type('#i0118', password)

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
@@ -58,13 +58,13 @@ export class Workers {
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 page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => { })
await this.solveActivities(page, activitiesUncompleted, punchCard)
page = await this.bot.browser.utils.getLatestTab(page)
const pages = await (page.browser()).pages()
const pages = page.context().pages()
if (pages.length > 3) {
await page.close()
@@ -118,7 +118,7 @@ export class Workers {
// Reselect the worker page
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
const pages = await activityPage.browser().pages()
const pages = activityPage.context().pages()
if (pages.length > 3) {
await activityPage.close()
@@ -152,7 +152,7 @@ export class Workers {
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 activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
await this.bot.utils.wait(5000)
switch (activity.promotionType) {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -15,18 +15,18 @@ export class ABC extends Workers {
const maxIterations = 15
let i
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 10_000 })
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10_000 })
const answers = $('.wk_OptionClickClass')
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 10_000 })
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10_000 })
await this.bot.utils.wait(2000)
await page.click(`#${answer}`) // Click answer
await this.bot.utils.wait(4000)
await page.waitForSelector('div.wk_button', { visible: true, timeout: 10_000 })
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10_000 })
await page.click('div.wk_button') // Click next question button
page = await this.bot.browser.utils.getLatestTab(page)

View File

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

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -10,7 +10,7 @@ export class Quiz extends Workers {
try {
// Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
@@ -29,7 +29,7 @@ export class Quiz extends Workers {
const answers: string[] = []
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
@@ -39,7 +39,7 @@ export class Quiz extends Workers {
// Click the answers
for (const answer of answers) {
await page.waitForSelector(answer, { visible: true, timeout: 2000 })
await page.waitForSelector(answer, { state: 'visible', timeout: 2000 })
// Click the answer on page
await page.click(answer)
@@ -59,7 +59,7 @@ export class Quiz extends Workers {
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import axios from 'axios'
import { Workers } from '../Workers'
@@ -63,6 +63,12 @@ export class Search extends Workers {
break
}
// Only for mobile searches
if (maxLoop > 3 && this.bot.isMobile) {
this.bot.log('SEARCH-BING-MOBILE', 'Search didn\'t gain point for 3 iterations, likely bad User-Agent', 'warn')
break
}
// If we didn't gain points for 10 iterations, assume it's stuck
if (maxLoop > 10) {
this.bot.log('SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn')
@@ -71,6 +77,11 @@ export class Search extends Workers {
}
}
// Only for mobile searches
if (missingPoints > 0 && this.bot.isMobile) {
return
}
// If we still got remaining search queries, generate extra ones
if (missingPoints > 0) {
this.bot.log('SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`)
@@ -121,7 +132,7 @@ export class Search extends Workers {
for (let i = 0; i < 5; i++) {
try {
const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
await searchPage.click(searchBar) // Focus on the textarea
await this.bot.utils.wait(500)
await searchPage.keyboard.down('Control')
@@ -155,20 +166,8 @@ export class Search extends Workers {
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.closeTabs(lastTab, this.searchPageURL)
await this.bot.utils.wait(4000)
}
@@ -276,19 +275,8 @@ export class Search extends Workers {
// Check if the URL is different from the original one, don't loop more than 5 times.
let i = 0
while (lastTabURL.href !== searchListingURL.href && i < 5) {
const browser = page.browser()
const tabs = await browser.pages()
if (tabs.length === 4) {
await lastTab.close()
} else if (tabs.length === 2) {
const newPage = await browser.newPage()
await newPage.goto(searchListingURL.href)
} else {
await lastTab.goBack()
}
await this.closeTabs(lastTab, searchListingURL.href)
// End of loop, refresh lastPage
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
@@ -301,6 +289,26 @@ export class Search extends Workers {
}
}
private async closeTabs(lastTab: Page, url: string) {
const browser = lastTab.context()
const tabs = browser.pages()
// If more than 3 tabs are open, close the last tab
if (tabs.length > 2) {
await lastTab.close()
// If only 1 tab is open, open a new one to search in
} else if (tabs.length === 1) {
const newPage = await browser.newPage()
await newPage.goto(url)
// Else go back one page
} else {
await lastTab.goBack()
}
}
private calculatePoints(counters: Counters) {
const mobileData = counters.mobileSearch?.[0] // Mobile searches
const genericData = counters.pcSearch?.[0] // Normal searches

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -11,7 +11,7 @@ export class ThisOrThat extends Workers {
try {
// Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'