This commit is contained in:
TheNetsky
2023-09-25 12:16:40 +02:00
parent 0a675dd6e0
commit c802492f18
27 changed files with 2115 additions and 1 deletions

40
src/Browser.ts Normal file
View File

@@ -0,0 +1,40 @@
import puppeteer from 'puppeteer-extra'
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
import { getUserAgent } from './util/UserAgent'
import { loadSesion } from './BrowserFunc'
puppeteer.use(StealthPlugin())
export async function Browser(email: string, headless = false) {
const userAgent = await getUserAgent(false)
const browser = await puppeteer.launch({
headless: headless, // Set to true for a headless browser
userDataDir: await loadSesion(email),
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
`--user-agent=${userAgent.userAgent}`
]
})
return browser
}
export async function mobileBrowser(email: string, headless = false) {
const userAgent = await getUserAgent(true)
const browser = await puppeteer.launch({
headless: headless, // Set to true for a headless browser,
userDataDir: await loadSesion(email),
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
`--user-agent=${userAgent.userAgent}`,
'--window-size=568,1024'
]
})
return browser
}

130
src/BrowserFunc.ts Normal file
View File

@@ -0,0 +1,130 @@
import { Page } from 'puppeteer'
import fs from 'fs'
import path from 'path'
import { baseURL, sessionPath } from './config.json'
import { wait } from './util/Utils'
import { tryDismissAllMessages, tryDismissCookieBanner } from './BrowserUtil'
import { log } from './util/Logger'
import { Counters, DashboardData } from './interface/DashboardData'
import { QuizData } from './interface/QuizData'
export async function goHome(page: Page): Promise<void> {
try {
const targetUrl = new URL(baseURL)
await page.goto(baseURL)
const maxIterations = 5 // Maximum iterations set to 5
for (let iteration = 1; iteration <= maxIterations; iteration++) {
await wait(3000)
await tryDismissCookieBanner(page)
try {
// If activities are found, exit the loop
await page.waitForSelector('#more-activities', { timeout: 1000 })
break
} catch (error) {
// Continue if element is not found
}
const currentUrl = new URL(page.url())
if (currentUrl.hostname !== targetUrl.hostname) {
await tryDismissAllMessages(page)
await wait(2000)
await page.goto(baseURL)
}
await wait(5000)
log('MAIN', 'Visited homepage successfully')
}
} catch (error) {
console.error('An error occurred:', error)
}
}
export async function getDashboardData(page: Page): Promise<DashboardData> {
await page.reload({ waitUntil: 'networkidle2' })
const scriptContent = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script'))
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
if (targetScript) {
return targetScript.innerText
} else {
throw new Error('Script containing dashboard data not found')
}
})
// Extract the dashboard object from the script content
const dashboardData = await page.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])
} else {
throw new Error('Dashboard data not found in the script')
}
}, scriptContent)
return dashboardData
}
export async function getSearchPoints(page: Page): Promise<Counters> {
const dashboardData = await getDashboardData(page)
return dashboardData.userStatus.counters
}
export async function getQuizData(page: Page): Promise<QuizData> {
const scriptContent = await page.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script'))
const targetScript = scripts.find(script => script.innerText.includes('_w.rewardsQuizRenderInfo'))
if (targetScript) {
return targetScript.innerText
} else {
throw new Error('Script containing quiz data not found')
}
})
const quizData = await page.evaluate(scriptContent => {
// Extract the dashboard object using regex
const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
const match = regex.exec(scriptContent)
if (match && match[1]) {
return JSON.parse(match[1])
} else {
throw new Error('Dashboard data not found in the script')
}
}, scriptContent)
return quizData
}
export async function loadSesion(email: string): Promise<string> {
const sessionDir = path.join(__dirname, sessionPath, email)
try {
// Create session dir
if (!fs.existsSync(sessionDir)) {
await fs.promises.mkdir(sessionDir, { recursive: true })
}
return sessionDir
} catch (error) {
throw new Error(error as string)
}
}

70
src/BrowserUtil.ts Normal file
View File

@@ -0,0 +1,70 @@
import { Page } from 'puppeteer'
import { wait } from './util/Utils'
export async function tryDismissAllMessages(page: Page): Promise<boolean> {
const buttons = [
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
{ selector: '#iShowSkip', label: 'iShowSkip' },
{ selector: '#iNext', label: 'iNext' },
{ selector: '#iLooksGood', label: 'iLooksGood' },
{ selector: '#idSIButton9', label: 'idSIButton9' },
{ selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' }
]
let result = false
for (const button of buttons) {
try {
const element = await page.waitForSelector(button.selector, { timeout: 1000 })
if (element) {
await element.click()
result = true
}
} catch (error) {
continue
}
}
return result
}
export async function tryDismissCookieBanner(page: Page): Promise<void> {
try {
await page.waitForSelector('#cookieConsentContainer', { timeout: 1000 })
const cookieBanner = await page.$('#cookieConsentContainer')
if (cookieBanner) {
const button = await cookieBanner.$('button')
if (button) {
await button.click()
await wait(2000)
}
}
} catch (error) {
// Continue if element is not found or other error occurs
}
}
export async function tryDismissBingCookieBanner(page: Page): Promise<void> {
try {
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 })
const cookieBanner = await page.$('#bnp_btn_accept')
if (cookieBanner) {
await cookieBanner.click()
}
} catch (error) {
// Continue if element is not found or other error occurs
}
}
export async function getLatestTab(page: Page) {
await wait(2000)
const browser = page.browser()
const pages = await browser.pages()
const newTab = pages[pages.length - 1] as Page
return newTab
}

10
src/accounts.example.json Normal file
View File

@@ -0,0 +1,10 @@
[
{
"email": "email_1",
"password": "password_1"
},
{
"email": "email_2",
"password": "password_2"
}
]

4
src/config.json Normal file
View File

@@ -0,0 +1,4 @@
{
"baseURL" : "https://rewards.bing.com",
"sessionPath": "sessions"
}

54
src/functions/DailySet.ts Normal file
View File

@@ -0,0 +1,54 @@
import { Page } from 'puppeteer'
import { DashboardData } from '../interface/DashboardData'
import { doPoll } from './activities/Poll'
import { getFormattedDate } from '../util/Utils'
import { doQuiz } from './activities/Quiz'
import { log } from '../util/Logger'
import { doUrlReward } from './activities/UrlReward'
export async function doDailySet(page: Page, data: DashboardData) {
const todayData = data.dailySetPromotions[getFormattedDate()]
const activitiesUncompleted = todayData?.filter(x => !x.complete) ?? []
if (!activitiesUncompleted.length) {
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')
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
// 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
}
}
log('DAILY-SET', 'Daily set items have been completed')
}

117
src/functions/Login.ts Normal file
View File

@@ -0,0 +1,117 @@
import { Page } from 'puppeteer'
import readline from 'readline'
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
import { wait } from '../util/Utils'
import { tryDismissAllMessages, tryDismissBingCookieBanner } from '../BrowserUtil'
import { log } from '../util/Logger'
export async function login(page: Page, email: string, password: string) {
try {
// Navigate to the Bing login page
await page.goto('https://login.live.com/')
const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 5000 }).then(() => true).catch(() => false)
if (!isLoggedIn) {
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
await execLogin(page, email, password)
log('LOGIN', 'Logged into Microsoft successfully')
} else {
log('LOGIN', 'Already logged in')
}
// Check if logged in to bing
await checkBingLogin(page)
// We're done logging in
log('LOGIN', 'Logged in successfully')
} catch (error) {
log('LOGIN', 'An error occurred:' + error, 'error')
}
}
async function execLogin(page: Page, email: string, password: string) {
await page.type('#i0116', email)
await page.click('#idSIButton9')
log('LOGIN', 'Email entered successfully')
try {
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
await wait(2000)
await page.type('#i0118', password)
await page.click('#idSIButton9')
} catch (error) {
log('LOGIN', '2FA code required')
const code = await new Promise<string>((resolve) => {
rl.question('Enter 2FA code:\n', (input) => {
rl.close()
resolve(input)
})
})
await page.type('input[name="otc"]', code)
await page.keyboard.press('Enter')
} finally {
log('LOGIN', 'Password entered successfully')
}
const currentURL = new URL(page.url())
while (currentURL.pathname !== '/' || currentURL.hostname !== 'account.microsoft.com') {
await tryDismissAllMessages(page)
currentURL.href = page.url()
}
// Wait for login to complete
await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 })
}
async function checkBingLogin(page: Page): Promise<void> {
try {
log('LOGIN-BING', 'Verifying Bing login')
await page.goto('https://www.bing.com/fd/auth/signin?action=interactive&provider=windows_live_id&return_url=https%3A%2F%2Fwww.bing.com%2F')
const maxIterations = 5
for (let iteration = 1; iteration <= maxIterations; iteration++) {
const currentUrl = new URL(page.url())
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
await wait(3000)
await tryDismissBingCookieBanner(page)
const loggedIn = await checkBingLoginStatus(page)
if (loggedIn) {
log('LOGIN-BING', 'Bing login verification passed!')
break
}
}
await wait(1000)
}
} catch (error) {
log('LOGIN-BING', 'An error occurred:' + error, 'error')
}
}
async function checkBingLoginStatus(page: Page): Promise<boolean> {
try {
await page.waitForSelector('#id_n', { timeout: 5000 })
return true
} catch (error) {
return false
}
}

View File

@@ -0,0 +1,51 @@
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'
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) {
switch (activity.promotionType) {
// Quiz (Poll/Quiz)
case 'quiz':
switch (activity.pointProgressMax) {
// Poll (Usually 10 points)
case 10:
log('ACTIVITY', 'Found promotion activity type: Poll')
await doPoll(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
}
}
}

View File

@@ -0,0 +1,31 @@
import { Page } from 'puppeteer'
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
import { getLatestTab } from '../../BrowserUtil'
import { log } from '../../util/Logger'
import { wait } from '../../util/Utils'
export async function doPoll(page: Page, data: PromotionalItem | MorePromotion) {
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(Math.random() * 2)}`
await pollPage.waitForSelector(buttonId)
await pollPage.click(buttonId)
await wait(2000)
await pollPage.close()
log('POLL', 'Completed the poll successfully')
} catch (error) {
log('POLL', 'An error occurred:' + error, 'error')
}
}

View File

@@ -0,0 +1,119 @@
import { Page } from 'puppeteer'
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
import { getQuizData } from '../../BrowserFunc'
import { wait } from '../../util/Utils'
import { getLatestTab } from '../../BrowserUtil'
import { log } from '../../util/Logger'
export async function doQuiz(page: Page, data: PromotionalItem | MorePromotion) {
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)
// Check if the quiz has been started or not
const quizNotStarted = await quizPage.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await quizPage.click('#rqStartQuiz')
} else {
log('QUIZ', 'Quiz has already been started, trying to finish it')
}
await wait(2000)
const quizData = await getQuizData(quizPage)
const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining
// All questions
for (let question = 0; question < questionsRemaining; question++) {
if (quizData.numberOfOptions === 8) {
const answers: string[] = []
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`)
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
await wait(500)
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
answers.push(`#rqAnswerOption${i}`)
}
}
for (const answer of answers) {
// Click the answer on page
await quizPage.click(answer)
await wait(1500)
const refreshSuccess = await waitForQuizRefresh(quizPage)
if (!refreshSuccess) {
await quizPage.close()
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return
}
}
// Other type quiz
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
const correctOption = quizData.correctAnswer
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await quizPage.waitForSelector(`#rqAnswerOption${i}`)
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) {
// Click the answer on page
await quizPage.click(`#rqAnswerOption${i}`)
await wait(1500)
const refreshSuccess = await waitForQuizRefresh(quizPage)
if (!refreshSuccess) {
await quizPage.close()
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return
}
}
}
}
}
// Done with
await quizPage.close()
log('QUIZ', 'Completed the quiz successfully')
} catch (error) {
const quizPage = await getLatestTab(page)
await quizPage.close()
log('QUIZ', 'An error occurred:' + error, '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

View File

@@ -0,0 +1,233 @@
import { Page } from 'puppeteer'
import axios from 'axios'
import { log } from '../../util/Logger'
import { shuffleArray, wait } from '../../util/Utils'
import { getSearchPoints } from '../../BrowserFunc'
import { DashboardData, DashboardImpression } from '../../interface/DashboardData'
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
export async function doSearch(page: Page, data: DashboardData, mobile: boolean) {
const locale = await page.evaluate(() => {
return navigator.language
})
log('SEARCH-BING', 'Starting bing searches')
const mobileData = data.userStatus.counters.mobileSearch[0] as DashboardImpression // 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.pointProgressMax - mobileData.pointProgress) :
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
if (missingPoints == 0) {
log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
return
}
// Generate search queries
const googleSearchQueries = shuffleArray(await getGoogleTrends(locale, missingPoints))
// Open a new tab
const browser = page.browser()
const searchPage = await browser.newPage()
// Go to bing
await searchPage.goto('https://bing.com')
let maxLoop = 0 // If the loop hits 20 this when not gaining any points, we're assuming it's stuck.
// Loop over Google search queries
for (let i = 0; i < googleSearchQueries.length; i++) {
const query = googleSearchQueries[i] as string
log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${mobile}`)
const newData = await bingSearch(page, searchPage, query)
const newMobileData = newData.mobileSearch[0] as DashboardImpression // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = mobile ?
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
// If the new point amount is the same as before
if (newMissingPoints == missingPoints) {
maxLoop++ // Add to max loop
} else { // There has been a change in points
maxLoop = 0 // Reset the loop
}
missingPoints = newMissingPoints
if (missingPoints == 0) {
break
}
if (maxLoop > 20) {
log('SEARCH-BING', 'Search didn\'t gain point for 20 iterations aborting searches', 'warn')
maxLoop = 0 // Reset to 0 so we can retry with related searches below
break
}
}
// If we still got remaining search queries, generate extra ones
if (missingPoints > 0) {
log('SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`)
let i = 0
while (missingPoints > 0) {
const query = googleSearchQueries[i++] as string
// Get related search terms to the Google search queries
const relatedTerms = await getRelatedTerms(query)
if (relatedTerms.length > 3) {
// Search for the first 2 related terms
for (const term of relatedTerms.slice(1, 3)) {
log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`)
const newData = await bingSearch(page, searchPage, query)
const newMobileData = newData.mobileSearch[0] as DashboardImpression // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = mobile ?
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
// If the new point amount is the same as before
if (newMissingPoints == missingPoints) {
maxLoop++ // Add to max loop
} else { // There has been a change in points
maxLoop = 0 // Reset the loop
}
missingPoints = newMissingPoints
// If we satisfied the searches
if (missingPoints == 0) {
break
}
// Try 5 more times
if (maxLoop > 5) {
log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
break
}
}
}
}
}
log('SEARCH-BING', 'Completed searches')
}
async function bingSearch(page: Page, searchPage: Page, query: string) {
// Try a max of 5 times
for (let i = 0; i < 5; i++) {
try {
const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { timeout: 3000 })
await searchPage.click(searchBar) // Focus on the textarea
await wait(500)
await searchPage.keyboard.down('Control')
await searchPage.keyboard.press('A')
await searchPage.keyboard.press('Backspace') // Delete the selected text
await searchPage.keyboard.up('Control')
await searchPage.keyboard.type(query)
await searchPage.keyboard.press('Enter')
await wait(Math.floor(Math.random() * (20_000 - 10_000) + 1) + 10_000)
return await getSearchPoints(page)
} catch (error) {
if (i === 5) {
log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
return await getSearchPoints(page)
}
log('SEARCH-BING', 'Search failed, retrying...', 'warn')
await wait(4000)
}
}
log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
return await getSearchPoints(page)
}
async function getGoogleTrends(locale: string, queryCount: number): Promise<string[]> {
const queryTerms: string[] = []
let i = 0
while (queryCount > queryTerms.length) {
i += 1
const date = new Date()
date.setDate(date.getDate() - i)
const formattedDate = formatDate(date)
try {
const request = {
url: `https://trends.google.com/trends/api/dailytrends?geo=US&hl=en&ed=${formattedDate}&ns=15`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
const response = await axios(request)
const data: GoogleTrends = JSON.parse((await response.data).slice(5))
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
queryTerms.push(topic.title.query.toLowerCase())
for (const relatedTopic of topic.relatedQueries) {
queryTerms.push(relatedTopic.query.toLowerCase())
}
}
// Deduplicate the search terms
const uniqueSearchTerms = Array.from(new Set(queryTerms))
queryTerms.length = 0
queryTerms.push(...uniqueSearchTerms)
} catch (error) {
log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
}
}
return queryTerms.slice(0, queryCount)
}
async function getRelatedTerms(term: string): Promise<string[]> {
try {
const request = {
url: `https://api.bing.com/osjson.aspx?query=${term}`,
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
const response = await axios(request)
return response.data[1] as string[]
} catch (error) {
log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
}
return []
}
function formatDate(date: Date): string {
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}${month}${day}`
}

View File

@@ -0,0 +1,26 @@
import { Page } from 'puppeteer'
import { getLatestTab } from '../../BrowserUtil'
import { log } from '../../util/Logger'
import { PromotionalItem, MorePromotion } from '../../interface/DashboardData'
export async function doUrlReward(page: Page, data: PromotionalItem | MorePromotion) {
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.close()
log('URL-REWARD', 'Completed the UrlReward successfully')
} catch (error) {
log('URL-REWARD', 'An error occurred:' + error, 'error')
}
}

82
src/index.ts Normal file
View File

@@ -0,0 +1,82 @@
import { Browser, mobileBrowser } from './Browser'
import { getDashboardData, goHome } from './BrowserFunc'
import { doDailySet } from './functions/DailySet'
import { login } from './functions/Login'
import { doMorePromotions } from './functions/MorePromotions'
import { doSearch } from './functions/activities/Search'
import { log } from './util/Logger'
import accounts from './accounts.json'
import { Account } from './interface/Account'
async function init() {
log('MAIN', 'Bot started')
for (const account of accounts) {
log('MAIN', `Started tasks for account ${account.email}`)
// DailySet, MorePromotions and Desktop Searches
await Desktop(account)
// Mobile Searches
await Mobile(account)
log('MAIN', `Completed tasks for account ${account.email}`)
}
// Clean exit
log('MAIN', 'Bot exited')
process.exit()
}
// Desktop
async function Desktop(account: Account) {
const browser = await Browser(account.email)
const page = await browser.newPage()
// Login into MS Rewards
await login(page, account.email, account.password)
await goHome(page)
const data = await getDashboardData(page)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Complete daily set
await doDailySet(page, data)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Complete more promotions
await doMorePromotions(page, data)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Do desktop searches
await doSearch(page, data, false)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Close desktop browser
await browser.close()
}
async function Mobile(account: Account) {
const browser = await mobileBrowser(account.email)
const page = await browser.newPage()
// Login into MS Rewards
await login(page, account.email, account.password)
await goHome(page)
const data = await getDashboardData(page)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Do mobile searches
await doSearch(page, data, true)
log('MAIN', `Current point count: ${data.userStatus.availablePoints}`)
// Close mobile browser
await browser.close()
}
init()

4
src/interface/Account.ts Normal file
View File

@@ -0,0 +1,4 @@
export interface Account {
email: string;
password: string;
}

View File

@@ -0,0 +1,673 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface DashboardData {
userStatus: UserStatus;
promotionalItem: PromotionalItem;
dailySetPromotions: { [key: string]: PromotionalItem[] };
streakPromotion: StreakPromotion;
streakBonusPromotions: StreakBonusPromotion[];
punchCards: any[];
dashboardFlights: DashboardFlights;
morePromotions: MorePromotion[];
suggestedRewards: AutoRedeemItem[];
coachMarks: CoachMarks;
welcomeTour: WelcomeTour;
userInterests: UserInterests;
isVisualParityTest: boolean;
mbingFlight: null;
langCountryMismatchPromo: null;
machineTranslationPromo: MachineTranslationPromo;
autoRedeemItem: AutoRedeemItem;
userProfile: UserProfile;
}
export interface AutoRedeemItem {
name: null | string;
price: number;
provider: null | string;
disabled: boolean;
category: string;
title: string;
variableGoalSpecificTitle: string;
smallImageUrl: string;
mediumImageUrl: string;
largeImageUrl: string;
largeShowcaseImageUrl: string;
description: Description;
showcase: boolean;
showcaseInAllCategory: boolean;
originalPrice: number;
discountedPrice: number;
popular: boolean;
isTestOnly: boolean;
groupId: string;
inGroup: boolean;
isDefaultItemInGroup: boolean;
groupTitle: string;
groupImageUrl: string;
groupShowcaseImageUrl: string;
instantWinGameId: string;
instantWinPlayAgainSku: string;
isLowInStock: boolean;
isOutOfStock: boolean;
getCodeMessage: string;
disableEmail: boolean;
stockMessage: string;
comingSoonFlag: boolean;
isGenericDonation: boolean;
isVariableRedemptionItem: boolean;
variableRedemptionItemCurrencySymbol: null;
variableRedemptionItemMin: number;
variableRedemptionItemMax: number;
variableItemConfigPointsToCurrencyConversionRatio: number;
isAutoRedeem: boolean;
}
export interface Description {
itemGroupText: string;
smallText: string;
largeText: string;
legalText: string;
showcaseTitle: string;
showcaseDescription: string;
}
export interface CoachMarks {
streaks: WelcomeTour;
}
export interface WelcomeTour {
promotion: DashboardImpression;
slides: Slide[];
}
export interface DashboardImpression {
name: null | string;
priority: number;
attributes: { [key: string]: string } | null;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
benefits?: Benefit[];
supportedLevelKeys?: string[];
supportedLevelTitles?: string[];
supportedLevelTitlesMobile?: string[];
activeLevel?: string;
isCodexAutoJoinUser?: boolean;
}
export interface Benefit {
key: string;
text: string;
url: null | string;
helpText: null | string;
supportedLevels: SupportedLevels;
}
export interface SupportedLevels {
level1?: string;
level2: string;
level2XBoxGold: string;
}
export interface Slide {
slideType: null;
slideShowTourId: string;
id: number;
title: string;
subtitle: null;
subtitle1: null;
description: string;
description1: null;
imageTitle: null;
image2Title: null | string;
image3Title: null | string;
image4Title: null | string;
imageDescription: null;
image2Description: null | string;
image3Description: null | string;
image4Description: null | string;
imageUrl: null | string;
darkImageUrl: null;
image2Url: null | string;
image3Url: null | string;
image4Url: null | string;
layout: null | string;
actionButtonText: null | string;
actionButtonUrl: null | string;
foregroundImageUrl: null;
backLink: null;
nextLink: CloseLink;
closeLink: CloseLink;
footnote: null | string;
termsText: null;
termsUrl: null;
privacyText: null;
privacyUrl: null;
taggedItem: null | string;
slideVisited: boolean;
aboutPageLinkText: null;
aboutPageLink: null;
redeemLink: null;
rewardsLink: null;
quizLinks?: any[];
quizCorrectAnswerTitle?: string;
quizWrongAnswerTitle?: string;
quizAnswerDescription?: string;
}
export interface CloseLink {
text: null | string;
url: null | string;
}
export interface PromotionalItem {
name: string;
priority: number;
attributes: PromotionalItemAttributes;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface PromotionalItemAttributes {
animated_icon: string;
bg_image: string;
complete: string;
daily_set_date?: string;
description: string;
destination: string;
icon: string;
image: string;
link_text: string;
max: string;
offerid: string;
progress: string;
sc_bg_image: string;
sc_bg_large_image: string;
small_image: string;
state: string;
title: string;
type: string;
give_eligible: string;
sc_title?: string;
sc_description?: string;
legal_text?: string;
legal_link_text?: string;
promotional?: string;
}
export interface DashboardFlights {
dashboardbannernav: string;
togglegiveuser: string;
spotifyRedirect: string;
give_eligible: string;
destination: string;
}
export interface MachineTranslationPromo {
}
export interface MorePromotion {
name: string;
priority: number;
attributes: { [key: string]: string };
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface StreakBonusPromotion {
name: string;
priority: number;
attributes: StreakBonusPromotionAttributes;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface StreakBonusPromotionAttributes {
hidden: string;
type: string;
title: string;
description: string;
image: string;
animated_icon: string;
activity_progress: string;
activity_max: string;
bonus_earned: string;
break_description: string;
give_eligible: string;
destination: string;
}
export interface StreakPromotion {
lastUpdatedDate: Date;
breakImageUrl: string;
lifetimeMaxValue: number;
bonusPointsEarned: number;
name: string;
priority: number;
attributes: StreakPromotionAttributes;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface StreakPromotionAttributes {
hidden: string;
type: string;
title: string;
image: string;
activity_progress: string;
last_updated: Date;
break_image: string;
lifetime_max: string;
bonus_points: string;
give_eligible: string;
destination: string;
}
export interface UserInterests {
name: string;
priority: number;
attributes: UserInterestsAttributes;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface UserInterestsAttributes {
hidden: string;
give_eligible: string;
destination: string;
}
export interface UserProfile {
ruid: string;
attributes: UserProfileAttributes;
}
export interface UserProfileAttributes {
publisher: string;
publisher_upd: Date;
creative: string;
creative_upd: Date;
program: string;
program_upd: Date;
country: string;
country_upd: Date;
referrerhash: string;
referrerhash_upd: Date;
optout_upd: Date;
language: string;
language_upd: Date;
target: string;
target_upd: Date;
created: Date;
created_upd: Date;
epuid: string;
epuid_upd: Date;
waitlistattributes: string;
waitlistattributes_upd: Date;
serpbotscore: string;
serpbotscore_upd: Date;
iscashbackeligible: string;
give_user: string;
}
export interface UserStatus {
levelInfo: LevelInfo;
availablePoints: number;
lifetimePoints: number;
lifetimePointsRedeemed: number;
ePuid: string;
redeemGoal: AutoRedeemItem;
counters: Counters;
lastOrder: LastOrder;
dashboardImpression: DashboardImpression;
referrerProgressInfo: ReferrerProgressInfo;
isGiveModeOn: boolean;
giveBalance: number;
firstTimeGiveModeOptIn: null;
giveOrganizationName: string;
lifetimeGivingPoints: number;
isRewardsUser: boolean;
isMuidTrialUser: boolean;
}
export interface Counters {
pcSearch: DashboardImpression[];
mobileSearch: DashboardImpression[];
shopAndEarn: DashboardImpression[];
activityAndQuiz: ActivityAndQuiz[];
dailyPoint: DashboardImpression[];
}
export interface ActivityAndQuiz {
name: string;
priority: number;
attributes: ActivityAndQuizAttributes;
offerId: string;
complete: boolean;
counter: number;
activityProgress: number;
activityProgressMax: number;
pointProgressMax: number;
pointProgress: number;
promotionType: string;
promotionSubtype: string;
title: string;
extBannerTitle: string;
titleStyle: string;
theme: string;
description: string;
extBannerDescription: string;
descriptionStyle: string;
showcaseTitle: string;
showcaseDescription: string;
imageUrl: string;
dynamicImage: string;
smallImageUrl: string;
backgroundImageUrl: string;
showcaseBackgroundImageUrl: string;
showcaseBackgroundLargeImageUrl: string;
promotionBackgroundLeft: string;
promotionBackgroundRight: string;
iconUrl: string;
animatedIconUrl: string;
animatedLargeBackgroundImageUrl: string;
destinationUrl: string;
linkText: string;
hash: string;
activityType: string;
isRecurring: boolean;
isHidden: boolean;
isTestOnly: boolean;
isGiveEligible: boolean;
level: string;
slidesCount: number;
legalText: string;
legalLinkText: string;
deviceType: string;
}
export interface ActivityAndQuizAttributes {
type: string;
title: string;
link_text: string;
description: string;
foreground_color: string;
image: string;
recurring: string;
destination: string;
'classification.ShowProgress': string;
hidden: string;
give_eligible: string;
}
export interface LastOrder {
id: null;
price: number;
status: null;
sku: null;
timestamp: Date;
catalogItem: null;
}
export interface LevelInfo {
activeLevel: string;
activeLevelName: string;
progress: number;
progressMax: number;
levels: Level[];
benefitsPromotion: DashboardImpression;
}
export interface Level {
key: string;
active: boolean;
name: string;
tasks: CloseLink[];
privileges: CloseLink[];
}
export interface ReferrerProgressInfo {
pointsEarned: number;
pointsMax: number;
isComplete: boolean;
promotions: any[];
}

View File

@@ -0,0 +1,44 @@
export interface GoogleTrends {
default: Default;
}
export interface Default {
trendingSearchesDays: TrendingSearchesDay[];
endDateForNextRequest: string;
rssFeedPageUrl: string;
}
export interface TrendingSearchesDay {
date: string;
formattedDate: string;
trendingSearches: TrendingSearch[];
}
export interface TrendingSearch {
title: Title;
formattedTraffic: string;
relatedQueries: Title[];
image: Image;
articles: Article[];
shareUrl: string;
}
export interface Article {
title: string;
timeAgo: string;
source: string;
image?: Image;
url: string;
snippet: string;
}
export interface Image {
newsUrl: string;
source: string;
imageUrl: string;
}
export interface Title {
query: string;
exploreLink: string;
}

50
src/interface/QuizData.ts Normal file
View File

@@ -0,0 +1,50 @@
export interface QuizData {
offerId: string;
quizId: string;
quizCategory: string;
IsCurrentQuestionCompleted: boolean;
quizRenderSummaryPage: boolean;
resetQuiz: boolean;
userClickedOnHint: boolean;
isDemoEnabled: boolean;
correctAnswer: string;
isMultiChoiceQuizType: boolean;
isPutInOrderQuizType: boolean;
isListicleQuizType: boolean;
isWOTQuizType: boolean;
isBugsForRewardsQuizType: boolean;
currentQuestionNumber: number;
maxQuestions: number;
resetTrackingCounters: boolean;
showWelcomePanel: boolean;
isAjaxCall: boolean;
showHint: boolean;
numberOfOptions: number;
isMobile: boolean;
inRewardsMode: boolean;
enableDailySetWelcomePane: boolean;
enableDailySetNonWelcomePane: boolean;
isDailySetUrlOffer: boolean;
isDailySetFlightEnabled: boolean;
dailySetUrlOfferId: string;
earnedCredits: number;
maxCredits: number;
creditsPerQuestion: number;
userAlreadyClickedOptions: number;
hasUserClickedOnOption: boolean;
recentAnswerChoice: string;
sessionTimerSeconds: string;
isOverlayMinimized: number;
ScreenReaderMsgOnMove: string;
ScreenReaderMsgOnDrop: string;
IsPartialPointsEnabled: boolean;
PrioritizeUrlOverCookies: boolean;
UseNewReportActivityAPI: boolean;
CorrectlyAnsweredQuestionCount: number;
showJoinRewardsPage: boolean;
CorrectOptionAnswer_WOT: string;
WrongOptionAnswer_WOT: string;
enableSlideAnimation: boolean;
ariaLoggingEnabled: boolean;
UseQuestionIndexInActivityId: boolean;
}

View File

@@ -0,0 +1,62 @@
// Chrome Product Data
export interface ChromeVersion {
timestamp: Date;
channels: Channels;
}
export interface Channels {
Stable: Beta;
Beta: Beta;
Dev: Beta;
Canary: Beta;
}
export interface Beta {
channel: string;
version: string;
revision: string;
}
// Edge Product Data
export interface EdgeVersion {
Product: string;
Releases: Release[];
}
export interface Release {
ReleaseId: number;
Platform: Platform;
Architecture: Architecture;
CVEs: string[];
ProductVersion: string;
Artifacts: Artifact[];
PublishedTime: Date;
ExpectedExpiryDate: Date;
}
export enum Architecture {
Arm64 = 'arm64',
Universal = 'universal',
X64 = 'x64',
X86 = 'x86'
}
export interface Artifact {
ArtifactName: string;
Location: string;
Hash: string;
HashAlgorithm: HashAlgorithm;
SizeInBytes: number;
}
export enum HashAlgorithm {
Sha256 = 'SHA256'
}
export enum Platform {
Android = 'Android',
IOS = 'iOS',
Linux = 'Linux',
MACOS = 'MacOS',
Windows = 'Windows'
}

18
src/util/Logger.ts Normal file
View File

@@ -0,0 +1,18 @@
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
const currentTime = new Date().toISOString()
switch (type) {
case 'warn':
console.warn(`[${currentTime}] [WARN] [${title}] ${message}`)
break
case 'error':
console.error(`[${currentTime}] [ERROR] [${title}] ${message}`)
break
default:
console.log(`[${currentTime}] [LOG] [${title}] ${message}`)
break
}
}

108
src/util/UserAgent.ts Normal file
View File

@@ -0,0 +1,108 @@
import axios from 'axios'
import { log } from './Logger'
import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil'
export async function getUserAgent(mobile: boolean) {
const system = getSystemComponents(mobile)
const app = await getAppComponents(mobile)
const uaTemplate = mobile ?
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Mobile Safari/537.36 EdgA/${app.edge_version}` :
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Safari/537.36 Edg/${app.edge_version}`
const platformVersion = `${mobile ? Math.floor(Math.random() * 5) + 9 : Math.floor(Math.random() * 15) + 1}.0.0`
const uaMetadata = {
mobile,
platform: mobile ? 'Android' : 'Windows',
fullVersionList: [
{ brand: 'Not/A)Brand', version: '99.0.0.0' },
{ brand: 'Microsoft Edge', version: app['edge_version'] },
{ brand: 'Chromium', version: app['chrome_version'] }
],
brands: [
{ brand: 'Not/A)Brand', version: '99' },
{ brand: 'Microsoft Edge', version: app['edge_major_version'] },
{ brand: 'Chromium', version: app['chrome_major_version'] }
],
platformVersion,
architecture: mobile ? '' : 'x86',
bitness: mobile ? '' : '64',
model: ''
}
return { userAgent: uaTemplate, userAgentMetadata: uaMetadata }
}
export async function getChromeVersion(): Promise<string> {
try {
const request = {
url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
const response = await axios(request)
const data: ChromeVersion = response.data
return data.channels.Stable.version
} catch (error) {
throw log('USERAGENT-CHROME-VERSION', 'An error occurred:' + error, 'error')
}
}
export async function getEdgeVersions() {
try {
const request = {
url: 'https://edgeupdates.microsoft.com/api/products',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
const response = await axios(request)
const data: EdgeVersion[] = response.data
const stable = data.find(x => x.Product == 'Stable') as EdgeVersion
return {
android: stable.Releases.find(x => x.Platform == 'Android')?.ProductVersion,
windows: stable.Releases.find(x => (x.Platform == 'Windows' && x.Architecture == 'x64'))?.ProductVersion
}
} catch (error) {
throw log('USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error')
}
}
export function getSystemComponents(mobile: boolean): string {
const osId: string = mobile ? 'Linux' : 'Windows NT 10.0'
const uaPlatform: string = mobile ? 'Android 10' : 'Win64; x64'
if (mobile) {
return `${uaPlatform}; ${osId}; K`
}
return `${uaPlatform}; ${osId}`
}
export async function getAppComponents(mobile: boolean) {
const versions = await getEdgeVersions()
const edgeVersion = mobile ? versions.android : versions.windows as string
const edgeMajorVersion = edgeVersion?.split('.')[0]
const chromeVersion = await getChromeVersion()
const chromeMajorVersion = chromeVersion?.split('.')[0]
const chromeReducedVersion = `${chromeMajorVersion}.0.0.0`
return {
edge_version: edgeVersion as string,
edge_major_version: edgeMajorVersion as string,
chrome_version: chromeVersion as string,
chrome_major_version: chromeMajorVersion as string,
chrome_reduced_version: chromeReducedVersion as string
}
}

22
src/util/Utils.ts Normal file
View File

@@ -0,0 +1,22 @@
export async function wait(ms: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(resolve, ms)
})
}
export function getFormattedDate(ms = Date.now()) {
const today = new Date(ms)
const month = String(today.getMonth() + 1).padStart(2, '0') // January is 0
const day = String(today.getDate()).padStart(2, '0')
const year = today.getFullYear()
return `${month}/${day}/${year}`
}
export function shuffleArray(array: string[]): string[] {
const shuffledArray = array.slice()
shuffledArray.sort(() => Math.random() - 0.5)
return shuffledArray
}