diff --git a/package.json b/package.json index 202381e..2a31b17 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "microsoft-rewards-script", - "version": "1.5.1", + "version": "1.5.2", "description": "Automatically do tasks for Microsoft Rewards but in TS!", "main": "index.js", "engines": { @@ -35,11 +35,11 @@ "typescript": "^5.5.4" }, "dependencies": { - "axios": "^1.7.9", + "axios": "^1.8.4", "chalk": "^4.1.2", "cheerio": "^1.0.0", - "fingerprint-generator": "^2.1.62", - "fingerprint-injector": "^2.1.62", + "fingerprint-generator": "^2.1.66", + "fingerprint-injector": "^2.1.66", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "ms": "^2.1.3", diff --git a/src/browser/Browser.ts b/src/browser/Browser.ts index b6ce1e3..9291b19 100644 --- a/src/browser/Browser.ts +++ b/src/browser/Browser.ts @@ -46,7 +46,7 @@ class Browser { const context = await newInjectedContext(browser as any, { fingerprint: fingerprint }) // 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) diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts index c439448..2b309d9 100644 --- a/src/browser/BrowserFunc.ts +++ b/src/browser/BrowserFunc.ts @@ -298,7 +298,7 @@ export default class BrowserFunc { async waitForQuizRefresh(page: Page): Promise { 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) return true diff --git a/src/browser/BrowserUtil.ts b/src/browser/BrowserUtil.ts index 8a6cbca..3a4386c 100644 --- a/src/browser/BrowserUtil.ts +++ b/src/browser/BrowserUtil.ts @@ -13,6 +13,7 @@ export default class BrowserUtil { async tryDismissAllMessages(page: Page): Promise { const buttons = [ + { selector: 'button[type="submit"]', label: 'Submit Button' }, { selector: '#acceptButton', label: 'AcceptButton' }, { selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' }, { selector: '#iLandingViewAction', label: 'iLandingViewAction' }, @@ -23,21 +24,25 @@ export default class BrowserUtil { { selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' }, { selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' }, { 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: '#reward_pivot_earn', label: 'Reward Coupon Accept' } ] const dismissTasks = buttons.map(async (button) => { 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 })) { await element.first().click({ timeout: 1000 }) + await page.waitForTimeout(500) this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`) return true } - } catch (error) { - // Ignore errors and continue + } catch { + // Silent fail } return false }) diff --git a/src/functions/Login.ts b/src/functions/Login.ts index bf64fc5..7428599 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -39,7 +39,7 @@ export class Login { // Check if account is locked 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) { await this.execLogin(page, email, password) @@ -86,30 +86,80 @@ export class Login { } private async enterEmail(page: Page, email: string) { - const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 2_000 }).catch(() => null) - if (emailPrefilled) { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft') - return - } + const emailInputSelector = 'input[type="email"]' - await page.fill('#i0116', email) - await page.click('#idSIButton9') - this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully') + try { + // Wait for email field + 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) { + const passwordInputSelector = 'input[type="password"]' + try { - await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 }) - await this.bot.utils.wait(2000) - await page.fill('#i0118', password) - await page.click('#idSIButton9') - this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully') - } catch { - this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entry failed or 2FA required') + // Wait for password field + const passwordField = await page.waitForSelector(passwordInputSelector, { state: 'visible', timeout: 5000 }).catch(() => null) + if (!passwordField) { + this.bot.log(this.bot.isMobile, 'LOGIN', 'Password field not found, possibly 2FA required', 'warn') + await this.handle2FA(page) + return + } + + 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) } } + private async handle2FA(page: Page) { try { const numberToPress = await this.get2FACode(page) @@ -138,7 +188,7 @@ export class Login { while (true) { const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null) if (button) { - await this.bot.utils.wait(60_000) + await this.bot.utils.wait(60000) await button.click() 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) const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 }) 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', '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!') break @@ -203,7 +253,7 @@ export class Login { } // 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') } @@ -272,7 +322,7 @@ export class Login { currentUrl = new URL(page.url()) await this.bot.utils.wait(5000) } - + const body = new URLSearchParams() body.append('grant_type', 'authorization_code') body.append('client_id', this.clientId) diff --git a/src/functions/Workers.ts b/src/functions/Workers.ts index 03b769f..efe5da5 100644 --- a/src/functions/Workers.ts +++ b/src/functions/Workers.ts @@ -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, 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) switch (activity.promotionType) { diff --git a/src/functions/activities/ABC.ts b/src/functions/activities/ABC.ts index 0f580ef..72e61d7 100644 --- a/src/functions/activities/ABC.ts +++ b/src/functions/activities/ABC.ts @@ -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', { state: 'visible', timeout: 10_000 }) + await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10000 }) const answers = $('.wk_OptionClickClass') 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 page.click(`#${answer}`) // Click answer 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 page = await this.bot.browser.utils.getLatestTab(page) diff --git a/src/functions/activities/Poll.ts b/src/functions/activities/Poll.ts index 8d5f395..526a2e0 100644 --- a/src/functions/activities/Poll.ts +++ b/src/functions/activities/Poll.ts @@ -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, { 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 page.click(buttonId) diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts index 0294264..e4a27e5 100644 --- a/src/functions/activities/Quiz.ts +++ b/src/functions/activities/Quiz.ts @@ -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}`, { 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')) if (answerAttribute && answerAttribute.toLowerCase() === 'true') { @@ -59,7 +59,7 @@ export class Quiz extends Workers { 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')) if (dataOption === correctOption) { diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index 425be17..5aebb63 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -156,7 +156,7 @@ export class Search extends Workers { await this.bot.utils.wait(500) 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 this.bot.utils.wait(500) await searchPage.keyboard.down(platformControlKey) @@ -312,7 +312,7 @@ export class Search extends Workers { await this.closeContinuePopup(page) // 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 let lastTab = await this.bot.browser.utils.getLatestTab(page) diff --git a/src/functions/activities/SearchOnBing.ts b/src/functions/activities/SearchOnBing.ts index b6f0526..6670c16 100644 --- a/src/functions/activities/SearchOnBing.ts +++ b/src/functions/activities/SearchOnBing.ts @@ -20,7 +20,7 @@ export class SearchOnBing extends Workers { const query = await this.getSearchQuery(activity.title) 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 this.bot.utils.wait(500) await page.keyboard.type(query)