mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 14:03:58 +00:00
1.0
This commit is contained in:
40
src/Browser.ts
Normal file
40
src/Browser.ts
Normal 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
130
src/BrowserFunc.ts
Normal 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
70
src/BrowserUtil.ts
Normal 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
10
src/accounts.example.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"email": "email_1",
|
||||
"password": "password_1"
|
||||
},
|
||||
{
|
||||
"email": "email_2",
|
||||
"password": "password_2"
|
||||
}
|
||||
]
|
||||
4
src/config.json
Normal file
4
src/config.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"baseURL" : "https://rewards.bing.com",
|
||||
"sessionPath": "sessions"
|
||||
}
|
||||
54
src/functions/DailySet.ts
Normal file
54
src/functions/DailySet.ts
Normal 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
117
src/functions/Login.ts
Normal 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
|
||||
}
|
||||
}
|
||||
51
src/functions/MorePromotions.ts
Normal file
51
src/functions/MorePromotions.ts
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
31
src/functions/activities/Poll.ts
Normal file
31
src/functions/activities/Poll.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
119
src/functions/activities/Quiz.ts
Normal file
119
src/functions/activities/Quiz.ts
Normal 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
|
||||
233
src/functions/activities/Search.ts
Normal file
233
src/functions/activities/Search.ts
Normal 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}`
|
||||
}
|
||||
26
src/functions/activities/UrlReward.ts
Normal file
26
src/functions/activities/UrlReward.ts
Normal 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
82
src/index.ts
Normal 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
4
src/interface/Account.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface Account {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
673
src/interface/DashboardData.ts
Normal file
673
src/interface/DashboardData.ts
Normal 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[];
|
||||
}
|
||||
44
src/interface/GoogleDailyTrends.ts
Normal file
44
src/interface/GoogleDailyTrends.ts
Normal 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
50
src/interface/QuizData.ts
Normal 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;
|
||||
}
|
||||
62
src/interface/UserAgentUtil.ts
Normal file
62
src/interface/UserAgentUtil.ts
Normal 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
18
src/util/Logger.ts
Normal 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
108
src/util/UserAgent.ts
Normal 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
22
src/util/Utils.ts
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user