diff --git a/README.md b/README.md index c874ebe..1b86b79 100644 --- a/README.md +++ b/README.md @@ -9,18 +9,24 @@ Under development, however mainly for personal use! 3. Change `accounts.example.json` to `accounts.json` and add your account details 4. Change `config.json` to your liking 5. Run `npm run build` to build the script -6. Run `nom run start` to start the built script +6. Run `npm run start` to start the built script ## Features ## +- [x] Multi-Account Support +- [x] Session Storing +- [x] 2FA Support +- [x] Discord Webhook Support - [x] Desktop Searches +- [x] Microsoft Edge Searches - [x] Mobile Searches +- [x] Emulate scrolling and link clicking (Optional) - [x] Completing Daily Set - [x] Completing More Promotions - [x] Solving Quiz (10 point variant) - [x] Solving Quiz (30-40 point variant) - [x] Completing Click Rewards - [x] Completing Polls -- [ ] Completing Punchcards +- [x] Completing Punchcards - [ ] Solving This Or That Quiz - [x] Clicking Promotional Items - [x] Solving ABC Quiz diff --git a/package.json b/package.json index 39117dd..e2e5593 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { "name": "microsoft-rewards-script", - "version": "1.0.7", - "description": "Automatically do tasks for Microsoft Rewards but in TS", + "version": "1.1.0", + "description": "Automatically do tasks for Microsoft Rewards but in TS!", "main": "index.js", + "engines": { + "node": ">=18.0.0" + }, "scripts": { "build": "tsc", "start": "node ./dist/index.js", - "dev": "ts-node ./src/index.ts" + "dev": "ts-node ./src/index.ts -dev" }, "keywords": [ "Bing Rewards", @@ -31,4 +34,4 @@ "puppeteer-extra-plugin-stealth": "^2.11.2", "ts-node": "^10.9.1" } -} +} \ No newline at end of file diff --git a/src/browser/BrowserFunc.ts b/src/browser/BrowserFunc.ts index 967b36b..2c0affb 100644 --- a/src/browser/BrowserFunc.ts +++ b/src/browser/BrowserFunc.ts @@ -7,7 +7,7 @@ import { tryDismissAllMessages, tryDismissCookieBanner } from './BrowserUtil' import { getFormattedDate, wait } from './../util/Utils' import { log } from './../util/Logger' -import { Counters, DashboardData } from './../interface/DashboardData' +import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData' import { QuizData } from './../interface/QuizData' import { baseURL, sessionPath } from './../config.json' @@ -195,7 +195,6 @@ export async function loadSesion(email: string): Promise { } return sessionDir - } catch (error) { throw new Error(error as string) } @@ -203,7 +202,9 @@ export async function loadSesion(email: string): Promise { export async function waitForQuizRefresh(page: Page) { try { - await page.waitForSelector('#rqHeaderCredits', { timeout: 5000 }) + await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 5000 }) + await wait(2000) + return true } catch (error) { log('QUIZ-REFRESH', 'An error occurred:' + error, 'error') @@ -213,7 +214,9 @@ export async function waitForQuizRefresh(page: Page) { export async function checkQuizCompleted(page: Page) { try { - await page.waitForSelector('#quizCompleteContainer', { timeout: 1000 }) + await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 1000 }) + await wait(2000) + return true } catch (error) { return false @@ -225,4 +228,21 @@ export async function refreshCheerio(page: Page) { const $ = load(html) return $ +} + +export async function getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion) { + let selector = '' + try { + const html = await page.content() + const $ = load(html) + + const element = $('.offer-cta').toArray().find(x => x.attribs.href?.includes(activity.offerId)) + if (element) { + selector = `a[href*="${element.attribs.href}"]` + } + } catch (error) { + log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error') + } + + return selector } \ No newline at end of file diff --git a/src/config.json b/src/config.json index fcfa0d1..cf2ce9e 100644 --- a/src/config.json +++ b/src/config.json @@ -3,9 +3,14 @@ "sessionPath": "sessions", "headless": false, "runOnZeroPoints": false, - "searches": { - "doMobile": true, - "doDesktop": true, + "workers": { + "doDailySet": true, + "doMorePromotions": true, + "doPunchCards": true, + "doDesktopSearch": true, + "doMobileSearch": true + }, + "searchSettings": { "scrollRandomResults": true, "clickRandomResults": true }, diff --git a/src/functions/Workers.ts b/src/functions/Workers.ts index 002ddbb..5e0733b 100644 --- a/src/functions/Workers.ts +++ b/src/functions/Workers.ts @@ -6,74 +6,33 @@ import { doUrlReward } from './activities/UrlReward' import { doThisOrThat } from './activities/ThisOrThat' import { doABC } from './activities/ABC' +import { getPunchCardActivity } from '../browser/BrowserFunc' +import { getLatestTab } from '../browser/BrowserUtil' + import { getFormattedDate, wait } from '../util/Utils' import { log } from '../util/Logger' -import { DashboardData, MorePromotion } from '../interface/DashboardData' +import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData' + // Daily Set export async function doDailySet(page: Page, data: DashboardData) { + const todayData = data.dailySetPromotions[getFormattedDate()] - const activitiesUncompleted = todayData?.filter(x => !x.complete) ?? [] + const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? [] if (!activitiesUncompleted.length) { - log('DAILY-SET', 'All daily set items have already been completed') + log('DAILY-SET', 'All Daily Set" items have already been completed') return } - for (const activity of activitiesUncompleted) { - log('DAILY-SET', 'Started doing daily set items') + // Solve Activities + log('DAILY-SET', 'Started solving "Daily Set" items') - // If activity does not give points, skip - if (activity.pointProgressMax <= 0) { - continue - } + await solveActivities(page, activitiesUncompleted) - switch (activity.promotionType) { - // Quiz (Poll, Quiz or ABC) - case 'quiz': - - switch (activity.pointProgressMax) { - // Poll or ABC (Usually 10 points) - case 10: - // Normal poll - if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) { - log('ACTIVITY', 'Found daily activity type: Poll') - await doPoll(page, activity) - } else { // ABC - log('ACTIVITY', 'Found daily activity type: ABC') - await doABC(page, activity) - } - break - - // This Or That Quiz (Usually 50 points) - case 50: - log('ACTIVITY', 'Found daily activity type: ThisOrThat') - await doThisOrThat(page, activity) - break - - // Quizzes are usually 30-40 points - default: - log('ACTIVITY', 'Found daily activity type: Quiz') - await doQuiz(page, activity) - break - } - break - - // UrlReward (Visit) - case 'urlreward': - log('ACTIVITY', 'Found daily activity type: UrlReward') - await doUrlReward(page, activity) - break - - default: - break - } - await wait(1500) - } - - log('DAILY-SET', 'Daily set items have been completed') + log('DAILY-SET', 'All "Daily Set" items have been completed') } // Punch Card @@ -82,66 +41,31 @@ export async function doPunchCard(page: Page, data: DashboardData) { const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards if (!punchCardsUncompleted.length) { - log('PUNCH-CARD', 'All punch cards have already been completed') + log('PUNCH-CARD', 'All "Punch Cards" have already been completed') return } - for (const promotion of punchCardsUncompleted) { - const activities = promotion.childPromotions.filter(x => !x.complete) // Only return uncompleted activities + for (const punchCard of punchCardsUncompleted) { + const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities - for (const activity of activities) { - log('PUNCH-CARD', 'Started doing daily set items') + // Solve Activities + log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`) - // If activity does not give points, skip - if (activity.pointProgressMax <= 0) { - continue - } + const browser = page.browser() + page = await browser.newPage() - switch (activity.promotionType) { - // Quiz (Poll, Quiz or ABC) - case 'quiz': + // Got to punch card index page in a new tab + await page.goto(punchCard.parentPromotion.destinationUrl, { referer: 'https://rewards.bing.com/' }) - switch (activity.pointProgressMax) { - // Poll or ABC (Usually 10 points) - case 10: - // Normal poll - if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) { - log('ACTIVITY', 'Found daily activity type: Poll') - await doPoll(page, activity) - } else { // ABC - log('ACTIVITY', 'Found daily activity type: ABC') - await doABC(page, activity) - } - break + await solveActivities(page, activitiesUncompleted, punchCard) - // This Or That Quiz (Usually 50 points) - case 50: - log('ACTIVITY', 'Found daily activity type: ThisOrThat') - await doThisOrThat(page, activity) - break + // Close the punch card index page + await page.close() - // Quizzes are usually 30-40 points - default: - log('ACTIVITY', 'Found daily activity type: Quiz') - await doQuiz(page, activity) - break - } - break - - // UrlReward (Visit) - case 'urlreward': - log('ACTIVITY', 'Found daily activity type: UrlReward') - await doUrlReward(page, activity) - break - - default: - break - } - await wait(1500) - } + log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`) } - log('PUNCH-CARD', 'Punch card items have been completed') + log('PUNCH-CARD', 'All "Punch Card" items have been completed') } // More Promotions @@ -153,59 +77,87 @@ export async function doMorePromotions(page: Page, data: DashboardData) { morePromotions.push(data.promotionalItem as unknown as MorePromotion) } - const activitiesUncompleted = morePromotions?.filter(x => !x.complete) ?? [] + const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0) ?? [] if (!activitiesUncompleted.length) { - log('MORE-PROMOTIONS', 'All more promotion items have already been completed') + log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed') return } - for (const activity of activitiesUncompleted) { - // If activity does not give points, skip - if (activity.pointProgressMax <= 0) { - continue + // Solve Activities + log('MORE-PROMOTIONS', 'Started solving "More Promotions" item') + + await solveActivities(page, activitiesUncompleted) + + log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed') +} + +// Solve all the different types of activities +async function solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) { + try { + for (const activity of activities) { + + if (punchCard) { + const selector = await getPunchCardActivity(page, activity) + + // Wait for page to load and click to load the activity in a new tab + await page.waitForSelector(selector, { timeout: 5000 }) + await page.click(selector) + + } else { + const selector = `[data-bi-id="${activity.offerId}"]` + + // Wait for page to load and click to load the activity in a new tab + await page.waitForSelector(selector, { timeout: 5000 }) + await page.click(selector) + } + + // Select the new activity page + const activityPage = await getLatestTab(page) + + switch (activity.promotionType) { + // Quiz (Poll, Quiz or ABC) + case 'quiz': + switch (activity.pointProgressMax) { + // Poll or ABC (Usually 10 points) + case 10: + // Normal poll + if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) { + log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`) + await doPoll(activityPage) + } else { // ABC + log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`) + await doABC(activityPage) + } + break + + // This Or That Quiz (Usually 50 points) + case 50: + log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`) + await doThisOrThat(activityPage, activity) + break + + // Quizzes are usually 30-40 points + default: + log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`) + await doQuiz(activityPage) + break + } + break + + // UrlReward (Visit) + case 'urlreward': + log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`) + await doUrlReward(activityPage) + break + + default: + break + } + await wait(1500) } - switch (activity.promotionType) { - // Quiz (Poll, Quiz or ABC) - case 'quiz': - - switch (activity.pointProgressMax) { - // Poll or ABC (Usually 10 points) - case 10: - // Normal poll - if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) { - log('ACTIVITY', 'Found daily activity type: Poll') - await doPoll(page, activity) - } else { // ABC - log('ACTIVITY', 'Found daily activity type: ABC') - await doABC(page, activity) - } - break - - // This Or That Quiz (Usually 50 points) - case 50: - log('ACTIVITY', 'Found daily activity type: ThisOrThat') - await doThisOrThat(page, activity) - break - - // Quizzes are usually 30-40 points - default: - log('ACTIVITY', 'Found promotion activity type: Quiz') - await doQuiz(page, activity) - break - } - break - - // UrlReward (Visit) - case 'urlreward': - log('ACTIVITY', 'Found promotion activity type: UrlReward') - await doUrlReward(page, activity) - break - - default: - break - } - await wait(1500) + } catch (error) { + log('ACTIVITY', 'An error occurred:' + error, 'error') } } \ No newline at end of file diff --git a/src/functions/activities/ABC.ts b/src/functions/activities/ABC.ts index 4d56146..52cde7a 100644 --- a/src/functions/activities/ABC.ts +++ b/src/functions/activities/ABC.ts @@ -5,47 +5,38 @@ import { getLatestTab } from '../../browser/BrowserUtil' import { log } from '../../util/Logger' import { randomNumber, wait } from '../../util/Utils' -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' - -export async function doABC(page: Page, data: PromotionalItem | MorePromotion) { +export async function doABC(page: Page) { log('ABC', 'Trying to complete poll') try { - const selector = `[data-bi-id="${data.offerId}"]` - - // Wait for page to load and click to load the quiz in a new tab - await page.waitForSelector(selector, { timeout: 5000 }) - await page.click(selector) - - let abcPage = await getLatestTab(page) await wait(2000) - let $ = await refreshCheerio(abcPage) + let $ = await refreshCheerio(page) // Don't loop more than 15 in case unable to solve, would lock otherwise const maxIterations = 15 let i for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) { - await abcPage.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 5000 }) + await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 5000 }) const answers = $('.wk_OptionClickClass') const answer = answers[randomNumber(0, 2)]?.attribs['id'] - await abcPage.waitForSelector(`#${answer}`, { visible: true, timeout: 5000 }) + await page.waitForSelector(`#${answer}`, { visible: true, timeout: 5000 }) await wait(2000) - await abcPage.click(`#${answer}`) // Click answer + await page.click(`#${answer}`) // Click answer await wait(4000) - await abcPage.waitForSelector('div.wk_button', { visible: true, timeout: 5000 }) - await abcPage.click('div.wk_button') // Click next question button + await page.waitForSelector('div.wk_button', { visible: true, timeout: 5000 }) + await page.click('div.wk_button') // Click next question button - abcPage = await getLatestTab(abcPage) - $ = await refreshCheerio(abcPage) + page = await getLatestTab(page) + $ = await refreshCheerio(page) await wait(1000) } await wait(4000) - await abcPage.close() + await page.close() if (i === maxIterations) { log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn') @@ -54,8 +45,7 @@ export async function doABC(page: Page, data: PromotionalItem | MorePromotion) { } } catch (error) { - const abcPage = await getLatestTab(page) - await abcPage.close() + await page.close() log('ABC', 'An error occurred:' + error, 'error') } } diff --git a/src/functions/activities/Poll.ts b/src/functions/activities/Poll.ts index bb7dc58..ad16f83 100644 --- a/src/functions/activities/Poll.ts +++ b/src/functions/activities/Poll.ts @@ -1,38 +1,26 @@ import { Page } from 'puppeteer' -import { getLatestTab } from '../../browser/BrowserUtil' import { log } from '../../util/Logger' import { randomNumber, wait } from '../../util/Utils' -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' - -export async function doPoll(page: Page, data: PromotionalItem | MorePromotion) { +export async function doPoll(page: Page) { log('POLL', 'Trying to complete poll') try { - const selector = `[data-bi-id="${data.offerId}"]` - - // Wait for page to load and click to load the quiz in a new tab - await page.waitForSelector(selector, { timeout: 5000 }) - await page.click(selector) - - const pollPage = await getLatestTab(page) - const buttonId = `#btoption${Math.floor(randomNumber(0, 1))}` - await pollPage.waitForNetworkIdle({ timeout: 5000 }) - await pollPage.waitForSelector(buttonId, { visible: true, timeout: 5000 }) + await page.waitForNetworkIdle({ timeout: 5000 }) + await page.waitForSelector(buttonId, { visible: true, timeout: 5000 }) await wait(2000) - await pollPage.click(buttonId) + await page.click(buttonId) await wait(4000) - await pollPage.close() + await page.close() log('POLL', 'Completed the poll successfully') } catch (error) { - const pollPage = await getLatestTab(page) - await pollPage.close() + await page.close() log('POLL', 'An error occurred:' + error, 'error') } } \ No newline at end of file diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts index c114eca..0886cb3 100644 --- a/src/functions/activities/Quiz.ts +++ b/src/functions/activities/Quiz.ts @@ -1,37 +1,27 @@ import { Page } from 'puppeteer' -import { getLatestTab } from '../../browser/BrowserUtil' import { getQuizData, waitForQuizRefresh } from '../../browser/BrowserFunc' import { wait } from '../../util/Utils' import { log } from '../../util/Logger' -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' - -export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) { +export async function doQuiz(page: Page) { log('QUIZ', 'Trying to complete quiz') try { - const selector = `[data-bi-id="${data.offerId}"]` - - // Wait for page to load and click to load the quiz in a new tab - await page.waitForSelector(selector, { timeout: 5000 }) - await page.click(selector) - - const quizPage = await getLatestTab(page) - await quizPage.waitForNetworkIdle({ timeout: 5000 }) + await page.waitForNetworkIdle({ timeout: 5000 }) await wait(2000) // Check if the quiz has been started or not - const quizNotStarted = await quizPage.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) + const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) if (quizNotStarted) { - await quizPage.click('#rqStartQuiz') + await page.click('#rqStartQuiz') } else { log('QUIZ', 'Quiz has already been started, trying to finish it') } await wait(2000) - let quizData = await getQuizData(quizPage) + let quizData = await getQuizData(page) const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining // All questions @@ -41,7 +31,7 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) const answers: string[] = [] for (let i = 0; i < quizData.numberOfOptions; i++) { - const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) + const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption')) if (answerAttribute && answerAttribute.toLowerCase() === 'true') { @@ -51,14 +41,14 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) // Click the answers for (const answer of answers) { - await quizPage.waitForSelector(answer, { visible: true, timeout: 2000 }) + await page.waitForSelector(answer, { visible: true, timeout: 2000 }) // Click the answer on page - await quizPage.click(answer) + await page.click(answer) - const refreshSuccess = await waitForQuizRefresh(quizPage) + const refreshSuccess = await waitForQuizRefresh(page) if (!refreshSuccess) { - await quizPage.close() + await page.close() log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error') return } @@ -66,21 +56,21 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) // Other type quiz } else if ([2, 3, 4].includes(quizData.numberOfOptions)) { - quizData = await getQuizData(quizPage) // Refresh Quiz Data + quizData = await getQuizData(page) // Refresh Quiz Data const correctOption = quizData.correctAnswer for (let i = 0; i < quizData.numberOfOptions; i++) { - const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) + const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option')) if (dataOption === correctOption) { // Click the answer on page - await quizPage.click(`#rqAnswerOption${i}`) + await page.click(`#rqAnswerOption${i}`) - const refreshSuccess = await waitForQuizRefresh(quizPage) + const refreshSuccess = await waitForQuizRefresh(page) if (!refreshSuccess) { - await quizPage.close() + await page.close() log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error') return } @@ -94,11 +84,10 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) // Done with await wait(2000) - await quizPage.close() + await page.close() log('QUIZ', 'Completed the quiz successfully') } catch (error) { - const quizPage = await getLatestTab(page) - await quizPage.close() + await page.close() log('QUIZ', 'An error occurred:' + error, 'error') } diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index ed7d42a..9660016 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -6,7 +6,7 @@ import { getSearchPoints } from '../../browser/BrowserFunc' import { log } from '../../util/Logger' import { randomNumber, shuffleArray, wait } from '../../util/Utils' -import { searches } from '../../config.json' +import { searchSettings } from '../../config.json' import { DashboardData, DashboardImpression } from '../../interface/DashboardData' import { GoogleTrends } from '../../interface/GoogleDailyTrends' @@ -153,12 +153,12 @@ async function bingSearch(page: Page, searchPage: Page, query: string) { await searchPage.keyboard.type(query) await searchPage.keyboard.press('Enter') - if (searches.scrollRandomResults) { + if (searchSettings.scrollRandomResults) { await wait(2000) await randomScroll(searchPage) } - if (searches.clickRandomResults) { + if (searchSettings.clickRandomResults) { await wait(2000) await clickRandomLink(searchPage) } @@ -266,12 +266,15 @@ async function clickRandomLink(page: Page) { 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 wait(3000) - let lastTab = await getLatestTab(page) // Will get current tab if no new one is created - await lastTab.waitForNetworkIdle() // Wait for page to load - + // Will get current tab if no new one is created + let lastTab = await getLatestTab(page) + + // Wait for website to finish loading, don't break loop however + await lastTab.waitForNetworkIdle({ idleTime: 1000, timeout: 5000 }).catch(() => { }) + // Check if the tab is closed or not if (!lastTab.isClosed()) { let lastTabURL = new URL(lastTab.url()) // Get new tab info diff --git a/src/functions/activities/ThisOrThat.ts b/src/functions/activities/ThisOrThat.ts index 77b6c29..8cde77b 100644 --- a/src/functions/activities/ThisOrThat.ts +++ b/src/functions/activities/ThisOrThat.ts @@ -35,8 +35,7 @@ export async function doThisOrThat(page: Page, data: PromotionalItem | MorePromo log('THIS-OR-THAT', 'Completed the ThisOrthat successfully') } catch (error) { - const thisorthatPage = await getLatestTab(page) - await thisorthatPage.close() + await page.close() log('THIS-OR-THAT', 'An error occurred:' + error, 'error') } diff --git a/src/functions/activities/UrlReward.ts b/src/functions/activities/UrlReward.ts index a4894ad..0d29bfb 100644 --- a/src/functions/activities/UrlReward.ts +++ b/src/functions/activities/UrlReward.ts @@ -1,29 +1,20 @@ import { Page } from 'puppeteer' -import { getLatestTab } from '../../browser/BrowserUtil' +import { wait } from '../../util/Utils' import { log } from '../../util/Logger' -import { PromotionalItem, MorePromotion } from '../../interface/DashboardData' - -export async function doUrlReward(page: Page, data: PromotionalItem | MorePromotion) { +export async function doUrlReward(page: Page) { log('URL-REWARD', 'Trying to complete UrlReward') try { - const selector = `[data-bi-id="${data.offerId}"]` - - // Wait for page to load and click to load the url reward in a new tab - await page.waitForSelector(selector, { timeout: 5000 }) - await page.click(selector) - // After waiting, close the page - const visitPage = await getLatestTab(page) - await visitPage.waitForNetworkIdle({ timeout: 10_000 }) - await visitPage.close() + await page.waitForNetworkIdle({ timeout: 10_000 }) + await wait(2000) + await page.close() log('URL-REWARD', 'Completed the UrlReward successfully') } catch (error) { - const visitPage = await getLatestTab(page) - await visitPage.close() + await page.close() log('URL-REWARD', 'An error occurred:' + error, 'error') } diff --git a/src/index.ts b/src/index.ts index 5e5fe8d..09fb72c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,15 @@ import Browser from './browser/Browser' import { getDashboardData, getEarnablePoints, goHome } from './browser/BrowserFunc' import { log } from './util/Logger' +import { loadAccounts } from './util/Account' import { login } from './functions/Login' -import { doDailySet, doMorePromotions } from './functions/Workers' +import { doDailySet, doMorePromotions, doPunchCard } from './functions/Workers' import { doSearch } from './functions/activities/Search' import { Account } from './interface/Account' -import accounts from './accounts.json' -import { runOnZeroPoints, searches } from './config.json' +import { runOnZeroPoints, workers } from './config.json' // Main bot class class MicrosoftRewardsBot { @@ -19,6 +19,8 @@ class MicrosoftRewardsBot { async run() { log('MAIN', 'Bot started') + const accounts = await loadAccounts() + for (const account of accounts) { log('MAIN', `Started tasks for account ${account.email}`) @@ -74,13 +76,22 @@ class MicrosoftRewardsBot { } // Complete daily set - await doDailySet(page, data) + if (workers.doDailySet) { + await doDailySet(page, data) + } // Complete more promotions - await doMorePromotions(page, data) + if (workers.doMorePromotions) { + await doMorePromotions(page, data) + } + + // Complete punch cards + if (workers.doPunchCards) { + await doPunchCard(page, data) + } // Do desktop searches - if (searches.doDesktop) { + if (workers.doDesktopSearch) { await doSearch(page, data, false) } @@ -110,7 +121,7 @@ class MicrosoftRewardsBot { } // Do mobile searches - if (searches.doMobile) { + if (workers.doMobileSearch) { await doSearch(page, data, true) } diff --git a/src/util/Account.ts b/src/util/Account.ts new file mode 100644 index 0000000..79805e9 --- /dev/null +++ b/src/util/Account.ts @@ -0,0 +1,20 @@ +import * as fs from 'fs' +import path from 'path' + +export async function loadAccounts() { + try { + let file = 'accounts.json' + + // If dev mode, use dev account(s) + if (process.argv.includes('-dev')) { + file = 'accounts.dev.json' + } + + const accountDir = path.join(__dirname, '../', file) + const accounts = fs.readFileSync(accountDir, 'utf-8') + + return JSON.parse(accounts) + } catch (error) { + throw new Error(error as string) + } +} \ No newline at end of file