5 Commits

Author SHA1 Message Date
TheNetsky
1d6167aeca Missing await 2023-11-04 16:24:25 +01:00
TheNetsky
3b15fe19a7 1.2.5 2023-11-04 16:21:56 +01:00
TheNetsky
66dcdd4a7a 1.2.4
Disable "This or That" quiz.
2023-10-30 21:10:47 +01:00
TheNetsky
40f32af67f 1.2.3 2023-10-30 20:37:40 +01:00
TheNetsky
c2b68faa74 1.2.2 2023-10-26 15:31:35 +02:00
21 changed files with 1187 additions and 996 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "microsoft-rewards-script", "name": "microsoft-rewards-script",
"version": "1.2.1", "version": "1.2.5",
"description": "Automatically do tasks for Microsoft Rewards but in TS!", "description": "Automatically do tasks for Microsoft Rewards but in TS!",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
@@ -32,9 +32,10 @@
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"eslint": "^8.49.0", "eslint": "^8.49.0",
"eslint-plugin-modules-newline": "^0.0.6", "eslint-plugin-modules-newline": "^0.0.6",
"puppeteer": "^21.2.1", "fingerprint-generator": "^2.1.42",
"fingerprint-injector": "^2.1.42",
"puppeteer": "^21.4.1",
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"ts-node": "^10.9.1" "ts-node": "^10.9.1"
} }
} }

View File

@@ -1,35 +1,60 @@
import puppeteer from 'puppeteer-extra' import puppeteer from 'puppeteer-extra'
import stealthPlugin from 'puppeteer-extra-plugin-stealth' import { FingerprintInjector } from 'fingerprint-injector'
import { FingerprintGenerator } from 'fingerprint-generator'
import { getUserAgent } from '../util/UserAgent' import { MicrosoftRewardsBot } from '../index'
import { loadSesion } from './BrowserFunc'
import { AccountProxy } from '../interface/Account' import { AccountProxy } from '../interface/Account'
import { headless } from '../config.json' /* Test Stuff
https://abrahamjuliot.github.io/creepjs/
https://botcheck.luminati.io/
http://f.vision/
*/
puppeteer.use(stealthPlugin())
class Browser { class Browser {
private bot: MicrosoftRewardsBot
constructor(bot: MicrosoftRewardsBot) {
this.bot = bot
}
async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) { async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) {
const userAgent = await getUserAgent(isMobile) // const userAgent = await getUserAgent(isMobile)
const browser = await puppeteer.launch({ const browser = await puppeteer.launch({
headless: headless, headless: this.bot.config.headless,
userDataDir: await loadSesion(email), userDataDir: await this.bot.browser.func.loadSesion(email),
args: [ args: [
'--no-sandbox', '--no-sandbox',
'--mute-audio', '--mute-audio',
'--disable-setuid-sandbox', '--disable-setuid-sandbox',
`--user-agent=${userAgent.userAgent}`, '--ignore-certificate-errors',
isMobile ? '--window-size=568,1024' : '', '--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors',
proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : '' proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : ''
] ]
}) })
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
devices: isMobile ? ['mobile'] : ['desktop'],
operatingSystems: isMobile ? ['android'] : ['windows'],
browsers: ['edge'],
browserListQuery: 'last 2 Edge versions'
})
// Modify the newPage function to attach the fingerprint
const originalNewPage = browser.newPage
browser.newPage = async function () {
const page = await originalNewPage.apply(browser)
await new FingerprintInjector().attachFingerprintToPuppeteer(page, { fingerprint, headers })
return page
}
return browser return browser
} }
} }
export default Browser export default Browser

View File

@@ -1,248 +1,254 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { CheerioAPI, load } from 'cheerio'
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { CheerioAPI, load } from 'cheerio'
import { tryDismissAllMessages, tryDismissCookieBanner } from './BrowserUtil' import { MicrosoftRewardsBot } from '../index'
import { getFormattedDate, wait } from './../util/Utils'
import { log } from './../util/Logger'
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData' import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
import { QuizData } from './../interface/QuizData' import { QuizData } from './../interface/QuizData'
import { baseURL, sessionPath } from './../config.json'
export async function goHome(page: Page): Promise<boolean> { export default class BrowserFunc {
private bot: MicrosoftRewardsBot
try { constructor(bot: MicrosoftRewardsBot) {
const dashboardURL = new URL(baseURL) this.bot = bot
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)
// Check if account is suspended
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
if (isSuspended) {
log('GO-HOME', 'This account is suspended!', 'error')
throw new Error('Account has been suspended!')
}
try {
// If activities are found, exit the loop
await page.waitForSelector('#more-activities', { timeout: 1000 })
break
} catch (error) {
// Continue if element is not found
}
const currentURL = new URL(page.url())
if (currentURL.hostname !== dashboardURL.hostname) {
await tryDismissAllMessages(page)
await wait(2000)
await page.goto(baseURL)
}
await wait(5000)
log('GO-HOME', 'Visited homepage successfully')
}
} catch (error) {
console.error('An error occurred:', error)
return false
} }
return true async goHome(page: Page): Promise<boolean> {
}
export async function getDashboardData(page: Page): Promise<DashboardData> { try {
const dashboardURL = new URL(baseURL) const dashboardURL = new URL(this.bot.config.baseURL)
const currentURL = new URL(page.url())
// Should never happen since tasks are opened in a new tab! await page.goto(this.bot.config.baseURL)
if (currentURL.hostname !== dashboardURL.hostname) {
log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page') const maxIterations = 5 // Maximum iterations set to 5
await goHome(page)
for (let iteration = 1; iteration <= maxIterations; iteration++) {
await this.bot.utils.wait(3000)
await this.bot.browser.utils.tryDismissCookieBanner(page)
// Check if account is suspended
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (isSuspended) {
this.bot.log('GO-HOME', 'This account is suspended!', 'error')
throw new Error('Account has been suspended!')
}
try {
// If activities are found, exit the loop
await page.waitForSelector('#more-activities', { timeout: 1000 })
break
} catch (error) {
// Continue if element is not found
}
const currentURL = new URL(page.url())
if (currentURL.hostname !== dashboardURL.hostname) {
await this.bot.browser.utils.tryDismissAllMessages(page)
await this.bot.utils.wait(2000)
await page.goto(this.bot.config.baseURL)
}
await this.bot.utils.wait(5000)
this.bot.log('GO-HOME', 'Visited homepage successfully')
}
} catch (error) {
console.error('An error occurred:', error)
return false
}
return true
} }
// Reload the page to get new data async getDashboardData(page: Page): Promise<DashboardData> {
await page.reload({ waitUntil: 'networkidle2' }) const dashboardURL = new URL(this.bot.config.baseURL)
const currentURL = new URL(page.url())
const scriptContent = await page.evaluate(() => { // Should never happen since tasks are opened in a new tab!
const scripts = Array.from(document.querySelectorAll('script')) if (currentURL.hostname !== dashboardURL.hostname) {
const targetScript = scripts.find(script => script.innerText.includes('var dashboard')) this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
await this.goHome(page)
if (targetScript) {
return targetScript.innerText
} else {
throw log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
} }
})
// Extract the dashboard object from the script content // Reload the page to get new data
const dashboardData = await page.evaluate(scriptContent => { await page.reload({ waitUntil: 'networkidle2' })
// Extract the dashboard object using regex
const regex = /var dashboard = (\{.*?\});/s
const match = regex.exec(scriptContent)
if (match && match[1]) { const scriptContent = await page.evaluate(() => {
return JSON.parse(match[1]) const scripts = Array.from(document.querySelectorAll('script'))
} else { const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
throw log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
}
}, scriptContent)
return dashboardData if (targetScript) {
} return targetScript.innerText
export async function getQuizData(page: Page): Promise<QuizData> {
try {
const html = await page.content()
const $ = load(html)
const scriptContent = $('script').filter((index, element) => {
return $(element).text().includes('_w.rewardsQuizRenderInfo')
}).text()
if (scriptContent) {
const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
const match = regex.exec(scriptContent)
if (match && match[1]) {
const quizData = JSON.parse(match[1])
return quizData
} else { } else {
throw log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error') throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
}
} else {
throw log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
}
} catch (error) {
throw log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
}
}
export async function getSearchPoints(page: Page): Promise<Counters> {
const dashboardData = await getDashboardData(page) // Always fetch newest data
return dashboardData.userStatus.counters
}
export async function getEarnablePoints(data: DashboardData, page: null | Page = null): Promise<number> {
try {
// Fetch new data if page is provided
if (page) {
data = await getDashboardData(page)
}
// These only include the points from tasks that the script can complete!
let totalEarnablePoints = 0
// Desktop Search Points
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
// Mobile Search Points
if (data.userStatus.counters.mobileSearch?.length) {
data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
}
// Daily Set
data.dailySetPromotions[getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
// More Promotions
data.morePromotions.forEach(x => {
// Only count points from supported activities
if (['quiz', 'urlreward'].includes(x.activityType)) {
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
} }
}) })
return totalEarnablePoints // Extract the dashboard object from the script content
} catch (error) { const dashboardData = await page.evaluate(scriptContent => {
throw log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error') // Extract the dashboard object using regex
} const regex = /var dashboard = (\{.*?\});/s
} const match = regex.exec(scriptContent)
export async function getCurrentPoints(data: DashboardData, page: null | Page = null): Promise<number> { if (match && match[1]) {
try { return JSON.parse(match[1])
// Fetch new data if page is provided } else {
if (page) { throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
data = await getDashboardData(page) }
}, scriptContent)
return dashboardData
}
async getQuizData(page: Page): Promise<QuizData> {
try {
const html = await page.content()
const $ = load(html)
const scriptContent = $('script').filter((index, element) => {
return $(element).text().includes('_w.rewardsQuizRenderInfo')
}).text()
if (scriptContent) {
const regex = /_w\.rewardsQuizRenderInfo\s*=\s*({.*?});/s
const match = regex.exec(scriptContent)
if (match && match[1]) {
const quizData = JSON.parse(match[1])
return quizData
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
}
} else {
throw this.bot.log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
}
} catch (error) {
throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
} }
return data.userStatus.availablePoints
} catch (error) {
throw log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
} }
}
export async function loadSesion(email: string): Promise<string> { async getSearchPoints(page: Page): Promise<Counters> {
const sessionDir = path.join(__dirname, sessionPath, email) const dashboardData = await this.getDashboardData(page) // Always fetch newest data
try { return dashboardData.userStatus.counters
// Create session dir }
if (!fs.existsSync(sessionDir)) {
await fs.promises.mkdir(sessionDir, { recursive: true }) async getEarnablePoints(data: DashboardData, page: null | Page = null): Promise<number> {
try {
// Fetch new data if page is provided
if (page) {
data = await this.getDashboardData(page)
}
// These only include the points from tasks that the script can complete!
let totalEarnablePoints = 0
// Desktop Search Points
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
// Mobile Search Points
if (data.userStatus.counters.mobileSearch?.length) {
data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
}
// Daily Set
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
// More Promotions
data.morePromotions.forEach(x => {
// Only count points from supported activities
if (['quiz', 'urlreward'].includes(x.activityType)) {
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
}
})
return totalEarnablePoints
} catch (error) {
throw this.bot.log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
} }
return sessionDir
} catch (error) {
throw new Error(error as string)
} }
}
export async function waitForQuizRefresh(page: Page): Promise<boolean> { async getCurrentPoints(data: DashboardData, page: null | Page = null): Promise<number> {
try { try {
await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 5000 }) // Fetch new data if page is provided
await wait(2000) if (page) {
data = await this.getDashboardData(page)
}
return true return data.userStatus.availablePoints
} catch (error) { } catch (error) {
log('QUIZ-REFRESH', 'An error occurred:' + error, 'error') throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
return false }
} }
}
export async function checkQuizCompleted(page: Page): Promise<boolean> { async loadSesion(email: string): Promise<string> {
try { const sessionDir = path.join(__dirname, this.bot.config.sessionPath, email)
await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 1000 })
await wait(2000)
return true try {
} catch (error) { // Create session dir
return false if (!fs.existsSync(sessionDir)) {
await fs.promises.mkdir(sessionDir, { recursive: true })
}
return sessionDir
} catch (error) {
throw new Error(error as string)
}
} }
}
export async function refreshCheerio(page: Page): Promise<CheerioAPI> { async waitForQuizRefresh(page: Page): Promise<boolean> {
const html = await page.content() try {
const $ = load(html) await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 10_000 })
await this.bot.utils.wait(2000)
return $ return true
} } catch (error) {
this.bot.log('QUIZ-REFRESH', 'An error occurred:' + error, 'error')
return false
}
}
export async function getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise<string> { async checkQuizCompleted(page: Page): Promise<boolean> {
let selector = '' try {
try { await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 2000 })
await this.bot.utils.wait(2000)
return true
} catch (error) {
return false
}
}
async refreshCheerio(page: Page): Promise<CheerioAPI> {
const html = await page.content() const html = await page.content()
const $ = load(html) const $ = load(html)
const element = $('.offer-cta').toArray().find(x => x.attribs.href?.includes(activity.offerId)) return $
if (element) { }
selector = `a[href*="${element.attribs.href}"]`
} async getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise<string> {
} catch (error) { let selector = ''
log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error') try {
const html = await page.content()
const $ = load(html)
const element = $('.offer-cta').toArray().find(x => x.attribs.href?.includes(activity.offerId))
if (element) {
selector = `a[href*="${element.attribs.href}"]`
}
} catch (error) {
this.bot.log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
}
return selector
} }
return selector
} }

View File

@@ -1,81 +1,90 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { wait } from './../util/Utils' import { MicrosoftRewardsBot } from '../index'
import { log } from './../util/Logger'
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 export default class BrowserUtil {
private bot: MicrosoftRewardsBot
for (const button of buttons) { constructor(bot: MicrosoftRewardsBot) {
this.bot = bot
}
async 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, { visible: true, timeout: 1000 })
if (element) {
await element.click()
result = true
}
} catch (error) {
continue
}
}
return result
}
async tryDismissCookieBanner(page: Page): Promise<void> {
try { try {
const element = await page.waitForSelector(button.selector, { visible: true, timeout: 1000 }) await page.waitForSelector('#cookieConsentContainer', { timeout: 1000 })
if (element) { const cookieBanner = await page.$('#cookieConsentContainer')
await element.click()
result = true if (cookieBanner) {
const button = await cookieBanner.$('button')
if (button) {
await button.click()
await this.bot.utils.wait(2000)
}
} }
} catch (error) { } catch (error) {
continue // Continue if element is not found or other error occurs
} }
} }
return result async tryDismissBingCookieBanner(page: Page): Promise<void> {
} try {
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 })
const cookieBanner = await page.$('#bnp_btn_accept')
export async function tryDismissCookieBanner(page: Page): Promise<void> { if (cookieBanner) {
try { await cookieBanner.click()
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
} }
} catch (error) {
// Continue if element is not found or other error occurs
} }
}
export async function tryDismissBingCookieBanner(page: Page): Promise<void> { async getLatestTab(page: Page): Promise<Page> {
try { try {
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 }) await this.bot.utils.wait(500)
const cookieBanner = await page.$('#bnp_btn_accept')
if (cookieBanner) { const browser = page.browser()
await cookieBanner.click() const pages = await browser.pages()
const newTab = pages[pages.length - 1]
if (newTab) {
return newTab
}
throw this.bot.log('GET-NEW-TAB', 'Unable to get latest tab', 'error')
} catch (error) {
throw this.bot.log('GET-NEW-TAB', 'An error occurred:' + error, 'error')
} }
} catch (error) {
// Continue if element is not found or other error occurs
} }
}
export async function getLatestTab(page: Page): Promise<Page> {
try {
await wait(500)
const browser = page.browser()
const pages = await browser.pages()
const newTab = pages[pages.length - 1]
if (newTab) {
return newTab
}
throw log('GET-NEW-TAB', 'Unable to get latest tab', 'error')
} catch (error) {
throw log('GET-NEW-TAB', 'An error occurred:' + error, 'error')
}
} }

View File

@@ -2,7 +2,7 @@
"baseURL": "https://rewards.bing.com", "baseURL": "https://rewards.bing.com",
"sessionPath": "sessions", "sessionPath": "sessions",
"headless": false, "headless": false,
"runOnZeroPoints": true, "runOnZeroPoints": false,
"clusters": 1, "clusters": 1,
"workers": { "workers": {
"doDailySet": true, "doDailySet": true,
@@ -14,7 +14,11 @@
"searchSettings": { "searchSettings": {
"useGeoLocaleQueries": false, "useGeoLocaleQueries": false,
"scrollRandomResults": true, "scrollRandomResults": true,
"clickRandomResults": true "clickRandomResults": true,
"searchDelay": {
"min": 10000,
"max": 20000
}
}, },
"webhook": { "webhook": {
"enabled": false, "enabled": false,

View File

@@ -0,0 +1,52 @@
import { Page } from 'puppeteer'
import { MicrosoftRewardsBot } from '../index'
import { Search } from './activities/Search'
import { ABC } from './activities/ABC'
import { Poll } from './activities/Poll'
import { Quiz } from './activities/Quiz'
import { ThisOrThat } from './activities/ThisOrThat'
import { UrlReward } from './activities/UrlReward'
import { DashboardData } from '../interface/DashboardData'
export default class Activities {
private bot: MicrosoftRewardsBot
constructor(bot: MicrosoftRewardsBot) {
this.bot = bot
}
doSearch = async (page: Page, data: DashboardData, mobile: boolean): Promise<void> => {
const search = new Search(this.bot)
await search.doSearch(page, data, mobile)
}
doABC = async (page: Page): Promise<void> => {
const abc = new ABC(this.bot)
await abc.doABC(page)
}
doPoll = async (page: Page): Promise<void> => {
const poll = new Poll(this.bot)
await poll.doPoll(page)
}
doThisOrThat = async (page: Page): Promise<void> => {
const thisOrThat = new ThisOrThat(this.bot)
await thisOrThat.doThisOrThat(page)
}
doQuiz = async (page: Page): Promise<void> => {
const quiz = new Quiz(this.bot)
await quiz.doQuiz(page)
}
doUrlReward = async (page: Page): Promise<void> => {
const urlReward = new UrlReward(this.bot)
await urlReward.doUrlReward(page)
}
}

View File

@@ -1,125 +1,133 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import readline from 'readline' import readline from 'readline'
import { MicrosoftRewardsBot } from '../index'
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
output: process.stdout output: process.stdout
}) })
import { tryDismissAllMessages, tryDismissBingCookieBanner } from '../browser/BrowserUtil'
import { wait } from '../util/Utils'
import { log } from '../util/Logger'
export async function login(page: Page, email: string, password: string) { export class Login {
private bot: MicrosoftRewardsBot
try { constructor(bot: MicrosoftRewardsBot) {
// Navigate to the Bing login page this.bot = bot
await page.goto('https://login.live.com/') }
const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 5000 }).then(() => true).catch(() => false) async login(page: Page, email: string, password: string) {
if (!isLoggedIn) { try {
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 5000 }).then(() => true).catch(() => false) // Navigate to the Bing login page
if (isLocked) { await page.goto('https://login.live.com/')
log('LOGIN', 'This account has been locked!', 'error')
throw new Error('Account has been locked!') const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
if (!isLoggedIn) {
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false)
if (isLocked) {
this.bot.log('LOGIN', 'This account has been locked!', 'error')
throw new Error('Account has been locked!')
}
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
await this.execLogin(page, email, password)
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
} else {
this.bot.log('LOGIN', 'Already logged in')
} }
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 }) // Check if logged in to bing
await this.checkBingLogin(page)
await execLogin(page, email, password) // We're done logging in
log('LOGIN', 'Logged into Microsoft successfully') this.bot.log('LOGIN', 'Logged in successfully')
} else {
log('LOGIN', 'Already logged in') } catch (error) {
// Throw and don't continue
throw this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
} }
// Check if logged in to bing
await checkBingLogin(page)
// We're done logging in
log('LOGIN', 'Logged in successfully')
} catch (error) {
// Throw and don't continue
throw log('LOGIN', 'An error occurred:' + error, 'error')
} }
}
async function execLogin(page: Page, email: string, password: string) { private async execLogin(page: Page, email: string, password: string) {
await page.type('#i0116', email) 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') await page.click('#idSIButton9')
} catch (error) { this.bot.log('LOGIN', 'Email entered successfully')
log('LOGIN', '2FA code required')
const code = await new Promise<string>((resolve) => { try {
rl.question('Enter 2FA code:\n', (input) => { await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
rl.close() await this.bot.utils.wait(2000)
resolve(input)
await page.type('#i0118', password)
await page.click('#idSIButton9')
} catch (error) {
this.bot.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.type('input[name="otc"]', code)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
} finally { } finally {
log('LOGIN', 'Password entered successfully') this.bot.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) { const currentURL = new URL(page.url())
log('LOGIN-BING', 'An error occurred:' + error, 'error')
}
}
async function checkBingLoginStatus(page: Page): Promise<boolean> { while (currentURL.pathname !== '/' || currentURL.hostname !== 'account.microsoft.com') {
try { await this.bot.browser.utils.tryDismissAllMessages(page)
await page.waitForSelector('#id_n', { timeout: 5000 }) currentURL.href = page.url()
return true }
} catch (error) {
return false // Wait for login to complete
await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 })
} }
private async checkBingLogin(page: Page): Promise<void> {
try {
this.bot.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 this.bot.utils.wait(3000)
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
const loggedIn = await this.checkBingLoginStatus(page)
if (loggedIn) {
this.bot.log('LOGIN-BING', 'Bing login verification passed!')
break
}
}
await this.bot.utils.wait(1000)
}
} catch (error) {
this.bot.log('LOGIN-BING', 'An error occurred:' + error, 'error')
}
}
private async checkBingLoginStatus(page: Page): Promise<boolean> {
try {
await page.waitForSelector('#id_n', { timeout: 10_000 })
return true
} catch (error) {
return false
}
}
} }

View File

@@ -1,173 +1,184 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { doPoll } from './activities/Poll'
import { doQuiz } from './activities/Quiz'
import { doUrlReward } from './activities/UrlReward'
import { doThisOrThat } from './activities/ThisOrThat'
import { doABC } from './activities/ABC'
import { getPunchCardActivity } from '../browser/BrowserFunc'
import { getLatestTab } from '../browser/BrowserUtil'
import { getFormattedDate, wait } from '../util/Utils'
import { log } from '../util/Logger'
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData' import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
import { baseURL } from '../config.json' import { MicrosoftRewardsBot } from '../index'
// Daily Set export class Workers {
export async function doDailySet(page: Page, data: DashboardData) { public bot: MicrosoftRewardsBot
const todayData = data.dailySetPromotions[getFormattedDate()] constructor(bot: MicrosoftRewardsBot) {
this.bot = bot
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
if (!activitiesUncompleted.length) {
log('DAILY-SET', 'All Daily Set" items have already been completed')
return
} }
// Solve Activities // Daily Set
log('DAILY-SET', 'Started solving "Daily Set" items') async doDailySet(page: Page, data: DashboardData) {
const todayData = data.dailySetPromotions[this.bot.utils.getFormattedDate()]
await solveActivities(page, activitiesUncompleted) const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
log('DAILY-SET', 'All "Daily Set" items have been completed') if (!activitiesUncompleted.length) {
} this.bot.log('DAILY-SET', 'All Daily Set" items have already been completed')
return
// Punch Card }
export async function doPunchCard(page: Page, data: DashboardData) {
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
if (!punchCardsUncompleted.length) {
log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
return
}
for (const punchCard of punchCardsUncompleted) {
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
// Solve Activities // Solve Activities
log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`) this.bot.log('DAILY-SET', 'Started solving "Daily Set" items')
const browser = page.browser() await this.solveActivities(page, activitiesUncompleted)
page = await browser.newPage()
// Got to punch card index page in a new tab this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: baseURL })
await solveActivities(page, activitiesUncompleted, punchCard)
// Close the punch card index page
await page.close()
log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
} }
log('PUNCH-CARD', 'All "Punch Card" items have been completed') // Punch Card
} async doPunchCard(page: Page, data: DashboardData) {
// More Promotions const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
export async function doMorePromotions(page: Page, data: DashboardData) {
const morePromotions = data.morePromotions
// Check if there is a promotional item if (!punchCardsUncompleted.length) {
if (data.promotionalItem) { // Convert and add the promotional item to the array this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
morePromotions.push(data.promotionalItem as unknown as MorePromotion) return
}
for (const punchCard of punchCardsUncompleted) {
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
// Solve Activities
this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
const browser = page.browser()
page = await browser.newPage()
// Got to punch card index page in a new tab
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
// Wait for new page to load, max 10 seconds, however try regardless in case of error
await page.waitForNetworkIdle({ timeout: 10_000 }).catch(() => { })
await this.solveActivities(page, activitiesUncompleted, punchCard)
// Close the punch card index page
await page.close()
this.bot.log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
}
this.bot.log('PUNCH-CARD', 'All "Punch Card" items have been completed')
} }
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0) ?? [] // More Promotions
async doMorePromotions(page: Page, data: DashboardData) {
const morePromotions = data.morePromotions
if (!activitiesUncompleted.length) { // Check if there is a promotional item
log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed') if (data.promotionalItem) { // Convert and add the promotional item to the array
return morePromotions.push(data.promotionalItem as unknown as MorePromotion)
}
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
if (!activitiesUncompleted.length) {
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
return
}
// Solve Activities
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
await this.solveActivities(page, activitiesUncompleted)
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
} }
// Solve Activities // Solve all the different types of activities
log('MORE-PROMOTIONS', 'Started solving "More Promotions" item') private async solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
for (const activity of activities) {
try {
await solveActivities(page, activitiesUncompleted) let selector = `[data-bi-id="${activity.offerId}"]`
log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed') if (punchCard) {
} selector = await this.bot.browser.func.getPunchCardActivity(page, activity)
// Solve all the different types of activities } else if (activity.name.toLowerCase().includes('membercenter')) {
async function solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
for (const activity of activities) {
try {
let selector = `[data-bi-id="${activity.offerId}"]` // Promotion
if (activity.priority === 1) {
if (punchCard) { selector = await page.waitForSelector('#promo-item', { visible: true, timeout: 2000 }).then(() => true).catch(() => false) ?
selector = await getPunchCardActivity(page, activity) '#promo-item' : activity.name
} else {
} else if (activity.name.toLowerCase().includes('membercenter')) { selector = `[data-bi-id="${activity.name}"]`
// Promotion
if (activity.priority === 1) {
selector = '#promo-item'
} else {
selector = `[data-bi-id="${activity.name}"]`
}
}
// Wait for element to load
await page.waitForSelector(selector, { timeout: 5000 })
// Click element, it will be opened in a new tab
await page.click(selector)
// Select the new activity page
const activityPage = await getLatestTab(page)
switch (activity.promotionType) {
// Quiz (Poll, Quiz or ABC)
case 'quiz':
switch (activity.pointProgressMax) {
// Poll or ABC (Usually 10 points)
case 10:
// Normal poll
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
await doPoll(activityPage)
} else { // ABC
log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
await doABC(activityPage)
}
break
// This Or That Quiz (Usually 50 points)
case 50:
log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
await doThisOrThat(activityPage)
break
// Quizzes are usually 30-40 points
default:
log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
await doQuiz(activityPage)
break
} }
break }
// UrlReward (Visit) // Wait for element to load
case 'urlreward': await page.waitForSelector(selector, { timeout: 10_000 })
log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
await doUrlReward(activityPage)
break
// Misc, Usually UrlReward Type // Click element, it will be opened in a new tab
default: await page.click(selector)
log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
await doUrlReward(activityPage) // Select the new activity page
break const activityPage = await this.bot.browser.utils.getLatestTab(page)
// Wait for the new tab to fully load, ignore error.
/*
Due to common false timeout on this function, we're ignoring the error regardless, if it worked then it's faster,
if it didn't then it gave enough time for the page to load.
*/
await activityPage.waitForNetworkIdle({ timeout: 10_000 }).catch(() => { })
// Cooldown
await this.bot.utils.wait(4000)
switch (activity.promotionType) {
// Quiz (Poll, Quiz or ABC)
case 'quiz':
switch (activity.pointProgressMax) {
// Poll or ABC (Usually 10 points)
case 10:
// Normal poll
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
this.bot.log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
await this.bot.activities.doPoll(activityPage)
} else { // ABC
this.bot.log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
await this.bot.activities.doABC(activityPage)
}
break
// This Or That Quiz (Usually 50 points)
case 50:
this.bot.log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
await this.bot.activities.doThisOrThat(activityPage)
break
// Quizzes are usually 30-40 points
default:
this.bot.log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
await this.bot.activities.doQuiz(activityPage)
break
}
break
// UrlReward (Visit)
case 'urlreward':
this.bot.log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
await this.bot.activities.doUrlReward(activityPage)
break
// Misc, Usually UrlReward Type
default:
this.bot.log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
await this.bot.activities.doUrlReward(activityPage)
break
}
// Cooldown
await this.bot.utils.wait(2000)
} catch (error) {
this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error')
} }
// Cooldown
await wait(1500)
} catch (error) {
log('ACTIVITY', 'An error occurred:' + error, 'error')
} }
} }
} }

View File

@@ -1,52 +1,52 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { refreshCheerio } from '../../browser/BrowserFunc' import { Workers } from '../Workers'
import { getLatestTab } from '../../browser/BrowserUtil'
import { log } from '../../util/Logger'
import { randomNumber, wait } from '../../util/Utils'
export async function doABC(page: Page) {
log('ABC', 'Trying to complete poll')
try { export class ABC extends Workers {
await wait(2000)
let $ = await refreshCheerio(page)
// Don't loop more than 15 in case unable to solve, would lock otherwise async doABC(page: Page) {
const maxIterations = 15 this.bot.log('ABC', 'Trying to complete poll')
let i
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 5000 })
const answers = $('.wk_OptionClickClass') try {
const answer = answers[randomNumber(0, 2)]?.attribs['id'] let $ = await this.bot.browser.func.refreshCheerio(page)
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 5000 }) // Don't loop more than 15 in case unable to solve, would lock otherwise
const maxIterations = 15
let i
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 10_000 })
await wait(2000) const answers = $('.wk_OptionClickClass')
await page.click(`#${answer}`) // Click answer const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
await wait(4000) await page.waitForSelector(`#${answer}`, { visible: true, timeout: 10_000 })
await page.waitForSelector('div.wk_button', { visible: true, timeout: 5000 })
await page.click('div.wk_button') // Click next question button
page = await getLatestTab(page) await this.bot.utils.wait(2000)
$ = await refreshCheerio(page) await page.click(`#${answer}`) // Click answer
await wait(1000)
await this.bot.utils.wait(4000)
await page.waitForSelector('div.wk_button', { visible: true, timeout: 10_000 })
await page.click('div.wk_button') // Click next question button
page = await this.bot.browser.utils.getLatestTab(page)
$ = await this.bot.browser.func.refreshCheerio(page)
await this.bot.utils.wait(1000)
}
await this.bot.utils.wait(4000)
await page.close()
if (i === maxIterations) {
this.bot.log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
} else {
this.bot.log('ABC', 'Completed the ABC successfully')
}
} catch (error) {
await page.close()
this.bot.log('ABC', 'An error occurred:' + error, 'error')
} }
await wait(4000)
await page.close()
if (i === maxIterations) {
log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
} else {
log('ABC', 'Completed the ABC successfully')
}
} catch (error) {
await page.close()
log('ABC', 'An error occurred:' + error, 'error')
} }
}
}

View File

@@ -1,26 +1,29 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { log } from '../../util/Logger' import { Workers } from '../Workers'
import { randomNumber, wait } from '../../util/Utils'
export async function doPoll(page: Page) {
log('POLL', 'Trying to complete poll')
try { export class Poll extends Workers {
const buttonId = `#btoption${Math.floor(randomNumber(0, 1))}`
await page.waitForNetworkIdle({ timeout: 5000 }) async doPoll(page: Page) {
await page.waitForSelector(buttonId, { visible: true, timeout: 5000 }) this.bot.log('POLL', 'Trying to complete poll')
await wait(2000)
await page.click(buttonId) try {
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
await wait(4000) await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 })
await page.close() await this.bot.utils.wait(2000)
log('POLL', 'Completed the poll successfully') await page.click(buttonId)
} catch (error) {
await page.close() await this.bot.utils.wait(4000)
log('POLL', 'An error occurred:' + error, 'error') await page.close()
this.bot.log('POLL', 'Completed the poll successfully')
} catch (error) {
await page.close()
this.bot.log('POLL', 'An error occurred:' + error, 'error')
}
} }
} }

View File

@@ -1,94 +1,91 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { getQuizData, waitForQuizRefresh } from '../../browser/BrowserFunc' import { Workers } from '../Workers'
import { wait } from '../../util/Utils'
import { log } from '../../util/Logger'
export async function doQuiz(page: Page) {
log('QUIZ', 'Trying to complete quiz')
try { export class Quiz extends Workers {
await page.waitForNetworkIdle({ timeout: 5000 })
await wait(2000)
// Check if the quiz has been started or not async doQuiz(page: Page) {
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) this.bot.log('QUIZ', 'Trying to complete quiz')
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
log('QUIZ', 'Quiz has already been started, trying to finish it')
}
await wait(2000) try {
// Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
this.bot.log('QUIZ', 'Quiz has already been started, trying to finish it')
}
let quizData = await getQuizData(page) await this.bot.utils.wait(2000)
const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining
// All questions let quizData = await this.bot.browser.func.getQuizData(page)
for (let question = 0; question < questionsRemaining; question++) { const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining
if (quizData.numberOfOptions === 8) { // All questions
const answers: string[] = [] for (let question = 0; question < questionsRemaining; question++) {
for (let i = 0; i < quizData.numberOfOptions; i++) { if (quizData.numberOfOptions === 8) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 }) const answers: string[] = []
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
if (answerAttribute && answerAttribute.toLowerCase() === 'true') { for (let i = 0; i < quizData.numberOfOptions; i++) {
answers.push(`#rqAnswerOption${i}`) const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
answers.push(`#rqAnswerOption${i}`)
}
} }
}
// Click the answers // Click the answers
for (const answer of answers) { for (const answer of answers) {
await page.waitForSelector(answer, { visible: true, timeout: 2000 }) await page.waitForSelector(answer, { visible: true, timeout: 2000 })
// Click the answer on page
await page.click(answer)
const refreshSuccess = await waitForQuizRefresh(page)
if (!refreshSuccess) {
await page.close()
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return
}
}
// Other type quiz
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
quizData = await getQuizData(page) // Refresh Quiz Data
const correctOption = quizData.correctAnswer
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 })
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) {
// Click the answer on page // Click the answer on page
await page.click(`#rqAnswerOption${i}`) await page.click(answer)
const refreshSuccess = await waitForQuizRefresh(page) const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
if (!refreshSuccess) { if (!refreshSuccess) {
await page.close() await page.close()
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error') this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return return
} }
} }
}
await wait(2000)
// Other type quiz
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data
const correctOption = quizData.correctAnswer
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) {
// Click the answer on page
await page.click(`#rqAnswerOption${i}`)
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
if (!refreshSuccess) {
await page.close()
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
return
}
}
}
await this.bot.utils.wait(2000)
}
} }
// Done with
await this.bot.utils.wait(2000)
await page.close()
this.bot.log('QUIZ', 'Completed the quiz successfully')
} catch (error) {
await page.close()
this.bot.log('QUIZ', 'An error occurred:' + error, 'error')
} }
// Done with
await wait(2000)
await page.close()
log('QUIZ', 'Completed the quiz successfully')
} catch (error) {
await page.close()
log('QUIZ', 'An error occurred:' + error, 'error')
} }
} }

View File

@@ -1,203 +1,232 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import axios from 'axios' import axios from 'axios'
import { getLatestTab } from '../../browser/BrowserUtil' import { Workers } from '../Workers'
import { getSearchPoints } from '../../browser/BrowserFunc'
import { log } from '../../util/Logger'
import { randomNumber, shuffleArray, wait } from '../../util/Utils'
import { searchSettings } from '../../config.json'
import { DashboardData, DashboardImpression } from '../../interface/DashboardData' import { DashboardData, DashboardImpression } from '../../interface/DashboardData'
import { GoogleTrends } from '../../interface/GoogleDailyTrends' import { GoogleTrends } from '../../interface/GoogleDailyTrends'
import { GoogleSearch } from '../../interface/Search' import { GoogleSearch } from '../../interface/Search'
export async function doSearch(page: Page, data: DashboardData, mobile: boolean) {
log('SEARCH-BING', 'Starting bing searches')
const mobileData = data.userStatus.counters?.mobileSearch ? data.userStatus.counters.mobileSearch[0] : null // Mobile searches export class Search extends Workers {
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) ? private searchPageURL = 'https://bing.com'
(mobileData.pointProgressMax - mobileData.pointProgress) :
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
if (missingPoints == 0) { public async doSearch(page: Page, data: DashboardData, mobile: boolean) {
log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`) this.bot.log('SEARCH-BING', 'Starting bing searches')
return
}
// Generate search queries const mobileData = data.userStatus.counters?.mobileSearch ? data.userStatus.counters.mobileSearch[0] : null // Mobile searches
let googleSearchQueries = await getGoogleTrends(data.userProfile.attributes.country, missingPoints) const edgeData = data.userStatus.counters.pcSearch[1] as DashboardImpression // Edge searches
googleSearchQueries = shuffleArray(googleSearchQueries) const genericData = data.userStatus.counters.pcSearch[0] as DashboardImpression // Normal searches
// Deduplicate the search terms let missingPoints = (mobile && mobileData) ?
googleSearchQueries = [...new Set(googleSearchQueries)] (mobileData.pointProgressMax - mobileData.pointProgress) :
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
// 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 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search
const queries: string[] = []
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related))
// Loop over Google search queries
for (let i = 0; i < queries.length; i++) {
const query = queries[i] as string
log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${mobile}`)
const newData = await bingSearch(page, searchPage, query)
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = (mobile && newMobileData) ?
(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) { if (missingPoints == 0) {
break this.bot.log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
return
} }
// If we didn't gain points for 10 iterations, assume it's stuck // Generate search queries
if (maxLoop > 10) { let googleSearchQueries = await this.getGoogleTrends(data.userProfile.attributes.country, missingPoints)
log('SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn') googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
maxLoop = 0 // Reset to 0 so we can retry with related searches below
break // Deduplicate the search terms
googleSearchQueries = [...new Set(googleSearchQueries)]
// Open a new tab
const browser = page.browser()
const searchPage = await browser.newPage()
// Go to bing
await searchPage.goto(this.searchPageURL)
let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search
const queries: string[] = []
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related))
// Loop over Google search queries
for (let i = 0; i < queries.length; i++) {
const query = queries[i] as string
this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${mobile}`)
const newData = await this.bingSearch(page, searchPage, query)
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = (mobile && newMobileData) ?
(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 we didn't gain points for 10 iterations, assume it's stuck
if (maxLoop > 10) {
this.bot.log('SEARCH-BING', 'Search didn\'t gain point for 10 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 we still got remaining search queries, generate extra ones
if (missingPoints > 0) { if (missingPoints > 0) {
log('SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`) this.bot.log('SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`)
let i = 0 let i = 0
while (missingPoints > 0) { while (missingPoints > 0) {
const query = googleSearchQueries[i++] as GoogleSearch const query = googleSearchQueries[i++] as GoogleSearch
// Get related search terms to the Google search queries // Get related search terms to the Google search queries
const relatedTerms = await getRelatedTerms(query?.topic) const relatedTerms = await this.getRelatedTerms(query?.topic)
if (relatedTerms.length > 3) { if (relatedTerms.length > 3) {
// Search for the first 2 related terms // Search for the first 2 related terms
for (const term of relatedTerms.slice(1, 3)) { for (const term of relatedTerms.slice(1, 3)) {
log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`) this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`)
const newData = await bingSearch(page, searchPage, query.topic) const newData = await this.bingSearch(page, searchPage, query.topic)
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
const newMissingPoints = (mobile && newMobileData) ? const newMissingPoints = (mobile && newMobileData) ?
(newMobileData.pointProgressMax - newMobileData.pointProgress) : (newMobileData.pointProgressMax - newMobileData.pointProgress) :
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress) (newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
// If the new point amount is the same as before // If the new point amount is the same as before
if (newMissingPoints == missingPoints) { if (newMissingPoints == missingPoints) {
maxLoop++ // Add to max loop maxLoop++ // Add to max loop
} else { // There has been a change in points } else { // There has been a change in points
maxLoop = 0 // Reset the loop maxLoop = 0 // Reset the loop
} }
missingPoints = newMissingPoints missingPoints = newMissingPoints
// If we satisfied the searches // If we satisfied the searches
if (missingPoints == 0) { if (missingPoints == 0) {
break break
} }
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck // Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
if (maxLoop > 5) { if (maxLoop > 5) {
log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn') this.bot.log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
return return
}
} }
} }
} }
} }
this.bot.log('SEARCH-BING', 'Completed searches')
} }
log('SEARCH-BING', 'Completed searches') private async 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, { visible: true, timeout: 10_000 })
await searchPage.click(searchBar) // Focus on the textarea
await this.bot.utils.wait(500)
await searchPage.keyboard.down('Control')
await searchPage.keyboard.press('A')
await searchPage.keyboard.press('Backspace')
await searchPage.keyboard.up('Control')
await searchPage.keyboard.type(query)
await searchPage.keyboard.press('Enter')
async function bingSearch(page: Page, searchPage: Page, query: string) { if (this.bot.config.searchSettings.scrollRandomResults) {
// Try a max of 5 times await this.bot.utils.wait(2000)
for (let i = 0; i < 5; i++) { await this.randomScroll(searchPage)
try { }
const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
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')
await searchPage.keyboard.up('Control')
await searchPage.keyboard.type(query)
await searchPage.keyboard.press('Enter')
if (searchSettings.scrollRandomResults) { if (this.bot.config.searchSettings.clickRandomResults) {
await wait(2000) await this.bot.utils.wait(2000)
await randomScroll(searchPage) await this.clickRandomLink(searchPage)
}
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
return await this.bot.browser.func.getSearchPoints(page)
} catch (error) {
if (i === 5) {
this.bot.log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
break
}
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
await this.bot.utils.wait(4000)
} }
if (searchSettings.clickRandomResults) {
await wait(2000)
await clickRandomLink(searchPage)
}
await wait(Math.floor(randomNumber(10_000, 20_000)))
return await getSearchPoints(page)
} catch (error) {
if (i === 5) {
log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
break
}
log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
await wait(4000)
} }
this.bot.log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
return await this.bot.browser.func.getSearchPoints(page)
} }
log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error') private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
return await getSearchPoints(page) const queryTerms: GoogleSearch[] = []
} let i = 0
async function getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> { geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
const queryTerms: GoogleSearch[] = []
let i = 0
geoLocale = (searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US' this.bot.log('SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
log('SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`) while (queryCount > queryTerms.length) {
i += 1
const date = new Date()
date.setDate(date.getDate() - i)
const formattedDate = this.formatDate(date)
while (queryCount > queryTerms.length) { try {
i += 1 const request = {
const date = new Date() url: `https://trends.google.com/trends/api/dailytrends?geo=${geoLocale}&hl=en&ed=${formattedDate}&ns=15`,
date.setDate(date.getDate() - i) method: 'GET',
const formattedDate = formatDate(date) 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: topic.title.query.toLowerCase(),
related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase())
})
}
} catch (error) {
this.bot.log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
}
}
return queryTerms
}
private async getRelatedTerms(term: string): Promise<string[]> {
try { try {
const request = { const request = {
url: `https://trends.google.com/trends/api/dailytrends?geo=${geoLocale}&hl=en&ed=${formattedDate}&ns=15`, url: `https://api.bing.com/osjson.aspx?query=${term}`,
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -206,126 +235,96 @@ async function getGoogleTrends(geoLocale: string, queryCount: number): Promise<G
const response = await axios(request) const response = await axios(request)
const data: GoogleTrends = JSON.parse((await response.data).slice(5)) return response.data[1] as string[]
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
queryTerms.push({
topic: topic.title.query.toLowerCase(),
related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase())
})
}
} catch (error) { } catch (error) {
log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error') this.bot.log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
} }
return []
} }
return queryTerms private 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')
async function getRelatedTerms(term: string): Promise<string[]> { return `${year}${month}${day}`
try { }
const request = {
url: `https://api.bing.com/osjson.aspx?query=${term}`, private async randomScroll(page: Page) {
method: 'GET', try {
headers: { // Press the arrow down key to scroll
'Content-Type': 'application/json' for (let i = 0; i < this.bot.utils.randomNumber(5, 100); i++) {
await page.keyboard.press('ArrowDown')
} }
} catch (error) {
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
} }
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 { private async clickRandomLink(page: Page) {
const year = date.getFullYear() try {
const month = String(date.getMonth() + 1).padStart(2, '0') const searchListingURL = new URL(page.url()) // Get searchPage info before clicking
const day = String(date.getDate()).padStart(2, '0')
return `${year}${month}${day}` await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not
}
async function randomScroll(page: Page) { // Will get current tab if no new one is created
try { let lastTab = await this.bot.browser.utils.getLatestTab(page)
// Press the arrow down key to scroll
for (let i = 0; i < randomNumber(5, 100); i++) {
await page.keyboard.press('ArrowDown')
}
} catch (error) {
log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
}
}
async function clickRandomLink(page: Page) { // Let website load, if it doesn't load within 5 sec. exit regardless
try { await lastTab.waitForNetworkIdle({ timeout: 5000 }).catch(() => { })
const searchListingURL = new URL(page.url()) // Get searchPage info before clicking
await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not // Check if the tab is closed or not
if (!lastTab.isClosed()) {
// Wait for website to load let lastTabURL = new URL(lastTab.url()) // Get new tab info
await wait(3000)
// Will get current tab if no new one is created
let lastTab = await getLatestTab(page)
// Wait for the body of the new page to be loaded
await lastTab.waitForSelector('body', { timeout: 10_000 }).catch(() => { })
// Check if the tab is closed or not
if (!lastTab.isClosed()) {
let lastTabURL = new URL(lastTab.url()) // Get new tab info
// Check if the URL is different from the original one, don't loop more than 5 times.
let i = 0
while (lastTabURL.href !== searchListingURL.href && i < 5) {
// If hostname is still bing, (Bing images/news etc)
if (lastTabURL.hostname == searchListingURL.hostname) {
await lastTab.goBack()
lastTab = await getLatestTab(page) // Get last opened tab
// If "goBack" didn't return to search listing (due to redirects)
if (lastTabURL.hostname !== searchListingURL.hostname) {
await lastTab.goto(searchListingURL.href)
}
} else { // No longer on bing, likely opened a new tab, close this tab
lastTab = await getLatestTab(page) // Get last opened tab
lastTabURL = new URL(lastTab.url())
const tabs = await (page.browser()).pages() // Get all tabs open
// If the browser has more than 3 tabs open, it has opened a new one, we need to close this one.
if (tabs.length > 3) {
// Make sure the page is still open!
if (!lastTab.isClosed()) {
await lastTab.close()
}
} else if (lastTabURL.href !== searchListingURL.href) {
// Check if the URL is different from the original one, don't loop more than 5 times.
let i = 0
while (lastTabURL.href !== searchListingURL.href && i < 5) {
// If hostname is still bing, (Bing images/news etc)
if (lastTabURL.hostname == searchListingURL.hostname) {
await lastTab.goBack() await lastTab.goBack()
lastTab = await getLatestTab(page) // Get last opened tab lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
lastTabURL = new URL(lastTab.url())
// If "goBack" didn't return to search listing (due to redirects) // If "goBack" didn't return to search listing (due to redirects)
if (lastTabURL.hostname !== searchListingURL.hostname) { if (lastTabURL.hostname !== searchListingURL.hostname) {
await lastTab.goto(searchListingURL.href) await lastTab.goto(this.searchPageURL)
}
} else { // No longer on bing, likely opened a new tab, close this tab
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
lastTabURL = new URL(lastTab.url())
const tabs = await (page.browser()).pages() // Get all tabs open
// If the browser has more than 3 tabs open, it has opened a new one, we need to close this one.
if (tabs.length > 3) {
// Make sure the page is still open!
if (!lastTab.isClosed()) {
await lastTab.close()
}
} else if (lastTabURL.href !== searchListingURL.href) {
await lastTab.goBack()
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
lastTabURL = new URL(lastTab.url())
// If "goBack" didn't return to search listing (due to redirects)
if (lastTabURL.hostname !== searchListingURL.hostname) {
await lastTab.goto(this.searchPageURL)
}
} }
} }
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
i++
} }
}
lastTab = await getLatestTab(page) // Finally update the lastTab var again }
i++ } catch (error) {
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
} }
} catch (error) {
log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
} }
} }

View File

@@ -1,34 +1,34 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { wait } from '../../util/Utils' import { Workers } from '../Workers'
import { log } from '../../util/Logger'
import { getQuizData } from '../../browser/BrowserFunc'
export async function doThisOrThat(page: Page) {
log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
try { export class ThisOrThat extends Workers {
await page.waitForNetworkIdle({ timeout: 5000 })
await wait(2000)
// Check if the quiz has been started or not async doThisOrThat(page: Page) {
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false) this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
if (quizNotStarted) {
await page.click('#rqStartQuiz') return
} else { try {
log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it') // Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
this.bot.log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
}
await this.bot.utils.wait(2000)
// Solving
const quizData = await this.bot.browser.func.getQuizData(page)
quizData // correctAnswer property is always null?
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrThat successfully')
} catch (error) {
await page.close()
this.bot.log('THIS-OR-THAT', 'An error occurred:' + error, 'error')
} }
await wait(2000)
// Solving
const quizData = await getQuizData(page)
quizData // correctAnswer property is always null?
log('THIS-OR-THAT', 'Completed the ThisOrthat successfully')
} catch (error) {
await page.close()
log('THIS-OR-THAT', 'An error occurred:' + error, 'error')
} }
} }

View File

@@ -1,21 +1,22 @@
import { Page } from 'puppeteer' import { Page } from 'puppeteer'
import { wait } from '../../util/Utils' import { Workers } from '../Workers'
import { log } from '../../util/Logger'
export async function doUrlReward(page: Page) {
log('URL-REWARD', 'Trying to complete UrlReward')
try { export class UrlReward extends Workers {
// After waiting, close the page
await page.waitForNetworkIdle({ timeout: 10_000 })
await wait(2000)
await page.close()
log('URL-REWARD', 'Completed the UrlReward successfully') async doUrlReward(page: Page) {
} catch (error) { this.bot.log('URL-REWARD', 'Trying to complete UrlReward')
await page.close()
log('URL-REWARD', 'An error occurred:' + error, 'error') try {
// After waiting, close the page
await page.close()
this.bot.log('URL-REWARD', 'Completed the UrlReward successfully')
} catch (error) {
await page.close()
this.bot.log('URL-REWARD', 'An error occurred:' + error, 'error')
}
} }
} }

View File

@@ -1,40 +1,60 @@
import cluster from 'cluster' import cluster from 'cluster'
import Browser from './browser/Browser' import Browser from './browser/Browser'
import { getDashboardData, getEarnablePoints, goHome } from './browser/BrowserFunc' import BrowserFunc from './browser/BrowserFunc'
import BrowserUtil from './browser/BrowserUtil'
import { log } from './util/Logger' import { log } from './util/Logger'
import { loadAccounts } from './util/Account' import Util from './util/Utils'
import { chunkArray } from './util/Utils' import { loadAccounts, loadConfig } from './util/Load'
import { login } from './functions/Login' import { Login } from './functions/Login'
import { doDailySet, doMorePromotions, doPunchCard } from './functions/Workers' import { Workers } from './functions/Workers'
import { doSearch } from './functions/activities/Search' import Activities from './functions/Activities'
import { Account } from './interface/Account' import { Account } from './interface/Account'
import { runOnZeroPoints, workers, clusters } from './config.json'
// Main bot class // Main bot class
class MicrosoftRewardsBot { export class MicrosoftRewardsBot {
private activeWorkers: number = clusters public log: typeof log
public config
public utils: Util
public activities: Activities = new Activities(this)
public browser: {
func: BrowserFunc,
utils: BrowserUtil
}
private collectedPoints: number = 0 private collectedPoints: number = 0
private browserFactory: Browser = new Browser() private activeWorkers: number
private browserFactory: Browser = new Browser(this)
private accounts: Account[] private accounts: Account[]
private workers: Workers
private login = new Login(this)
constructor() { constructor() {
this.log = log
this.accounts = [] this.accounts = []
this.utils = new Util()
this.workers = new Workers(this)
this.browser = {
func: new BrowserFunc(this),
utils: new BrowserUtil(this)
}
this.config = loadConfig()
this.activeWorkers = this.config.clusters
} }
async initialize() { async initialize() {
this.accounts = await loadAccounts() this.accounts = loadAccounts()
} }
async run() { async run() {
log('MAIN', `Bot started with ${clusters} clusters`) log('MAIN', `Bot started with ${this.config.clusters} clusters`)
// Only cluster when there's more than 1 cluster demanded // Only cluster when there's more than 1 cluster demanded
if (clusters > 1) { if (this.config.clusters > 1) {
if (cluster.isPrimary) { if (cluster.isPrimary) {
this.runMaster() this.runMaster()
} else { } else {
@@ -48,7 +68,7 @@ class MicrosoftRewardsBot {
private runMaster() { private runMaster() {
log('MAIN-PRIMARY', 'Primary process started') log('MAIN-PRIMARY', 'Primary process started')
const accountChunks = chunkArray(this.accounts, clusters) const accountChunks = this.utils.chunkArray(this.accounts, this.config.clusters)
for (let i = 0; i < accountChunks.length; i++) { for (let i = 0; i < accountChunks.length; i++) {
const worker = cluster.fork() const worker = cluster.fork()
@@ -71,7 +91,6 @@ class MicrosoftRewardsBot {
private runWorker() { private runWorker() {
log('MAIN-WORKER', `Worker ${process.pid} spawned`) log('MAIN-WORKER', `Worker ${process.pid} spawned`)
// Receive the chunk of accounts from the master // Receive the chunk of accounts from the master
process.on('message', async ({ chunk }) => { process.on('message', async ({ chunk }) => {
await this.runTasks(chunk) await this.runTasks(chunk)
@@ -86,7 +105,7 @@ class MicrosoftRewardsBot {
await this.Desktop(account) await this.Desktop(account)
// If runOnZeroPoints is false and 0 points to earn, stop and try the next account // If runOnZeroPoints is false and 0 points to earn, stop and try the next account
if (!runOnZeroPoints && this.collectedPoints === 0) { if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
continue continue
} }
@@ -119,22 +138,22 @@ class MicrosoftRewardsBot {
log('MAIN', 'Starting DESKTOP browser') log('MAIN', 'Starting DESKTOP browser')
// Login into MS Rewards // Login into MS Rewards
await login(page, account.email, account.password) await this.login.login(page, account.email, account.password)
const wentHome = await goHome(page) const wentHome = await this.browser.func.goHome(page)
if (!wentHome) { if (!wentHome) {
throw log('MAIN', 'Unable to get dashboard page', 'error') throw log('MAIN', 'Unable to get dashboard page', 'error')
} }
const data = await getDashboardData(page) const data = await this.browser.func.getDashboardData(page)
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`) log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
const earnablePoints = await getEarnablePoints(data) const earnablePoints = await this.browser.func.getEarnablePoints(data)
this.collectedPoints = earnablePoints this.collectedPoints = earnablePoints
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`) log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
// If runOnZeroPoints is false and 0 points to earn, don't continue // If runOnZeroPoints is false and 0 points to earn, don't continue
if (!runOnZeroPoints && this.collectedPoints === 0) { if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping') log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping')
// Close desktop browser // Close desktop browser
@@ -142,23 +161,23 @@ class MicrosoftRewardsBot {
} }
// Complete daily set // Complete daily set
if (workers.doDailySet) { if (this.config.workers.doDailySet) {
await doDailySet(page, data) await this.workers.doDailySet(page, data)
} }
// Complete more promotions // Complete more promotions
if (workers.doMorePromotions) { if (this.config.workers.doMorePromotions) {
await doMorePromotions(page, data) await this.workers.doMorePromotions(page, data)
} }
// Complete punch cards // Complete punch cards
if (workers.doPunchCards) { if (this.config.workers.doPunchCards) {
await doPunchCard(page, data) await this.workers.doPunchCard(page, data)
} }
// Do desktop searches // Do desktop searches
if (workers.doDesktopSearch) { if (this.config.workers.doDesktopSearch) {
await doSearch(page, data, false) await this.activities.doSearch(page, data, false)
} }
// Close desktop browser // Close desktop browser
@@ -182,11 +201,11 @@ class MicrosoftRewardsBot {
log('MAIN', 'Starting MOBILE browser') log('MAIN', 'Starting MOBILE browser')
// Login into MS Rewards // Login into MS Rewards
await login(page, account.email, account.password) await this.login.login(page, account.email, account.password)
await goHome(page) await this.browser.func.goHome(page)
const data = await getDashboardData(page) const data = await this.browser.func.getDashboardData(page)
// If no mobile searches data found, stop (Does not exist on new accounts) // If no mobile searches data found, stop (Does not exist on new accounts)
if (!data.userStatus.counters.mobileSearch) { if (!data.userStatus.counters.mobileSearch) {
@@ -197,12 +216,12 @@ class MicrosoftRewardsBot {
} }
// Do mobile searches // Do mobile searches
if (workers.doMobileSearch) { if (this.config.workers.doMobileSearch) {
await doSearch(page, data, true) await this.activities.doSearch(page, data, true)
} }
// Fetch new points // Fetch new points
const earnablePoints = await getEarnablePoints(data, page) const earnablePoints = await this.browser.func.getEarnablePoints(data, page)
// If the new earnable is 0, means we got all the points, else retract // If the new earnable is 0, means we got all the points, else retract
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints) this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)

35
src/interface/Config.ts Normal file
View File

@@ -0,0 +1,35 @@
export interface Config {
baseURL: string;
sessionPath: string;
headless: boolean;
runOnZeroPoints: boolean;
clusters: number;
workers: Workers;
searchSettings: SearchSettings;
webhook: Webhook;
}
export interface SearchSettings {
useGeoLocaleQueries: boolean;
scrollRandomResults: boolean;
clickRandomResults: boolean;
searchDelay: SearchDelay;
}
export interface SearchDelay {
min: number;
max: number;
}
export interface Webhook {
enabled: boolean;
url: string;
}
export interface Workers {
doDailySet: boolean;
doMorePromotions: boolean;
doPunchCards: boolean;
doDesktopSearch: boolean;
doMobileSearch: boolean;
}

View File

@@ -1,9 +1,11 @@
import * as fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import { Account } from '../interface/Account' import { Account } from '../interface/Account'
import { Config } from '../interface/Config'
export async function loadAccounts(): Promise<Account[]> {
export function loadAccounts(): Account[] {
try { try {
let file = 'accounts.json' let file = 'accounts.json'
@@ -19,4 +21,15 @@ export async function loadAccounts(): Promise<Account[]> {
} catch (error) { } catch (error) {
throw new Error(error as string) throw new Error(error as string)
} }
}
export function loadConfig(): Config {
try {
const configDir = path.join(__dirname, '../', 'config.json')
const config = fs.readFileSync(configDir, 'utf-8')
return JSON.parse(config)
} catch (error) {
throw new Error(error as string)
}
} }

View File

@@ -1,7 +1,7 @@
import { Webhook } from './Webhook' import { Webhook } from './Webhook'
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') { export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
const currentTime = new Date().toISOString() const currentTime = new Date().toLocaleString()
let str = '' let str = ''

View File

@@ -1,38 +1,42 @@
export async function wait(ms: number): Promise<void> { export default class Util {
return new Promise<void>((resolve) => {
setTimeout(resolve, ms)
})
}
export function getFormattedDate(ms = Date.now()): string { async wait(ms: number): Promise<void> {
const today = new Date(ms) return new Promise<void>((resolve) => {
const month = String(today.getMonth() + 1).padStart(2, '0') // January is 0 setTimeout(resolve, ms)
const day = String(today.getDate()).padStart(2, '0') })
const year = today.getFullYear() }
return `${month}/${day}/${year}` getFormattedDate(ms = Date.now()): string {
} const today = new Date(ms)
const month = String(today.getMonth() + 1).padStart(2, '0') // January is 0
export function shuffleArray<T>(array: T[]): T[] { const day = String(today.getDate()).padStart(2, '0')
const shuffledArray = array.slice() const year = today.getFullYear()
shuffledArray.sort(() => Math.random() - 0.5) return `${month}/${day}/${year}`
}
return shuffledArray
} shuffleArray<T>(array: T[]): T[] {
const shuffledArray = array.slice()
export function randomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min shuffledArray.sort(() => Math.random() - 0.5)
}
return shuffledArray
export function chunkArray<T>(arr: T[], numChunks: number): T[][] { }
const chunkSize = Math.ceil(arr.length / numChunks)
const chunks: T[][] = [] randomNumber(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min
for (let i = 0; i < arr.length; i += chunkSize) { }
const chunk = arr.slice(i, i + chunkSize)
chunks.push(chunk) chunkArray<T>(arr: T[], numChunks: number): T[][] {
const chunkSize = Math.ceil(arr.length / numChunks)
const chunks: T[][] = []
for (let i = 0; i < arr.length; i += chunkSize) {
const chunk = arr.slice(i, i + chunkSize)
chunks.push(chunk)
}
return chunks
} }
return chunks
} }

View File

@@ -1,10 +1,13 @@
import axios from 'axios' import axios from 'axios'
import { webhook } from '../config.json' import { loadConfig } from './Load'
export async function Webhook(content: string) { export async function Webhook(content: string) {
const webhook = loadConfig().webhook
if (!webhook.enabled || webhook.url.length < 10) return if (!webhook.enabled || webhook.url.length < 10) return
const request = { const request = {
method: 'POST', method: 'POST',
url: webhook.url, url: webhook.url,

View File

@@ -64,7 +64,8 @@
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/accounts.json" "src/accounts.json",
"src/config.json"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"