diff --git a/.gitignore b/.gitignore index d0cd3a7..0ea3667 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist/ node_modules/ package-lock.json accounts.json +notes +accounts.dev.json diff --git a/package.json b/package.json index 35d6131..0fb74ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "microsoft-rewards-script", - "version": "1.0.4", + "version": "1.0.5", "description": "Automatically do tasks for Microsoft Rewards but in TS", "main": "index.js", "scripts": { @@ -22,12 +22,13 @@ "typescript": "^5.2.2" }, "dependencies": { - "axios": "^1.5.0", + "axios": "^1.5.1", + "cheerio": "^1.0.0-rc.12", "eslint": "^8.49.0", "eslint-plugin-modules-newline": "^0.0.6", - "puppeteer": "^21.2.1", + "puppeteer": "^21.3.6", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2", "ts-node": "^10.9.1" } -} \ No newline at end of file +} diff --git a/src/BrowserFunc.ts b/src/BrowserFunc.ts index 0b0cc9b..9111e6a 100644 --- a/src/BrowserFunc.ts +++ b/src/BrowserFunc.ts @@ -1,15 +1,17 @@ import { Page } from 'puppeteer' import fs from 'fs' import path from 'path' +import { load } from 'cheerio' -import { baseURL, sessionPath } from './config.json' -import { getFormattedDate, wait } from './util/Utils' import { tryDismissAllMessages, tryDismissCookieBanner } from './BrowserUtil' +import { getFormattedDate, wait } from './util/Utils' import { log } from './util/Logger' import { Counters, DashboardData } from './interface/DashboardData' import { QuizData } from './interface/QuizData' +import { baseURL, sessionPath } from './config.json' + export async function goHome(page: Page): Promise { try { @@ -53,7 +55,7 @@ export async function goHome(page: Page): Promise { } } catch (error) { - console.error('An error occurred:', error) + console.error('An error occurred:', JSON.stringify(error, null, 2)) return false } @@ -101,30 +103,32 @@ export async function getDashboardData(page: Page): Promise { } export async function getQuizData(page: Page): Promise { - const scriptContent = await page.evaluate(() => { - const scripts = Array.from(document.querySelectorAll('script')) - const targetScript = scripts.find(script => script.innerText.includes('_w.rewardsQuizRenderInfo')) + try { + const html = await page.content() + const $ = load(html) - if (targetScript) { - return targetScript.innerText + 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 log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error') + } } else { throw log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error') } - }) - const quizData = await page.evaluate(scriptContent => { - // Extract the dashboard object using regex - const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s - const match = regex.exec(scriptContent) + } catch (error) { + throw log('GET-QUIZ-DATA', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') + } - if (match && match[1]) { - return JSON.parse(match[1]) - } else { - throw log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error') - } - }, scriptContent) - - return quizData } export async function getSearchPoints(page: Page): Promise { @@ -134,32 +138,38 @@ export async function getSearchPoints(page: Page): Promise { } export async function getEarnablePoints(data: DashboardData, page: null | Page = null): Promise { - // Fetch new data if page is provided - if (page) { - data = await getDashboardData(page) - } - - // These only include the points from tasks that the script can complete! - let totalEarnablePoints = 0 - - // Desktop Search Points - data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) - - // Mobile Search Points - data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) - - // Daily Set - data.dailySetPromotions[getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) - - // More Promotions - data.morePromotions.forEach(x => { - // Only count points from supported activities - if (['quiz', 'urlreward'].includes(x.activityType)) { - totalEarnablePoints += (x.pointProgressMax - x.pointProgress) + try { + // Fetch new data if page is provided + if (page) { + data = await getDashboardData(page) } - }) - return totalEarnablePoints + // These only include the points from tasks that the script can complete! + let totalEarnablePoints = 0 + + // Desktop Search Points + data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) + + // Mobile Search Points + if (data.userStatus.counters.mobileSearch?.length) { + data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) + } + + // Daily Set + data.dailySetPromotions[getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress)) + + // More Promotions + data.morePromotions.forEach(x => { + // Only count points from supported activities + if (['quiz', 'urlreward'].includes(x.activityType)) { + totalEarnablePoints += (x.pointProgressMax - x.pointProgress) + } + }) + + return totalEarnablePoints + } catch (error) { + throw log('GET-EARNABLE-POINTS', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') + } } export async function loadSesion(email: string): Promise { @@ -176,4 +186,23 @@ export async function loadSesion(email: string): Promise { } catch (error) { throw new Error(error as string) } +} + +export async function waitForQuizRefresh(page: Page) { + try { + await page.waitForSelector('#rqHeaderCredits', { timeout: 5000 }) + return true + } catch (error) { + log('QUIZ-REFRESH', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') + return false + } +} + +export async function checkQuizCompleted(page: Page) { + try { + await page.waitForSelector('#quizCompleteContainer', { timeout: 1000 }) + return true + } catch (error) { + return false + } } \ No newline at end of file diff --git a/src/BrowserUtil.ts b/src/BrowserUtil.ts index 2ad0f32..c15b368 100644 --- a/src/BrowserUtil.ts +++ b/src/BrowserUtil.ts @@ -1,4 +1,5 @@ import { Page } from 'puppeteer' + import { wait } from './util/Utils' export async function tryDismissAllMessages(page: Page): Promise { @@ -15,7 +16,7 @@ export async function tryDismissAllMessages(page: Page): Promise { for (const button of buttons) { try { - const element = await page.waitForSelector(button.selector, { timeout: 1000 }) + const element = await page.waitForSelector(button.selector, { visible: true, timeout: 1000 }) if (element) { await element.click() result = true diff --git a/src/functions/DailySet.ts b/src/functions/DailySet.ts index 9f60cfa..fba5364 100644 --- a/src/functions/DailySet.ts +++ b/src/functions/DailySet.ts @@ -1,11 +1,14 @@ import { Page } from 'puppeteer' -import { DashboardData } from '../interface/DashboardData' -import { doPoll } from './activities/Poll' -import { getFormattedDate, wait } from '../util/Utils' -import { doQuiz } from './activities/Quiz' -import { log } from '../util/Logger' -import { doUrlReward } from './activities/UrlReward' +import { doPoll } from './activities/Poll' +import { doQuiz } from './activities/Quiz' +import { doUrlReward } from './activities/UrlReward' +import { doThisOrThat } from './activities/ThisOrThat' + +import { getFormattedDate, wait } from '../util/Utils' +import { log } from '../util/Logger' + +import { DashboardData } from '../interface/DashboardData' export async function doDailySet(page: Page, data: DashboardData) { const todayData = data.dailySetPromotions[getFormattedDate()] @@ -19,6 +22,12 @@ export async function doDailySet(page: Page, data: DashboardData) { for (const activity of activitiesUncompleted) { log('DAILY-SET', 'Started doing daily set items') + + // If activity does not give points, skip + if (activity.pointProgressMax <= 0) { + continue + } + switch (activity.promotionType) { // Quiz (Poll/Quiz) case 'quiz': @@ -30,6 +39,12 @@ export async function doDailySet(page: Page, data: DashboardData) { await doPoll(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') diff --git a/src/functions/Login.ts b/src/functions/Login.ts index 548256a..4660483 100644 --- a/src/functions/Login.ts +++ b/src/functions/Login.ts @@ -6,8 +6,8 @@ const rl = readline.createInterface({ output: process.stdout }) -import { wait } from '../util/Utils' import { tryDismissAllMessages, tryDismissBingCookieBanner } from '../BrowserUtil' +import { wait } from '../util/Utils' import { log } from '../util/Logger' export async function login(page: Page, email: string, password: string) { @@ -34,7 +34,7 @@ export async function login(page: Page, email: string, password: string) { log('LOGIN', 'Logged in successfully') } catch (error) { - log('LOGIN', 'An error occurred:' + error, 'error') + log('LOGIN', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } @@ -103,7 +103,7 @@ async function checkBingLogin(page: Page): Promise { } } catch (error) { - log('LOGIN-BING', 'An error occurred:' + error, 'error') + log('LOGIN-BING', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } diff --git a/src/functions/MorePromotions.ts b/src/functions/MorePromotions.ts index ecf746f..859c2af 100644 --- a/src/functions/MorePromotions.ts +++ b/src/functions/MorePromotions.ts @@ -1,23 +1,30 @@ import { Page } from 'puppeteer' -import { DashboardData } from '../interface/DashboardData' + import { doPoll } from './activities/Poll' import { doQuiz } from './activities/Quiz' -import { log } from '../util/Logger' import { doUrlReward } from './activities/UrlReward' -import { wait } from '../util/Utils' +import { doThisOrThat } from './activities/ThisOrThat' +import { wait } from '../util/Utils' +import { log } from '../util/Logger' + +import { DashboardData } from '../interface/DashboardData' export async function doMorePromotions(page: Page, data: DashboardData) { const morePromotions = data.morePromotions const activitiesUncompleted = morePromotions?.filter(x => !x.complete) ?? [] - + if (!activitiesUncompleted.length) { 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 + } switch (activity.promotionType) { // Quiz (Poll/Quiz) @@ -30,6 +37,12 @@ export async function doMorePromotions(page: Page, data: DashboardData) { await doPoll(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') diff --git a/src/functions/PunchCard.ts b/src/functions/PunchCard.ts new file mode 100644 index 0000000..80b4c1d --- /dev/null +++ b/src/functions/PunchCard.ts @@ -0,0 +1,72 @@ +import { Page } from 'puppeteer' + +import { doPoll } from './activities/Poll' +import { doQuiz } from './activities/Quiz' +import { doUrlReward } from './activities/UrlReward' +import { doThisOrThat } from './activities/ThisOrThat' + +import { wait } from '../util/Utils' +import { log } from '../util/Logger' + +import { DashboardData } from '../interface/DashboardData' + +export async function doPunchCard(page: Page, data: DashboardData) { + + const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // filter out the uncompleted punch cards + + if (!punchCardsUncompleted.length) { + log('PUNCH-CARD', 'All punch cards have already been completed') + return + } + + // Todo + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const activitiesUncompleted: any = '' + + for (const activity of activitiesUncompleted) { + log('PUNCH-CARD', 'Started doing daily set items') + + // If activity does not give points, skip + if (activity.pointProgressMax <= 0) { + continue + } + + switch (activity.promotionType) { + // Quiz (Poll/Quiz) + case 'quiz': + + switch (activity.pointProgressMax) { + // Poll (Usually 10 points) + case 10: + log('ACTIVITY', 'Found daily activity type: Poll') + await doPoll(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('PUNCH-CARD', 'Punch card items have been completed') +} \ No newline at end of file diff --git a/src/functions/activities/Poll.ts b/src/functions/activities/Poll.ts index 397ea48..461df33 100644 --- a/src/functions/activities/Poll.ts +++ b/src/functions/activities/Poll.ts @@ -1,9 +1,11 @@ import { Page } from 'puppeteer' -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' + import { getLatestTab } from '../../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) { log('POLL', 'Trying to complete poll') @@ -18,7 +20,7 @@ export async function doPoll(page: Page, data: PromotionalItem | MorePromotion) const buttonId = `#btoption${Math.floor(randomNumber(0, 1))}` - await pollPage.waitForSelector(buttonId) + await pollPage.waitForSelector(buttonId, { visible: true, timeout: 5000 }) await pollPage.click(buttonId) await wait(2000) @@ -26,6 +28,6 @@ export async function doPoll(page: Page, data: PromotionalItem | MorePromotion) log('POLL', 'Completed the poll successfully') } catch (error) { - log('POLL', 'An error occurred:' + error, 'error') + log('POLL', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } \ No newline at end of file diff --git a/src/functions/activities/Quiz.ts b/src/functions/activities/Quiz.ts index 75832d0..342c2f9 100644 --- a/src/functions/activities/Quiz.ts +++ b/src/functions/activities/Quiz.ts @@ -1,10 +1,12 @@ import { Page } from 'puppeteer' -import { MorePromotion, PromotionalItem } from '../../interface/DashboardData' -import { getQuizData } from '../../BrowserFunc' -import { wait } from '../../util/Utils' + import { getLatestTab } from '../../BrowserUtil' +import { getQuizData, waitForQuizRefresh } from '../../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) { log('QUIZ', 'Trying to complete quiz') @@ -28,7 +30,6 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) await wait(2000) const quizData = await getQuizData(quizPage) - const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining // All questions @@ -38,7 +39,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}`) + const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption')) await wait(500) @@ -49,7 +50,7 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) // Click the answers for (const answer of answers) { - await wait(2000) + await quizPage.waitForSelector(answer, { visible: true, timeout: 2000 }) // Click the answer on page await quizPage.click(answer) @@ -68,7 +69,7 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) for (let i = 0; i < quizData.numberOfOptions; i++) { - const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`) + const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option')) if (dataOption === correctOption) { @@ -97,27 +98,7 @@ export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) } catch (error) { const quizPage = await getLatestTab(page) await quizPage.close() - log('QUIZ', 'An error occurred:' + error, 'error') + log('QUIZ', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } -} - -async function waitForQuizRefresh(page: Page) { - try { - await page.waitForSelector('#rqHeaderCredits', { timeout: 5000 }) - return true - } catch (error) { - log('QUIZ-REFRESH', 'An error occurred:' + error, 'error') - return false - } -} - -async function checkQuizCompleted(page: Page) { - try { - await page.waitForSelector('#quizCompleteContainer', { timeout: 1000 }) - return true - } catch (error) { - return false - } -} -checkQuizCompleted \ No newline at end of file +} \ No newline at end of file diff --git a/src/functions/activities/Search.ts b/src/functions/activities/Search.ts index 6695857..8cb0933 100644 --- a/src/functions/activities/Search.ts +++ b/src/functions/activities/Search.ts @@ -1,16 +1,16 @@ import { Page } from 'puppeteer' import axios from 'axios' +import { getLatestTab } from '../../BrowserUtil' +import { getSearchPoints } from '../../BrowserFunc' import { log } from '../../util/Logger' import { randomNumber, shuffleArray, wait } from '../../util/Utils' -import { getSearchPoints } from '../../BrowserFunc' import { searches } from '../../config.json' import { DashboardData, DashboardImpression } from '../../interface/DashboardData' import { GoogleTrends } from '../../interface/GoogleDailyTrends' import { GoogleSearch } from '../../interface/Search' -import { getLatestTab } from '../../BrowserUtil' export async function doSearch(page: Page, data: DashboardData, mobile: boolean) { const locale = await page.evaluate(() => { @@ -19,11 +19,11 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean) log('SEARCH-BING', 'Starting bing searches') - const mobileData = data.userStatus.counters.mobileSearch[0] as DashboardImpression // Mobile searches + 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 ? + let missingPoints = (mobile && mobileData) ? (mobileData.pointProgressMax - mobileData.pointProgress) : (edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress) @@ -32,7 +32,7 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean) return } - // Generate search queries + // Generate search queries let googleSearchQueries = await getGoogleTrends(locale, missingPoints) as GoogleSearch[] googleSearchQueries = shuffleArray(googleSearchQueries) @@ -59,11 +59,11 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean) const newData = await bingSearch(page, searchPage, query, mobile) - const newMobileData = newData.mobileSearch[0] as DashboardImpression // Mobile searches + 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 ? + const newMissingPoints = (mobile && newMobileData) ? (newMobileData.pointProgressMax - newMobileData.pointProgress) : (newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress) @@ -103,11 +103,11 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean) log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`) const newData = await bingSearch(page, searchPage, query.topic, mobile) - const newMobileData = newData.mobileSearch[0] as DashboardImpression // Mobile searches + 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 ? + const newMissingPoints = (mobile && newMobileData) ? (newMobileData.pointProgressMax - newMobileData.pointProgress) : (newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress) @@ -143,7 +143,7 @@ async function bingSearch(page: Page, searchPage: Page, query: string, mobile: b for (let i = 0; i < 5; i++) { try { const searchBar = '#sb_form_q' - await searchPage.waitForSelector(searchBar, { timeout: 3000 }) + await searchPage.waitForSelector(searchBar, { visible: true, timeout: 3000 }) await searchPage.click(searchBar) // Focus on the textarea await wait(500) await searchPage.keyboard.down('Control') @@ -169,10 +169,11 @@ async function bingSearch(page: Page, searchPage: Page, query: string, mobile: b } catch (error) { if (i === 5) { - log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error') - return await getSearchPoints(page) + log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + JSON.stringify(error, null, 2), 'error') + break + } - log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error') + log('SEARCH-BING', 'Search failed, An error occurred:' + JSON.stringify(error, null, 2), 'error') log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn') await wait(4000) @@ -214,7 +215,7 @@ async function getGoogleTrends(locale: string, queryCount: number): Promise { return response.data[1] as string[] } catch (error) { - log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error') + log('SEARCH-BING-RELTATED', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } return [] } @@ -255,7 +256,7 @@ async function randomScroll(page: Page) { await page.keyboard.press('ArrowDown') } } catch (error) { - log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error') + log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } @@ -301,6 +302,6 @@ async function clickRandomLink(page: Page, mobile: boolean) { } } } catch (error) { - log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error') + log('SEARCH-RANDOM-CLICK', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } \ No newline at end of file diff --git a/src/functions/activities/ThisOrThat.ts b/src/functions/activities/ThisOrThat.ts new file mode 100644 index 0000000..92d9261 --- /dev/null +++ b/src/functions/activities/ThisOrThat.ts @@ -0,0 +1,39 @@ +import { Page } from 'puppeteer' + +import { getLatestTab } from '../../BrowserUtil' +import { wait } from '../../util/Utils' +import { log } from '../../util/Logger' + +import { PromotionalItem, MorePromotion } from '../../interface/DashboardData' + +export async function doThisOrThat(page: Page, data: PromotionalItem | MorePromotion) { + return // Todo + log('THIS-OR-THAT', 'Trying to complete ThisOrThat') + + try { + const selector = `[data-bi-id="${data.offerId}"]` + + // Wait for page to load and click to load the this or that quiz in a new tab + await page.waitForSelector(selector, { timeout: 5000 }) + await page.click(selector) + + const thisorthatPage = await getLatestTab(page) + + // Check if the quiz has been started or not + const quizNotStarted = await thisorthatPage.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) + if (quizNotStarted) { + await thisorthatPage.click('#rqStartQuiz') + } else { + log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it') + } + + await wait(2000) + + // Solving + + log('THIS-OR-THAT', 'Completed the ThisOrthat successfully') + } catch (error) { + log('THIS-OR-THAT', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') + } + +} \ No newline at end of file diff --git a/src/functions/activities/UrlReward.ts b/src/functions/activities/UrlReward.ts index 0089933..57cace6 100644 --- a/src/functions/activities/UrlReward.ts +++ b/src/functions/activities/UrlReward.ts @@ -1,8 +1,9 @@ import { Page } from 'puppeteer' + import { getLatestTab } from '../../BrowserUtil' import { log } from '../../util/Logger' -import { PromotionalItem, MorePromotion } from '../../interface/DashboardData' +import { PromotionalItem, MorePromotion } from '../../interface/DashboardData' export async function doUrlReward(page: Page, data: PromotionalItem | MorePromotion) { log('URL-REWARD', 'Trying to complete UrlReward') @@ -20,7 +21,7 @@ export async function doUrlReward(page: Page, data: PromotionalItem | MorePromot log('URL-REWARD', 'Completed the UrlReward successfully') } catch (error) { - log('URL-REWARD', 'An error occurred:' + error, 'error') + log('URL-REWARD', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 5da81de..21fcd01 100644 --- a/src/index.ts +++ b/src/index.ts @@ -99,6 +99,14 @@ async function Mobile(account: Account) { const data = await getDashboardData(page) + // 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') + + // Close mobile browser + return await browser.close() + } + // Do mobile searches if (searches.doMobile) { await doSearch(page, data, true) diff --git a/src/interface/DashboardData.ts b/src/interface/DashboardData.ts index 1a7a8ac..91bb642 100644 --- a/src/interface/DashboardData.ts +++ b/src/interface/DashboardData.ts @@ -1,11 +1,10 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ export interface DashboardData { userStatus: UserStatus; promotionalItem: PromotionalItem; dailySetPromotions: { [key: string]: PromotionalItem[] }; streakPromotion: StreakPromotion; streakBonusPromotions: StreakBonusPromotion[]; - punchCards: any[]; + punchCards: PunchCard[]; dashboardFlights: DashboardFlights; morePromotions: MorePromotion[]; suggestedRewards: AutoRedeemItem[]; @@ -188,7 +187,7 @@ export interface Slide { aboutPageLink: null; redeemLink: null; rewardsLink: null; - quizLinks?: any[]; + quizLinks?: string[]; quizCorrectAnswerTitle?: string; quizWrongAnswerTitle?: string; quizAnswerDescription?: string; @@ -210,7 +209,7 @@ export interface PromotionalItem { activityProgressMax: number; pointProgressMax: number; pointProgress: number; - promotionType: string; + promotionType: Type; promotionSubtype: string; title: string; extBannerTitle: string; @@ -248,9 +247,9 @@ export interface PromotionalItem { } export interface PromotionalItemAttributes { - animated_icon: string; + animated_icon?: string; bg_image: string; - complete: string; + complete: GiveEligible; daily_set_date?: string; description: string; destination: string; @@ -263,22 +262,43 @@ export interface PromotionalItemAttributes { sc_bg_image: string; sc_bg_large_image: string; small_image: string; - state: string; + state: State; title: string; - type: string; - give_eligible: string; - sc_title?: string; - sc_description?: string; - legal_text?: string; - legal_link_text?: string; - promotional?: string; + type: Type; + give_eligible: GiveEligible; + activity_max?: string; + activity_progress?: string; + is_wot?: GiveEligible; + offer_counter?: string; + promotional?: GiveEligible; + parentPunchcards?: string; + 'classification.DescriptionText'?: string; + 'classification.PunchcardChildrenCount'?: string; + 'classification.PunchcardEndDate'?: Date; + 'classification.Template'?: string; + 'classification.TitleText'?: string; +} + +export enum GiveEligible { + False = 'False', + True = 'True' +} + +export enum State { + Default = 'Default' +} + +export enum Type { + Quiz = 'quiz', + Urlreward = 'urlreward', + UrlrewardUrlrewardUrlrewardUrlrewardUrlreward = 'urlreward,urlreward,urlreward,urlreward,urlreward' } export interface DashboardFlights { dashboardbannernav: string; togglegiveuser: string; spotifyRedirect: string; - give_eligible: string; + give_eligible: GiveEligible; destination: string; } @@ -333,6 +353,12 @@ export interface MorePromotion { deviceType: string; } +export interface PunchCard { + name: string; + parentPromotion: PromotionalItem; + childPromotions: PromotionalItem[]; +} + export interface StreakBonusPromotion { name: string; priority: number; @@ -382,17 +408,18 @@ export interface StreakBonusPromotion { } export interface StreakBonusPromotionAttributes { - hidden: string; + hidden: GiveEligible; type: string; title: string; description: string; + description_localizedkey: string; image: string; animated_icon: string; activity_progress: string; activity_max: string; bonus_earned: string; break_description: string; - give_eligible: string; + give_eligible: GiveEligible; destination: string; } @@ -449,7 +476,7 @@ export interface StreakPromotion { } export interface StreakPromotionAttributes { - hidden: string; + hidden: GiveEligible; type: string; title: string; image: string; @@ -458,7 +485,7 @@ export interface StreakPromotionAttributes { break_image: string; lifetime_max: string; bonus_points: string; - give_eligible: string; + give_eligible: GiveEligible; destination: string; } @@ -511,8 +538,8 @@ export interface UserInterests { } export interface UserInterestsAttributes { - hidden: string; - give_eligible: string; + hidden: GiveEligible; + give_eligible: GiveEligible; destination: string; } @@ -543,10 +570,9 @@ export interface UserProfileAttributes { epuid_upd: Date; waitlistattributes: string; waitlistattributes_upd: Date; - serpbotscore: string; - serpbotscore_upd: Date; - iscashbackeligible: string; - give_user: string; + cbedc: GiveEligible; + iscashbackeligible: GiveEligible; + give_user: GiveEligible; } export interface UserStatus { @@ -571,7 +597,7 @@ export interface UserStatus { export interface Counters { pcSearch: DashboardImpression[]; - mobileSearch: DashboardImpression[]; + mobileSearch?: DashboardImpression[]; shopAndEarn: DashboardImpression[]; activityAndQuiz: ActivityAndQuiz[]; dailyPoint: DashboardImpression[]; @@ -634,9 +660,9 @@ export interface ActivityAndQuizAttributes { image: string; recurring: string; destination: string; - 'classification.ShowProgress': string; - hidden: string; - give_eligible: string; + 'classification.ShowProgress': GiveEligible; + hidden: GiveEligible; + give_eligible: GiveEligible; } export interface LastOrder { @@ -669,5 +695,5 @@ export interface ReferrerProgressInfo { pointsEarned: number; pointsMax: number; isComplete: boolean; - promotions: any[]; + promotions: string[]; } diff --git a/src/util/UserAgent.ts b/src/util/UserAgent.ts index c59006b..21864db 100644 --- a/src/util/UserAgent.ts +++ b/src/util/UserAgent.ts @@ -1,5 +1,7 @@ import axios from 'axios' + import { log } from './Logger' + import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil' export async function getUserAgent(mobile: boolean) { @@ -49,7 +51,7 @@ export async function getChromeVersion(): Promise { return data.channels.Stable.version } catch (error) { - throw log('USERAGENT-CHROME-VERSION', 'An error occurred:' + error, 'error') + throw log('USERAGENT-CHROME-VERSION', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } @@ -73,7 +75,7 @@ export async function getEdgeVersions() { } catch (error) { - throw log('USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error') + throw log('USERAGENT-EDGE-VERSION', 'An error occurred:' + JSON.stringify(error, null, 2), 'error') } } diff --git a/src/util/Webhook.ts b/src/util/Webhook.ts index b1a4125..a06fb84 100644 --- a/src/util/Webhook.ts +++ b/src/util/Webhook.ts @@ -3,9 +3,8 @@ import axios from 'axios' import { webhook } from '../config.json' export async function Webhook(content: string) { - if (!webhook.enabled) return - if (webhook.url.length < 10) return - + if (!webhook.enabled || webhook.url.length < 10) return + const request = { method: 'POST', url: webhook.url,