mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 22:13:58 +00:00
* fix misspelling * fix: avoid skipping eligible items in "More Promotion" * remove unnecessary output * Fix Formatting * replace checking variable * decrease id length * revert previous commit * change condition for eligible promo * Other fixes --------- Co-authored-by: TheNetsky <56271887+TheNetsky@users.noreply.github.com>
320 lines
12 KiB
TypeScript
320 lines
12 KiB
TypeScript
import { Page } from 'playwright'
|
|
import { CheerioAPI, load } from 'cheerio'
|
|
import axios, { AxiosRequestConfig } from 'axios'
|
|
|
|
import { MicrosoftRewardsBot } from '../index'
|
|
|
|
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
|
import { QuizData } from './../interface/QuizData'
|
|
import { AppUserData } from '../interface/AppUserData'
|
|
|
|
|
|
export default class BrowserFunc {
|
|
private bot: MicrosoftRewardsBot
|
|
|
|
constructor(bot: MicrosoftRewardsBot) {
|
|
this.bot = bot
|
|
}
|
|
|
|
|
|
/**
|
|
* Navigate the provided page to rewards homepage
|
|
* @param {Page} page Playwright page
|
|
*/
|
|
async goHome(page: Page) {
|
|
|
|
try {
|
|
const dashboardURL = new URL(this.bot.config.baseURL)
|
|
|
|
if (page.url() === dashboardURL.href) {
|
|
return
|
|
}
|
|
|
|
await page.goto(this.bot.config.baseURL)
|
|
|
|
const maxIterations = 5 // Maximum iterations set to 5
|
|
|
|
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
|
await this.bot.utils.wait(3000)
|
|
await this.bot.browser.utils.tryDismissCookieBanner(page)
|
|
|
|
// Check if account is suspended
|
|
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
|
|
if (isSuspended) {
|
|
this.bot.log('GO-HOME', 'This account is suspended!', 'error')
|
|
throw new Error('Account has been suspended!')
|
|
}
|
|
|
|
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) {
|
|
await this.bot.browser.utils.tryDismissAllMessages(page)
|
|
|
|
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)
|
|
}
|
|
|
|
} catch (error) {
|
|
throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fetch user dashboard data
|
|
* @returns {DashboardData} Object of user bing rewards dashboard data
|
|
*/
|
|
async getDashboardData(): Promise<DashboardData> {
|
|
const dashboardURL = new URL(this.bot.config.baseURL)
|
|
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(this.bot.homePage)
|
|
}
|
|
|
|
// Reload the page to get new data
|
|
await this.bot.homePage.reload({ waitUntil: 'domcontentloaded' })
|
|
|
|
const scriptContent = await this.bot.homePage.evaluate(() => {
|
|
const scripts = Array.from(document.querySelectorAll('script'))
|
|
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
|
|
|
return targetScript?.innerText ? targetScript.innerText : null
|
|
})
|
|
|
|
if (!scriptContent) {
|
|
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
|
}
|
|
|
|
// Extract the dashboard object from the script content
|
|
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
|
// Extract the dashboard object using regex
|
|
const regex = /var dashboard = (\{.*?\});/s
|
|
const match = regex.exec(scriptContent)
|
|
|
|
if (match && match[1]) {
|
|
return JSON.parse(match[1])
|
|
}
|
|
}, scriptContent)
|
|
|
|
if (!dashboardData) {
|
|
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
|
}
|
|
|
|
return dashboardData
|
|
}
|
|
|
|
/**
|
|
* Get search point counters
|
|
* @returns {Counters} Object of search counter data
|
|
*/
|
|
async getSearchPoints(): Promise<Counters> {
|
|
const dashboardData = await this.getDashboardData() // Always fetch newest data
|
|
|
|
return dashboardData.userStatus.counters
|
|
}
|
|
|
|
/**
|
|
* Get total earnable points with web browser
|
|
* @returns {number} Total earnable points
|
|
*/
|
|
async getBrowserEarnablePoints(): Promise<number> {
|
|
try {
|
|
const data = await this.getDashboardData()
|
|
|
|
// These only include the points from tasks that the script can complete!
|
|
let totalEarnablePoints = 0
|
|
|
|
// Desktop Search Points
|
|
if (data.userStatus.counters.pcSearch?.length) {
|
|
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[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
|
|
|
// More Promotions
|
|
if (data.morePromotions?.length) {
|
|
data.morePromotions.forEach(x => {
|
|
// Only count points from supported activities
|
|
if (['quiz', 'urlreward'].includes(x.promotionType) && x.exclusiveLockedFeatureStatus !== 'locked') {
|
|
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
|
}
|
|
})
|
|
}
|
|
|
|
return totalEarnablePoints
|
|
} catch (error) {
|
|
throw this.bot.log('GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get total earnable points with mobile app
|
|
* @returns {number} Total earnable points
|
|
*/
|
|
async getAppEarnablePoints(accessToken: string): Promise<number> {
|
|
try {
|
|
const eligibleOffers = [
|
|
'ENUS_readarticle3_30points',
|
|
'Gamification_Sapphire_DailyCheckIn'
|
|
]
|
|
let totalEarnablePoints = 0
|
|
|
|
const data = await this.getDashboardData()
|
|
let geoLocale = data.userProfile.attributes.country
|
|
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us'
|
|
|
|
const userDataRequest: AxiosRequestConfig = {
|
|
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613',
|
|
method: 'GET',
|
|
headers: {
|
|
'Authorization': `Bearer ${accessToken}`,
|
|
'X-Rewards-Country': geoLocale,
|
|
'X-Rewards-Language': 'en'
|
|
}
|
|
}
|
|
|
|
const userDataResponse: AppUserData = (await axios(userDataRequest)).data
|
|
const userData = userDataResponse.response
|
|
const eligibleActivities = userData.promotions.filter((x) => eligibleOffers.includes(x.attributes.offerid ?? ''))
|
|
|
|
for (const item of eligibleActivities) {
|
|
if (item.attributes.type === 'msnreadearn') {
|
|
totalEarnablePoints += parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '')
|
|
break
|
|
} else if (item.attributes.type === 'checkin') {
|
|
const checkInDay = parseInt(item.attributes.progress ?? '') % 7
|
|
|
|
if (checkInDay < 6 && (new Date()).getDate() != (new Date(item.attributes.last_updated ?? '')).getDate()) {
|
|
totalEarnablePoints += parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '')
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
return totalEarnablePoints
|
|
} catch (error) {
|
|
throw this.bot.log('GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current point amount
|
|
* @returns {number} Current total point amount
|
|
*/
|
|
async getCurrentPoints(): Promise<number> {
|
|
try {
|
|
const data = await this.getDashboardData()
|
|
|
|
return data.userStatus.availablePoints
|
|
} catch (error) {
|
|
throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse quiz data from provided page
|
|
* @param {Page} page Playwright page
|
|
* @returns {QuizData} Quiz data object
|
|
*/
|
|
async getQuizData(page: Page): Promise<QuizData> {
|
|
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 waitForQuizRefresh(page: Page): Promise<boolean> {
|
|
try {
|
|
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 })
|
|
await this.bot.utils.wait(2000)
|
|
|
|
return true
|
|
} catch (error) {
|
|
this.bot.log('QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
|
return false
|
|
}
|
|
}
|
|
|
|
async checkQuizCompleted(page: Page): Promise<boolean> {
|
|
try {
|
|
await page.waitForSelector('#quizCompleteContainer', { state: 'visible', timeout: 2000 })
|
|
await this.bot.utils.wait(2000)
|
|
|
|
return true
|
|
} catch (error) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async refreshCheerio(page: Page): Promise<CheerioAPI> {
|
|
const html = await page.content()
|
|
const $ = load(html)
|
|
|
|
return $
|
|
}
|
|
|
|
async getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise<string> {
|
|
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) {
|
|
this.bot.log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
|
|
}
|
|
|
|
return selector
|
|
}
|
|
|
|
} |