From 28286ff9fe13b28e436a8542f7ccf078df2da5f2 Mon Sep 17 00:00:00 2001 From: TheNetsky <56271887+TheNetsky@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:38:58 +0100 Subject: [PATCH] Pre 1.3.0 --- .vscode/launch.json | 20 ---- README.md | 2 +- package.json | 13 ++- src/browser/Browser.ts | 15 +-- src/browser/BrowserFunc.ts | 132 +++++++++++++------------ src/browser/BrowserUtil.ts | 34 +++++++ src/functions/Activities.ts | 4 +- src/functions/Login.ts | 17 ++-- src/functions/Workers.ts | 17 ++-- src/functions/activities/Quiz.ts | 2 +- src/functions/activities/Search.ts | 131 +++++++++++++----------- src/functions/activities/ThisOrThat.ts | 17 +++- src/index.ts | 68 +++++++------ src/util/Load.ts | 16 +++ 14 files changed, 285 insertions(+), 203 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 1ddf16e..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -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": [ - "/**" - ], - "program": "${file}", - "outFiles": [ - "${workspaceFolder}/**/*.js" - ] - } - ] -} \ No newline at end of file diff --git a/README.md b/README.md index 05e24a8..6baf453 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Under development, however mainly for personal use! - [x] Completing Click Rewards - [x] Completing Polls - [x] Completing Punchcards -- [ ] Solving This Or That Quiz +- [x] Solving This Or That Quiz (Random) - [x] Clicking Promotional Items - [x] Solving ABC Quiz - [ ] Completing Shopping Game diff --git a/package.json b/package.json index bf4e3ca..bf62702 100644 --- a/package.json +++ b/package.json @@ -25,17 +25,16 @@ "license": "ISC", "devDependencies": { "@typescript-eslint/eslint-plugin": "^6.7.0", + "eslint": "^8.54.0", + "eslint-plugin-modules-newline": "^0.0.6", "typescript": "^5.2.2" }, "dependencies": { - "axios": "^1.5.1", + "axios": "^1.6.1", "cheerio": "^1.0.0-rc.12", - "eslint": "^8.49.0", - "eslint-plugin-modules-newline": "^0.0.6", - "fingerprint-generator": "^2.1.42", - "fingerprint-injector": "^2.1.42", - "puppeteer": "^21.4.1", - "puppeteer-extra": "^3.3.6", + "fingerprint-generator": "^2.1.43", + "fingerprint-injector": "^2.1.43", + "puppeteer": "^21.2.1", "ts-node": "^10.9.1" } } diff --git a/src/browser/Browser.ts b/src/browser/Browser.ts index 7e50e99..b4f5ed6 100644 --- a/src/browser/Browser.ts +++ b/src/browser/Browser.ts @@ -1,8 +1,9 @@ -import puppeteer from 'puppeteer-extra' +import puppeteer from 'puppeteer' import { FingerprintInjector } from 'fingerprint-injector' import { FingerprintGenerator } from 'fingerprint-generator' import { MicrosoftRewardsBot } from '../index' +import { loadSesion } from '../util/Load' import { AccountProxy } from '../interface/Account' @@ -20,12 +21,12 @@ class Browser { this.bot = bot } - async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) { - // const userAgent = await getUserAgent(isMobile) + async createBrowser(email: string, proxy: AccountProxy) { + // const userAgent = await getUserAgent(isMobile) const browser = await puppeteer.launch({ - headless: this.bot.config.headless, - userDataDir: await this.bot.browser.func.loadSesion(email), + headless: this.bot.config.headless ? 'new' : false, + userDataDir: await loadSesion(this.bot.config.sessionPath, email), args: [ '--no-sandbox', '--mute-audio', @@ -38,8 +39,8 @@ class Browser { }) const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({ - devices: isMobile ? ['mobile'] : ['desktop'], - operatingSystems: isMobile ? ['android'] : ['windows'], + devices: this.bot.isMobile ? ['mobile'] : ['desktop'], + operatingSystems: this.bot.isMobile ? ['android'] : ['windows'], browsers: ['edge'], browserListQuery: 'last 2 Edge versions' }) diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts index 892ac5d..b301a40 100644 --- a/src/browser/BrowserFunc.ts +++ b/src/browser/BrowserFunc.ts @@ -1,7 +1,5 @@ import { Page } from 'puppeteer' import { CheerioAPI, load } from 'cheerio' -import fs from 'fs' -import path from 'path' import { MicrosoftRewardsBot } from '../index' @@ -16,7 +14,12 @@ export default class BrowserFunc { this.bot = bot } - async goHome(page: Page): Promise { + + /** + * Navigate the provided page to rewards homepage + * @param {Page} page Puppeteer page + */ + async goHome(page: Page) { try { const dashboardURL = new URL(this.bot.config.baseURL) @@ -39,12 +42,14 @@ export default class BrowserFunc { try { // If activities are found, exit the loop await page.waitForSelector('#more-activities', { timeout: 1000 }) + this.bot.log('GO-HOME', 'Visited homepage successfully') break } catch (error) { // Continue if element is not found } + // Below runs if the homepage was unable to be visited const currentURL = new URL(page.url()) if (currentURL.hostname !== dashboardURL.hostname) { @@ -52,34 +57,37 @@ export default class BrowserFunc { await this.bot.utils.wait(2000) await page.goto(this.bot.config.baseURL) + } else { + this.bot.log('GO-HOME', 'Visited homepage successfully') + break } await this.bot.utils.wait(5000) - this.bot.log('GO-HOME', 'Visited homepage successfully') } } catch (error) { - console.error('An error occurred:', error) - return false + throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error') } - - return true } - async getDashboardData(page: Page): Promise { + /** + * Fetch user dashboard data + * @returns {DashboardData} Object of user bing rewards dashboard data + */ + async getDashboardData(): Promise { 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! if (currentURL.hostname !== dashboardURL.hostname) { 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 - 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 targetScript = scripts.find(script => script.innerText.includes('var dashboard')) @@ -91,7 +99,7 @@ export default class BrowserFunc { }) // 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 const regex = /var dashboard = (\{.*?\});/s const match = regex.exec(scriptContent) @@ -106,47 +114,23 @@ export default class BrowserFunc { return dashboardData } - async getQuizData(page: Page): Promise { - try { - const html = await page.content() - const $ = load(html) - - 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') - } - - } catch (error) { - throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error') - } - - } - - async getSearchPoints(page: Page): Promise { - const dashboardData = await this.getDashboardData(page) // Always fetch newest data + /** + * Get search point counters + * @returns {Counters} Object of search counter data + */ + async getSearchPoints(): Promise { + const dashboardData = await this.getDashboardData() // Always fetch newest data return dashboardData.userStatus.counters } - async getEarnablePoints(data: DashboardData, page: null | Page = null): Promise { + /** + * Get total earnable points + * @returns {number} Total earnable points + */ + async getEarnablePoints(): Promise { try { - // Fetch new data if page is provided - if (page) { - data = await this.getDashboardData(page) - } + const data = await this.getDashboardData() // These only include the points from tasks that the script can complete! let totalEarnablePoints = 0 @@ -176,12 +160,13 @@ export default class BrowserFunc { } } - async getCurrentPoints(data: DashboardData, page: null | Page = null): Promise { + /** + * Get current point amount + * @returns {number} Current total point amount + */ + async getCurrentPoints(): Promise { try { - // Fetch new data if page is provided - if (page) { - data = await this.getDashboardData(page) - } + const data = await this.getDashboardData() return data.userStatus.availablePoints } catch (error) { @@ -189,24 +174,43 @@ export default class BrowserFunc { } } - async loadSesion(email: string): Promise { - 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 { try { - // Create session dir - if (!fs.existsSync(sessionDir)) { - await fs.promises.mkdir(sessionDir, { recursive: true }) + const html = await page.content() + const $ = load(html) + + 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) { - throw new Error(error as string) + throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error') } + } async waitForQuizRefresh(page: Page): Promise { 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) return true diff --git a/src/browser/BrowserUtil.ts b/src/browser/BrowserUtil.ts index ea13e99..22ba237 100644 --- a/src/browser/BrowserUtil.ts +++ b/src/browser/BrowserUtil.ts @@ -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') + } + } + } \ No newline at end of file diff --git a/src/functions/Activities.ts b/src/functions/Activities.ts index 71ae7cb..8901f50 100644 --- a/src/functions/Activities.ts +++ b/src/functions/Activities.ts @@ -19,9 +19,9 @@ export default class Activities { this.bot = bot } - doSearch = async (page: Page, data: DashboardData, mobile: boolean): Promise => { + doSearch = async (page: Page, data: DashboardData): Promise => { const search = new Search(this.bot) - await search.doSearch(page, data, mobile) + await search.doSearch(page, data) } doABC = async (page: Page): Promise => { diff --git a/src/functions/Login.ts b/src/functions/Login.ts index d851013..88f4524 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -20,11 +20,12 @@ export class Login { try { // 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) { + // Check if account is locked const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false) if (isLocked) { this.bot.log('LOGIN', 'This account has been locked!', 'error') @@ -64,9 +65,11 @@ export class Login { await page.type('#i0118', password) await page.click('#idSIButton9') + // When erroring at this stage it means a 2FA code is required } catch (error) { this.bot.log('LOGIN', '2FA code required') + // Wait for user input const code = await new Promise((resolve) => { rl.question('Enter 2FA code:\n', (input) => { rl.close() @@ -83,13 +86,13 @@ export class Login { 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) currentURL.href = page.url() } // 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 { @@ -103,11 +106,11 @@ export class Login { const currentUrl = new URL(page.url()) if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') { - await this.bot.utils.wait(3000) await this.bot.browser.utils.tryDismissBingCookieBanner(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!') break } @@ -123,7 +126,7 @@ export class Login { private async checkBingLoginStatus(page: Page): Promise { try { - await page.waitForSelector('#id_n', { timeout: 10_000 }) + await page.waitForSelector('#id_n', { timeout: 5000 }) return true } catch (error) { return false diff --git a/src/functions/Workers.ts b/src/functions/Workers.ts index a0f507e..d767c60 100644 --- a/src/functions/Workers.ts +++ b/src/functions/Workers.ts @@ -92,6 +92,8 @@ export class Workers { // Solve all the different types of activities private async solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) { + let activityPage = page + for (const activity of activities) { try { @@ -111,14 +113,11 @@ export class Workers { } } - // Wait for element to load - await page.waitForSelector(selector, { timeout: 10_000 }) - // Click element, it will be opened in a new tab await page.click(selector) // Select the new activity page - const activityPage = await this.bot.browser.utils.getLatestTab(page) + activityPage = await this.bot.browser.utils.getLatestTab(page) // Wait for the new tab to fully load, ignore error. /* @@ -126,9 +125,7 @@ export class Workers { if it didn't then it gave enough time for the page to load. */ await activityPage.waitForNetworkIdle({ timeout: 10_000 }).catch(() => { }) - - // Cooldown - await this.bot.utils.wait(4000) + await this.bot.utils.wait(5000) switch (activity.promotionType) { // Quiz (Poll, Quiz or ABC) @@ -175,8 +172,14 @@ export class Workers { // Cooldown await this.bot.utils.wait(2000) + } catch (error) { this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error') + const tabs = await (page.browser()).pages() + + if (tabs.length > 2) { + await activityPage.close() // Already assigned to be the "latest tab" + } } } } diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts index 0e219e6..90957a8 100644 --- a/src/functions/activities/Quiz.ts +++ b/src/functions/activities/Quiz.ts @@ -52,7 +52,7 @@ export class Quiz extends Workers { } } - // Other type quiz + // Other type quiz, lightspeed } else if ([2, 3, 4].includes(quizData.numberOfOptions)) { quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data const correctOption = quizData.correctAnswer diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index b908be3..d7b7849 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -12,19 +12,25 @@ export class Search extends Workers { 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') + let retries = 0 + while ((!data.userStatus.counters?.pcSearch || data.userStatus.counters.pcSearch.length < 2) && retries < 3) { + data = await this.bot.browser.func.getDashboardData() + retries++ + } + const mobileData = data.userStatus.counters?.mobileSearch ? data.userStatus.counters.mobileSearch[0] : null // Mobile searches 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 missingPoints = (this.bot.isMobile && mobileData) ? (mobileData.pointProgressMax - mobileData.pointProgress) : (edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress) 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 } @@ -35,31 +41,39 @@ export class Search extends Workers { // Deduplicate the search terms googleSearchQueries = [...new Set(googleSearchQueries)] - // Open a new tab - const browser = page.browser() - const searchPage = await browser.newPage() // 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 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 for (let i = 0; i < queries.length; i++) { 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) + let newData = await this.bingSearch(page, page, query) + + let retries = 0 + while ((!newData?.pcSearch || newData.pcSearch.length < 2) && retries < 3) { + newData = await this.bot.browser.func.getSearchPoints() + retries++ + } + + if (!newData?.pcSearch || newData.pcSearch.length < 2) { + newData = await this.bot.browser.func.getSearchPoints() + } 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) ? + const newMissingPoints = (this.bot.isMobile && newMobileData) ? (newMobileData.pointProgressMax - newMobileData.pointProgress) : (newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress) @@ -97,14 +111,21 @@ export class Search extends Workers { if (relatedTerms.length > 3) { // Search for the first 2 related terms for (const term of relatedTerms.slice(1, 3)) { - this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`) - const newData = await this.bingSearch(page, searchPage, query.topic) + this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${this.bot.isMobile}`) + + let newData = await this.bingSearch(page, page, query.topic) + + let retries = 0 + while ((!newData?.pcSearch || newData.pcSearch.length < 2) && retries < 3) { + newData = await this.bot.browser.func.getSearchPoints() + retries++ + } 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) ? + const newMissingPoints = (this.bot.isMobile && newMobileData) ? (newMobileData.pointProgressMax - newMobileData.pointProgress) : (newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress) @@ -162,7 +183,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))) - return await this.bot.browser.func.getSearchPoints(page) + return await this.bot.browser.func.getSearchPoints() } catch (error) { if (i === 5) { @@ -178,7 +199,7 @@ export class Search extends Workers { } 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 { @@ -211,7 +232,7 @@ export class Search extends Workers { for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) { queryTerms.push({ topic: topic.title.query.toLowerCase(), - related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase()) + related: topic.relatedQueries.map(x => x.query.toLowerCase()) }) } @@ -253,9 +274,10 @@ export class Search extends Workers { private async randomScroll(page: Page) { try { // 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') } + } catch (error) { this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error') } @@ -271,57 +293,52 @@ export class Search extends Workers { let lastTab = await this.bot.browser.utils.getLatestTab(page) // Let website load, if it doesn't load within 5 sec. exit regardless - await lastTab.waitForNetworkIdle({ timeout: 5000 }).catch(() => { }) + await this.bot.utils.wait(5000) - // Check if the tab is closed or not - if (!lastTab.isClosed()) { - let lastTabURL = new URL(lastTab.url()) // Get new tab info + 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. - let i = 0 - while (lastTabURL.href !== searchListingURL.href && i < 5) { - // If hostname is still bing, (Bing images/news etc) - if (lastTabURL.hostname == searchListingURL.hostname) { + // 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) { + // If hostname is still bing, (Bing images/news etc) + if (lastTabURL.hostname === searchListingURL.hostname) { + await lastTab.goBack() + + // Refresh + lastTab = await this.bot.browser.utils.getLatestTab(page) + 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) + } + + } else { // No longer on bing, likely opened a new tab, close this tab + const tabs = await (page.browser()).pages() + + // 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) { + await lastTab.close() + + } else if (lastTabURL.href !== searchListingURL.href) { await lastTab.goBack() - - lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab + // Refresh + lastTab = await this.bot.browser.utils.getLatestTab(page) + 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) } - - } else { // No longer on bing, likely opened a new tab, close this tab - lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab - 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) { this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error') } diff --git a/src/functions/activities/ThisOrThat.ts b/src/functions/activities/ThisOrThat.ts index f3913c4..7613121 100644 --- a/src/functions/activities/ThisOrThat.ts +++ b/src/functions/activities/ThisOrThat.ts @@ -8,7 +8,7 @@ export class ThisOrThat extends Workers { async doThisOrThat(page: Page) { this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat') - return + try { // Check if the quiz has been started or not const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false) @@ -22,7 +22,20 @@ export class ThisOrThat extends Workers { // Solving 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 + + 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) { diff --git a/src/index.ts b/src/index.ts index 099bd88..1a2f9ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,5 @@ import cluster from 'cluster' +import { Page } from 'puppeteer' import Browser from './browser/Browser' import BrowserFunc from './browser/BrowserFunc' @@ -24,6 +25,8 @@ export class MicrosoftRewardsBot { func: BrowserFunc, utils: BrowserUtil } + public isMobile: boolean = false + public homePage!: Page private collectedPoints: number = 0 private activeWorkers: number @@ -122,8 +125,8 @@ export class MicrosoftRewardsBot { // Desktop async Desktop(account: Account) { - const browser = await this.browserFactory.createBrowser(account.email, account.proxy, false) - 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() // If for some reason the browser initializes with more than 2 pages, close these @@ -133,51 +136,53 @@ export class MicrosoftRewardsBot { } // 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') - // Login into MS Rewards - await this.login.login(page, account.email, account.password) + // Login into MS Rewards, then go to rewards homepage + 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) - if (!wentHome) { - throw log('MAIN', 'Unable to get dashboard page', 'error') - } - - const data = await this.browser.func.getDashboardData(page) + const data = await this.browser.func.getDashboardData() 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 log('MAIN-POINTS', `You can earn ${earnablePoints} points today`) // If runOnZeroPoints is false and 0 points to earn, don't continue if (!this.config.runOnZeroPoints && this.collectedPoints === 0) { - log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping') + log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!') // Close desktop browser 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 if (this.config.workers.doDailySet) { - await this.workers.doDailySet(page, data) + await this.workers.doDailySet(workerPage, data) } // Complete more promotions if (this.config.workers.doMorePromotions) { - await this.workers.doMorePromotions(page, data) + await this.workers.doMorePromotions(workerPage, data) } // Complete punch cards if (this.config.workers.doPunchCards) { - await this.workers.doPunchCard(page, data) + await this.workers.doPunchCard(workerPage, data) } // Do desktop searches if (this.config.workers.doDesktopSearch) { - await this.activities.doSearch(page, data, false) + await this.activities.doSearch(workerPage, data) } // Close desktop browser @@ -186,8 +191,10 @@ export class MicrosoftRewardsBot { // Mobile async Mobile(account: Account) { - const browser = await this.browserFactory.createBrowser(account.email, account.proxy, true) - const page = await browser.newPage() + this.isMobile = true + + const browser = await this.browserFactory.createBrowser(account.email, account.proxy) + this.homePage = await browser.newPage() let pages = await browser.pages() // If for some reason the browser initializes with more than 2 pages, close these @@ -196,32 +203,37 @@ export class MicrosoftRewardsBot { pages = await browser.pages() } // 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') - // Login into MS Rewards - await this.login.login(page, account.email, account.password) + // Login into MS Rewards, then go to rewards homepage + 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(page) + const data = await this.browser.func.getDashboardData() // If no mobile searches data found, stop (Does not exist on new accounts) if (!data.userStatus.counters.mobileSearch) { - log('MAIN', 'No mobile searches found, stopping') + log('MAIN', 'No mobile searches found, stopping!') // Close mobile browser 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 if (this.config.workers.doMobileSearch) { - await this.activities.doSearch(page, data, true) + await this.activities.doSearch(workerPage, data) } // 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 this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints) diff --git a/src/util/Load.ts b/src/util/Load.ts index 0742881..a5399f3 100644 --- a/src/util/Load.ts +++ b/src/util/Load.ts @@ -32,4 +32,20 @@ export function loadConfig(): Config { } catch (error) { throw new Error(error as string) } +} + +export async function loadSesion(sessionPath: string, email: string): Promise { + 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) + } } \ No newline at end of file