mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-11 10:56:17 +00:00
1.0
This commit is contained in:
36
.eslintrc.js
Normal file
36
.eslintrc.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
module.exports = {
|
||||||
|
'env': {
|
||||||
|
'es2021': true,
|
||||||
|
'node': true
|
||||||
|
},
|
||||||
|
'extends': [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended'
|
||||||
|
],
|
||||||
|
'parser': '@typescript-eslint/parser',
|
||||||
|
'parserOptions': {
|
||||||
|
'ecmaVersion': 12,
|
||||||
|
'sourceType': 'module'
|
||||||
|
},
|
||||||
|
'plugins': [
|
||||||
|
'@typescript-eslint'
|
||||||
|
],
|
||||||
|
'rules': {
|
||||||
|
'linebreak-style': [
|
||||||
|
'error',
|
||||||
|
'unix'
|
||||||
|
],
|
||||||
|
'quotes': [
|
||||||
|
'error',
|
||||||
|
'single'
|
||||||
|
],
|
||||||
|
'semi': [
|
||||||
|
'error',
|
||||||
|
'never'
|
||||||
|
],
|
||||||
|
'comma-dangle': 'off',
|
||||||
|
'@typescript-eslint/comma-dangle': 'error',
|
||||||
|
'prefer-arrow-callback': 'error'
|
||||||
|
// Add any other rules you want to enforce here
|
||||||
|
}
|
||||||
|
}
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
sessions/
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
accounts.json
|
||||||
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Program",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${file}",
|
||||||
|
"outFiles": [
|
||||||
|
"${workspaceFolder}/**/*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
# Microsoft-Rewards-Script
|
# Microsoft-Rewards-Script
|
||||||
Automated Microsoft Rewards script, however this time using TypeScriptt and Puppteer.
|
Automated Microsoft Rewards script, however this time using TypeScript and Puppteer.
|
||||||
|
|
||||||
|
Under development, however mainly for personal use!
|
||||||
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "microsoft-rewards-script",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Automatically do tasks for Microsoft Rewards but in TS",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"start": "node ./dist/index.js",
|
||||||
|
"dev": "ts-node ./src/index.ts"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Bing Rewards",
|
||||||
|
"Microsoft Rewards",
|
||||||
|
"Bot",
|
||||||
|
"TypeScript",
|
||||||
|
"Puppeteer"
|
||||||
|
],
|
||||||
|
"author": "Netsky",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||||
|
"typescript": "^5.2.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.5.0",
|
||||||
|
"eslint": "^8.49.0",
|
||||||
|
"eslint-plugin-modules-newline": "^0.0.6",
|
||||||
|
"puppeteer": "^21.2.1",
|
||||||
|
"puppeteer-extra": "^3.3.6",
|
||||||
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
"ts-node": "^10.9.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
71
tsconfig.json
Normal file
71
tsconfig.json
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
/* Basic Options */
|
||||||
|
"target": "ES2020", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
|
||||||
|
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||||
|
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
|
||||||
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
"sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
"strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
/* Additional Checks */
|
||||||
|
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
"noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
/* Module Resolution Options */
|
||||||
|
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user