- Updated packages
- Cleanup
- Improved message dismissal
- Improved login functionality

With help from @LightZirconite
This commit is contained in:
TheNetsky
2025-04-24 14:50:22 +02:00
parent 325bf65b30
commit 0ddc964878
11 changed files with 96 additions and 41 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "microsoft-rewards-script", "name": "microsoft-rewards-script",
"version": "1.5.1", "version": "1.5.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": {
@@ -35,11 +35,11 @@
"typescript": "^5.5.4" "typescript": "^5.5.4"
}, },
"dependencies": { "dependencies": {
"axios": "^1.7.9", "axios": "^1.8.4",
"chalk": "^4.1.2", "chalk": "^4.1.2",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"fingerprint-generator": "^2.1.62", "fingerprint-generator": "^2.1.66",
"fingerprint-injector": "^2.1.62", "fingerprint-injector": "^2.1.66",
"http-proxy-agent": "^7.0.2", "http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"ms": "^2.1.3", "ms": "^2.1.3",

View File

@@ -46,7 +46,7 @@ class Browser {
const context = await newInjectedContext(browser as any, { fingerprint: fingerprint }) const context = await newInjectedContext(browser as any, { fingerprint: fingerprint })
// Set timeout to preferred amount // Set timeout to preferred amount
context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30_000)) context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30000))
await context.addCookies(sessionData.cookies) await context.addCookies(sessionData.cookies)

View File

@@ -298,7 +298,7 @@ export default class BrowserFunc {
async waitForQuizRefresh(page: Page): Promise<boolean> { async waitForQuizRefresh(page: Page): Promise<boolean> {
try { try {
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 }) await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10000 })
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
return true return true

View File

@@ -13,6 +13,7 @@ export default class BrowserUtil {
async tryDismissAllMessages(page: Page): Promise<boolean> { async tryDismissAllMessages(page: Page): Promise<boolean> {
const buttons = [ const buttons = [
{ selector: 'button[type="submit"]', label: 'Submit Button' },
{ selector: '#acceptButton', label: 'AcceptButton' }, { selector: '#acceptButton', label: 'AcceptButton' },
{ selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' }, { selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' },
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' }, { selector: '#iLandingViewAction', label: 'iLandingViewAction' },
@@ -23,21 +24,25 @@ export default class BrowserUtil {
{ selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' }, { selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' },
{ selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' }, { selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' },
{ selector: '.maybe-later', label: 'Mobile Rewards App Banner' }, { selector: '.maybe-later', label: 'Mobile Rewards App Banner' },
{ selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container' }, { selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container', isXPath: true },
{ selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' }, { selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' },
{ selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' } { selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' }
] ]
const dismissTasks = buttons.map(async (button) => { const dismissTasks = buttons.map(async (button) => {
try { try {
const element = page.locator(button.selector) const element = button.isXPath
? page.locator(`xpath=${button.selector}`)
: page.locator(button.selector)
if (await element.first().isVisible({ timeout: 1000 })) { if (await element.first().isVisible({ timeout: 1000 })) {
await element.first().click({ timeout: 1000 }) await element.first().click({ timeout: 1000 })
await page.waitForTimeout(500)
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`) this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
return true return true
} }
} catch (error) { } catch {
// Ignore errors and continue // Silent fail
} }
return false return false
}) })

View File

@@ -39,7 +39,7 @@ export class Login {
// Check if account is locked // Check if account is locked
await this.checkAccountLocked(page) await this.checkAccountLocked(page)
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }).then(() => true).catch(() => false) const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 }).then(() => true).catch(() => false)
if (!isLoggedIn) { if (!isLoggedIn) {
await this.execLogin(page, email, password) await this.execLogin(page, email, password)
@@ -86,30 +86,80 @@ export class Login {
} }
private async enterEmail(page: Page, email: string) { private async enterEmail(page: Page, email: string) {
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 2_000 }).catch(() => null) const emailInputSelector = 'input[type="email"]'
if (emailPrefilled) {
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
return
}
await page.fill('#i0116', email) try {
await page.click('#idSIButton9') // Wait for email field
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully') const emailField = await page.waitForSelector(emailInputSelector, { state: 'visible', timeout: 2000 }).catch(() => null)
if (!emailField) {
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email field not found', 'warn')
return
}
await this.bot.utils.wait(1000)
// Check if email is prefilled
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 5000 }).catch(() => null)
if (emailPrefilled) {
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
} else {
// Else clear and fill email
await page.fill(emailInputSelector, '')
await this.bot.utils.wait(500)
await page.fill(emailInputSelector, email)
await this.bot.utils.wait(1000)
}
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
if (nextButton) {
await nextButton.click()
await this.bot.utils.wait(2000)
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
} else {
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after email entry', 'warn')
}
} catch (error) {
this.bot.log(this.bot.isMobile, 'LOGIN', `Email entry failed: ${error}`, 'error')
}
} }
private async enterPassword(page: Page, password: string) { private async enterPassword(page: Page, password: string) {
const passwordInputSelector = 'input[type="password"]'
try { try {
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 }) // Wait for password field
await this.bot.utils.wait(2000) const passwordField = await page.waitForSelector(passwordInputSelector, { state: 'visible', timeout: 5000 }).catch(() => null)
await page.fill('#i0118', password) if (!passwordField) {
await page.click('#idSIButton9') this.bot.log(this.bot.isMobile, 'LOGIN', 'Password field not found, possibly 2FA required', 'warn')
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully') await this.handle2FA(page)
} catch { return
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entry failed or 2FA required') }
await this.bot.utils.wait(1000)
// Clear and fill password
await page.fill(passwordInputSelector, '')
await this.bot.utils.wait(500)
await page.fill(passwordInputSelector, password)
await this.bot.utils.wait(1000)
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
if (nextButton) {
await nextButton.click()
await this.bot.utils.wait(2000)
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
} else {
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after password entry', 'warn')
}
} catch (error) {
this.bot.log(this.bot.isMobile, 'LOGIN', `Password entry failed: ${error}`, 'error')
await this.handle2FA(page) await this.handle2FA(page)
} }
} }
private async handle2FA(page: Page) { private async handle2FA(page: Page) {
try { try {
const numberToPress = await this.get2FACode(page) const numberToPress = await this.get2FACode(page)
@@ -138,7 +188,7 @@ export class Login {
while (true) { while (true) {
const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null) const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null)
if (button) { if (button) {
await this.bot.utils.wait(60_000) await this.bot.utils.wait(60000)
await button.click() await button.click()
continue continue
@@ -148,7 +198,7 @@ export class Login {
} }
} }
await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => {}) await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => { })
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 }) const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
return await element.textContent() return await element.textContent()
@@ -162,7 +212,7 @@ export class Login {
this.bot.log(this.bot.isMobile, 'LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`) this.bot.log(this.bot.isMobile, 'LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`)
this.bot.log(this.bot.isMobile, 'LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds') this.bot.log(this.bot.isMobile, 'LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds')
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60_000 }) await page.waitForSelector('#i0281', { state: 'detached', timeout: 60000 })
this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!') this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!')
break break
@@ -203,7 +253,7 @@ export class Login {
} }
// Wait for login to complete // Wait for login to complete
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }) await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 })
this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal') this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal')
} }
@@ -272,7 +322,7 @@ export class Login {
currentUrl = new URL(page.url()) currentUrl = new URL(page.url())
await this.bot.utils.wait(5000) await this.bot.utils.wait(5000)
} }
const body = new URLSearchParams() const body = new URLSearchParams()
body.append('grant_type', 'authorization_code') body.append('grant_type', 'authorization_code')
body.append('client_id', this.clientId) body.append('client_id', this.clientId)

View File

@@ -153,7 +153,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, 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. if it didn't then it gave enough time for the page to load.
*/ */
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { }) await activityPage.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { })
await this.bot.utils.wait(2000) await this.bot.utils.wait(2000)
switch (activity.promotionType) { switch (activity.promotionType) {

View File

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

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, { state: 'visible', timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not await page.waitForSelector(buttonId, { state: 'visible', timeout: 10000 }).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

@@ -29,7 +29,7 @@ export class Quiz extends Workers {
const answers: string[] = [] const answers: string[] = []
for (let i = 0; i < quizData.numberOfOptions; i++) { for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 }) const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption')) const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
if (answerAttribute && answerAttribute.toLowerCase() === 'true') { if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
@@ -59,7 +59,7 @@ export class Quiz extends Workers {
for (let i = 0; i < quizData.numberOfOptions; i++) { for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 }) const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option')) const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) { if (dataOption === correctOption) {

View File

@@ -156,7 +156,7 @@ export class Search extends Workers {
await this.bot.utils.wait(500) await this.bot.utils.wait(500)
const searchBar = '#sb_form_q' const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 }) await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
await searchPage.click(searchBar) // Focus on the textarea await searchPage.click(searchBar) // Focus on the textarea
await this.bot.utils.wait(500) await this.bot.utils.wait(500)
await searchPage.keyboard.down(platformControlKey) await searchPage.keyboard.down(platformControlKey)
@@ -312,7 +312,7 @@ export class Search extends Workers {
await this.closeContinuePopup(page) await this.closeContinuePopup(page)
// Stay for 10 seconds for page to load and "visit" // Stay for 10 seconds for page to load and "visit"
await this.bot.utils.wait(10_000) await this.bot.utils.wait(10000)
// Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click // Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click
let lastTab = await this.bot.browser.utils.getLatestTab(page) let lastTab = await this.bot.browser.utils.getLatestTab(page)

View File

@@ -20,7 +20,7 @@ export class SearchOnBing extends Workers {
const query = await this.getSearchQuery(activity.title) const query = await this.getSearchQuery(activity.title)
const searchBar = '#sb_form_q' const searchBar = '#sb_form_q'
await page.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 }) await page.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
await page.click(searchBar) await page.click(searchBar)
await this.bot.utils.wait(500) await this.bot.utils.wait(500)
await page.keyboard.type(query) await page.keyboard.type(query)