mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-28 19:01:04 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3fb641ecd | ||
|
|
bca1e7c896 | ||
|
|
fd7c8e36d4 | ||
|
|
dcb0c25d46 | ||
|
|
a8cb5482d4 | ||
|
|
28286ff9fe | ||
|
|
1d6167aeca | ||
|
|
3b15fe19a7 | ||
|
|
66dcdd4a7a | ||
|
|
40f32af67f | ||
|
|
c2b68faa74 |
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@@ -1,20 +0,0 @@
|
||||
{
|
||||
// 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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,6 +11,11 @@ Under development, however mainly for personal use!
|
||||
5. Run `npm run build` to build the script
|
||||
6. Run `npm run start` to start the built script
|
||||
|
||||
## Notes ##
|
||||
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run chrome-kill-win` script. (Windows)
|
||||
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
|
||||
|
||||
|
||||
## Features ##
|
||||
- [x] Multi-Account Support
|
||||
- [x] Session Storing
|
||||
@@ -31,8 +36,7 @@ Under development, however mainly for personal use!
|
||||
- [x] Completing Click Rewards
|
||||
- [x] Completing Polls
|
||||
- [x] Completing Punchcards
|
||||
- [ ] Solving This Or That Quiz
|
||||
- [x] Clicking Promotional Items
|
||||
- [x] Solving This Or That Quiz (Random)
|
||||
- [x] Solving ABC Quiz
|
||||
- [ ] Completing Shopping Game
|
||||
- [ ] Completing Gaming Tab
|
||||
|
||||
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "microsoft-rewards-script",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.2",
|
||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
@@ -10,7 +10,8 @@
|
||||
"build": "tsc",
|
||||
"start": "node ./dist/index.js",
|
||||
"ts-start": "ts-node ./src/index.ts",
|
||||
"dev": "ts-node ./src/index.ts -dev"
|
||||
"dev": "ts-node ./src/index.ts -dev",
|
||||
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\""
|
||||
},
|
||||
"keywords": [
|
||||
"Bing Rewards",
|
||||
@@ -25,16 +26,16 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"typescript": "^5.2.2"
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-plugin-modules-newline": "^0.0.6",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1",
|
||||
"axios": "^1.6.2",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"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"
|
||||
"fingerprint-generator": "^2.1.45",
|
||||
"fingerprint-injector": "^2.1.45",
|
||||
"puppeteer": "^21.6.1",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,75 @@
|
||||
import puppeteer from 'puppeteer-extra'
|
||||
import stealthPlugin from 'puppeteer-extra-plugin-stealth'
|
||||
import puppeteer from 'puppeteer'
|
||||
import { FingerprintInjector } from 'fingerprint-injector'
|
||||
import { FingerprintGenerator } from 'fingerprint-generator'
|
||||
|
||||
import { getUserAgent } from '../util/UserAgent'
|
||||
import { loadSesion } from './BrowserFunc'
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { loadSesion } from '../util/Load'
|
||||
|
||||
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 {
|
||||
private bot: MicrosoftRewardsBot
|
||||
private usedUserAgents: string[] = []
|
||||
|
||||
async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) {
|
||||
const userAgent = await getUserAgent(isMobile)
|
||||
constructor(bot: MicrosoftRewardsBot) {
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
async createBrowser(email: string, proxy: AccountProxy) {
|
||||
// const userAgent = await getUserAgent(isMobile)
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: headless,
|
||||
userDataDir: await loadSesion(email),
|
||||
headless: this.bot.config.headless ? 'new' : false,
|
||||
userDataDir: await loadSesion(this.bot.config.sessionPath, email),
|
||||
args: [
|
||||
'--no-sandbox',
|
||||
'--mute-audio',
|
||||
'--disable-setuid-sandbox',
|
||||
`--user-agent=${userAgent.userAgent}`,
|
||||
isMobile ? '--window-size=568,1024' : '',
|
||||
'--ignore-certificate-errors',
|
||||
'--ignore-certificate-errors-spki-list',
|
||||
'--ignore-ssl-errors',
|
||||
proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : ''
|
||||
]
|
||||
})
|
||||
|
||||
return browser
|
||||
let fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||
browsers: ['edge']
|
||||
})
|
||||
|
||||
if (this.usedUserAgents) {
|
||||
while (this.usedUserAgents.includes(fingerPrintData.fingerprint.navigator.userAgent)) {
|
||||
fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||
browsers: ['edge']
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.usedUserAgents.push(fingerPrintData.fingerprint.navigator.userAgent)
|
||||
|
||||
// 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, fingerPrintData)
|
||||
return page
|
||||
}
|
||||
|
||||
this.bot.log('BROWSER', `Created browser with User-Agent: "${fingerPrintData.fingerprint.navigator.userAgent}"`)
|
||||
|
||||
return browser
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Browser
|
||||
@@ -1,93 +1,109 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { CheerioAPI, load } from 'cheerio'
|
||||
|
||||
import { tryDismissAllMessages, tryDismissCookieBanner } from './BrowserUtil'
|
||||
import { getFormattedDate, wait } from './../util/Utils'
|
||||
import { log } from './../util/Logger'
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
||||
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
|
||||
|
||||
constructor(bot: MicrosoftRewardsBot) {
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Navigate the provided page to rewards homepage
|
||||
* @param {Page} page Puppeteer page
|
||||
*/
|
||||
async goHome(page: Page) {
|
||||
|
||||
try {
|
||||
const dashboardURL = new URL(baseURL)
|
||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||
|
||||
await page.goto(baseURL)
|
||||
if (page.url() === dashboardURL.href) {
|
||||
return
|
||||
}
|
||||
|
||||
await page.goto(this.bot.config.baseURL)
|
||||
|
||||
const maxIterations = 5 // Maximum iterations set to 5
|
||||
|
||||
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
||||
await wait(3000)
|
||||
await tryDismissCookieBanner(page)
|
||||
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: 3000 }).then(() => true).catch(() => false)
|
||||
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
|
||||
if (isSuspended) {
|
||||
log('GO-HOME', 'This account is suspended!', 'error')
|
||||
this.bot.log('GO-HOME', 'This account is suspended!', 'error')
|
||||
throw new Error('Account has been suspended!')
|
||||
}
|
||||
|
||||
try {
|
||||
// If activities are found, exit the loop
|
||||
await page.waitForSelector('#more-activities', { timeout: 1000 })
|
||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
||||
break
|
||||
|
||||
} catch (error) {
|
||||
// Continue if element is not found
|
||||
}
|
||||
|
||||
// Below runs if the homepage was unable to be visited
|
||||
const currentURL = new URL(page.url())
|
||||
|
||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||
await tryDismissAllMessages(page)
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
await wait(2000)
|
||||
await page.goto(baseURL)
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.goto(this.bot.config.baseURL)
|
||||
} else {
|
||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
||||
break
|
||||
}
|
||||
|
||||
await wait(5000)
|
||||
log('GO-HOME', 'Visited homepage successfully')
|
||||
await this.bot.utils.wait(5000)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('An error occurred:', error)
|
||||
return false
|
||||
throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export async function getDashboardData(page: Page): Promise<DashboardData> {
|
||||
const dashboardURL = new URL(baseURL)
|
||||
const currentURL = new URL(page.url())
|
||||
/**
|
||||
* Fetch user dashboard data
|
||||
* @returns {DashboardData} Object of user bing rewards dashboard data
|
||||
*/
|
||||
async getDashboardData(): Promise<DashboardData> {
|
||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||
const currentURL = new URL(this.bot.homePage.url())
|
||||
|
||||
// Should never happen since tasks are opened in a new tab!
|
||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||
log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
|
||||
await goHome(page)
|
||||
this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
|
||||
await this.goHome(this.bot.homePage)
|
||||
}
|
||||
|
||||
// Reload the page to get new data
|
||||
await page.reload({ waitUntil: 'networkidle2' })
|
||||
await this.bot.homePage.reload({ waitUntil: 'networkidle2' })
|
||||
|
||||
const scriptContent = await page.evaluate(() => {
|
||||
const scriptContent = await this.bot.homePage.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 log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
||||
}
|
||||
})
|
||||
|
||||
// Extract the dashboard object from the script content
|
||||
const dashboardData = await page.evaluate(scriptContent => {
|
||||
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
||||
// Extract the dashboard object using regex
|
||||
const regex = /var dashboard = (\{.*?\});/s
|
||||
const match = regex.exec(scriptContent)
|
||||
@@ -95,14 +111,79 @@ export async function getDashboardData(page: Page): Promise<DashboardData> {
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
} else {
|
||||
throw log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
}, scriptContent)
|
||||
|
||||
return dashboardData
|
||||
}
|
||||
|
||||
export async function getQuizData(page: Page): Promise<QuizData> {
|
||||
/**
|
||||
* Get search point counters
|
||||
* @returns {Counters} Object of search counter data
|
||||
*/
|
||||
async getSearchPoints(): Promise<Counters> {
|
||||
const dashboardData = await this.getDashboardData() // Always fetch newest data
|
||||
|
||||
return dashboardData.userStatus.counters
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total earnable points
|
||||
* @returns {number} Total earnable points
|
||||
*/
|
||||
async getEarnablePoints(): Promise<number> {
|
||||
try {
|
||||
const data = await this.getDashboardData()
|
||||
|
||||
// These only include the points from tasks that the script can complete!
|
||||
let totalEarnablePoints = 0
|
||||
|
||||
// Desktop Search Points
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current point amount
|
||||
* @returns {number} Current total point amount
|
||||
*/
|
||||
async getCurrentPoints(): Promise<number> {
|
||||
try {
|
||||
const data = await this.getDashboardData()
|
||||
|
||||
return data.userStatus.availablePoints
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse quiz data from provided page
|
||||
* @param {Page} page Puppeteer page
|
||||
* @returns {QuizData} Quiz data object
|
||||
*/
|
||||
async getQuizData(page: Page): Promise<QuizData> {
|
||||
try {
|
||||
const html = await page.content()
|
||||
const $ = load(html)
|
||||
@@ -119,103 +200,34 @@ export async function getQuizData(page: Page): Promise<QuizData> {
|
||||
const quizData = JSON.parse(match[1])
|
||||
return quizData
|
||||
} else {
|
||||
throw log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
|
||||
throw this.bot.log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
|
||||
}
|
||||
} else {
|
||||
throw log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
|
||||
throw this.bot.log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.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> {
|
||||
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||
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
|
||||
} catch (error) {
|
||||
throw log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCurrentPoints(data: DashboardData, page: null | Page = null): Promise<number> {
|
||||
try {
|
||||
// Fetch new data if page is provided
|
||||
if (page) {
|
||||
data = await getDashboardData(page)
|
||||
}
|
||||
|
||||
return data.userStatus.availablePoints
|
||||
} catch (error) {
|
||||
throw log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
export async function waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 5000 })
|
||||
await wait(2000)
|
||||
await page.waitForSelector('span.rqMCredits', { visible: true, timeout: 10_000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
log('QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkQuizCompleted(page: Page): Promise<boolean> {
|
||||
async checkQuizCompleted(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 1000 })
|
||||
await wait(2000)
|
||||
await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 2000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
@@ -223,14 +235,14 @@ export async function checkQuizCompleted(page: Page): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshCheerio(page: Page): Promise<CheerioAPI> {
|
||||
async refreshCheerio(page: Page): Promise<CheerioAPI> {
|
||||
const html = await page.content()
|
||||
const $ = load(html)
|
||||
|
||||
return $
|
||||
}
|
||||
|
||||
export async function getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise<string> {
|
||||
async getPunchCardActivity(page: Page, activity: PromotionalItem | MorePromotion): Promise<string> {
|
||||
let selector = ''
|
||||
try {
|
||||
const html = await page.content()
|
||||
@@ -241,8 +253,10 @@ export async function getPunchCardActivity(page: Page, activity: PromotionalItem
|
||||
selector = `a[href*="${element.attribs.href}"]`
|
||||
}
|
||||
} catch (error) {
|
||||
log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,16 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { wait } from './../util/Utils'
|
||||
import { log } from './../util/Logger'
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
export async function tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
|
||||
export default class BrowserUtil {
|
||||
private bot: MicrosoftRewardsBot
|
||||
|
||||
constructor(bot: MicrosoftRewardsBot) {
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
const buttons = [
|
||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||
@@ -31,7 +38,7 @@ export async function tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
return result
|
||||
}
|
||||
|
||||
export async function tryDismissCookieBanner(page: Page): Promise<void> {
|
||||
async tryDismissCookieBanner(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.waitForSelector('#cookieConsentContainer', { timeout: 1000 })
|
||||
const cookieBanner = await page.$('#cookieConsentContainer')
|
||||
@@ -40,7 +47,7 @@ export async function tryDismissCookieBanner(page: Page): Promise<void> {
|
||||
const button = await cookieBanner.$('button')
|
||||
if (button) {
|
||||
await button.click()
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +56,7 @@ export async function tryDismissCookieBanner(page: Page): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryDismissBingCookieBanner(page: Page): Promise<void> {
|
||||
async tryDismissBingCookieBanner(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 })
|
||||
const cookieBanner = await page.$('#bnp_btn_accept')
|
||||
@@ -62,9 +69,9 @@ export async function tryDismissBingCookieBanner(page: Page): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLatestTab(page: Page): Promise<Page> {
|
||||
async getLatestTab(page: Page): Promise<Page> {
|
||||
try {
|
||||
await wait(500)
|
||||
await this.bot.utils.wait(500)
|
||||
|
||||
const browser = page.browser()
|
||||
const pages = await browser.pages()
|
||||
@@ -74,8 +81,44 @@ export async function getLatestTab(page: Page): Promise<Page> {
|
||||
return newTab
|
||||
}
|
||||
|
||||
throw log('GET-NEW-TAB', 'Unable to get latest tab', 'error')
|
||||
throw this.bot.log('GET-NEW-TAB', 'Unable to get latest tab', 'error')
|
||||
} catch (error) {
|
||||
throw log('GET-NEW-TAB', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log('GET-NEW-TAB', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async getTabs(page: Page) {
|
||||
try {
|
||||
const browser = page.browser()
|
||||
const pages = await browser.pages()
|
||||
|
||||
const homeTab = pages[1]
|
||||
let homeTabURL: URL
|
||||
|
||||
if (!homeTab) {
|
||||
throw this.bot.log('GET-TABS', 'Home tab could not be found!', 'error')
|
||||
|
||||
} else {
|
||||
homeTabURL = new URL(homeTab.url())
|
||||
|
||||
if (homeTabURL.hostname !== 'rewards.bing.com') {
|
||||
throw this.bot.log('GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const workerTab = pages[2]
|
||||
if (!workerTab) {
|
||||
throw this.bot.log('GET-TABS', 'Worker tab could not be found!', 'error')
|
||||
}
|
||||
|
||||
return {
|
||||
homeTab: homeTab,
|
||||
workerTab: workerTab
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-TABS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"baseURL": "https://rewards.bing.com",
|
||||
"sessionPath": "sessions",
|
||||
"headless": false,
|
||||
"runOnZeroPoints": true,
|
||||
"runOnZeroPoints": false,
|
||||
"clusters": 1,
|
||||
"workers": {
|
||||
"doDailySet": true,
|
||||
@@ -14,7 +14,11 @@
|
||||
"searchSettings": {
|
||||
"useGeoLocaleQueries": false,
|
||||
"scrollRandomResults": true,
|
||||
"clickRandomResults": true
|
||||
"clickRandomResults": true,
|
||||
"searchDelay": {
|
||||
"min": 10000,
|
||||
"max": 20000
|
||||
}
|
||||
},
|
||||
"webhook": {
|
||||
"enabled": false,
|
||||
|
||||
52
src/functions/Activities.ts
Normal file
52
src/functions/Activities.ts
Normal 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): Promise<void> => {
|
||||
const search = new Search(this.bot)
|
||||
await search.doSearch(page, data)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +1,75 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import readline from 'readline'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
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
|
||||
|
||||
constructor(bot: MicrosoftRewardsBot) {
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
async login(page: Page, email: string, password: string) {
|
||||
|
||||
try {
|
||||
// Navigate to the Bing login page
|
||||
await page.goto('https://login.live.com/')
|
||||
await page.goto('https://rewards.bing.com/signin')
|
||||
|
||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 5000 }).then(() => true).catch(() => false)
|
||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
|
||||
|
||||
if (!isLoggedIn) {
|
||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 5000 }).then(() => true).catch(() => false)
|
||||
// Check if account is locked
|
||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false)
|
||||
if (isLocked) {
|
||||
log('LOGIN', 'This account has been locked!', 'error')
|
||||
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 execLogin(page, email, password)
|
||||
log('LOGIN', 'Logged into Microsoft successfully')
|
||||
await this.execLogin(page, email, password)
|
||||
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
||||
} else {
|
||||
log('LOGIN', 'Already logged in')
|
||||
this.bot.log('LOGIN', 'Already logged in')
|
||||
}
|
||||
|
||||
// Check if logged in to bing
|
||||
await checkBingLogin(page)
|
||||
await this.checkBingLogin(page)
|
||||
|
||||
// We're done logging in
|
||||
log('LOGIN', 'Logged in successfully')
|
||||
this.bot.log('LOGIN', 'Logged in successfully')
|
||||
|
||||
} catch (error) {
|
||||
// Throw and don't continue
|
||||
throw log('LOGIN', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.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.click('#idSIButton9')
|
||||
|
||||
log('LOGIN', 'Email entered successfully')
|
||||
this.bot.log('LOGIN', 'Email entered successfully')
|
||||
|
||||
try {
|
||||
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.type('#i0118', password)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
// When erroring at this stage it means a 2FA code is required
|
||||
} catch (error) {
|
||||
log('LOGIN', '2FA code required')
|
||||
this.bot.log('LOGIN', '2FA code required')
|
||||
|
||||
// Wait for user input
|
||||
const code = await new Promise<string>((resolve) => {
|
||||
rl.question('Enter 2FA code:\n', (input) => {
|
||||
rl.close()
|
||||
@@ -72,23 +81,23 @@ async function execLogin(page: Page, email: string, password: string) {
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
} 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)
|
||||
while (currentURL.pathname !== '/' || currentURL.hostname !== 'rewards.bing.com') {
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
currentURL.href = page.url()
|
||||
}
|
||||
|
||||
// Wait for login to complete
|
||||
await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 })
|
||||
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
|
||||
}
|
||||
|
||||
async function checkBingLogin(page: Page): Promise<void> {
|
||||
private async checkBingLogin(page: Page): Promise<void> {
|
||||
try {
|
||||
log('LOGIN-BING', 'Verifying Bing login')
|
||||
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
|
||||
@@ -97,25 +106,25 @@ async function checkBingLogin(page: Page): Promise<void> {
|
||||
const currentUrl = new URL(page.url())
|
||||
|
||||
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
|
||||
await wait(3000)
|
||||
await tryDismissBingCookieBanner(page)
|
||||
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
|
||||
|
||||
const loggedIn = await checkBingLoginStatus(page)
|
||||
if (loggedIn) {
|
||||
log('LOGIN-BING', 'Bing login verification passed!')
|
||||
const loggedIn = await this.checkBingLoginStatus(page)
|
||||
// If mobile browser, skip this step
|
||||
if (loggedIn || this.bot.isMobile) {
|
||||
this.bot.log('LOGIN-BING', 'Bing login verification passed!')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
await wait(1000)
|
||||
await this.bot.utils.wait(1000)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log('LOGIN-BING', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('LOGIN-BING', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function checkBingLoginStatus(page: Page): Promise<boolean> {
|
||||
private async checkBingLoginStatus(page: Page): Promise<boolean> {
|
||||
try {
|
||||
await page.waitForSelector('#id_n', { timeout: 5000 })
|
||||
return true
|
||||
@@ -123,3 +132,5 @@ async function checkBingLoginStatus(page: Page): Promise<boolean> {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,76 +1,85 @@
|
||||
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 { baseURL } from '../config.json'
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
export class Workers {
|
||||
public bot: MicrosoftRewardsBot
|
||||
|
||||
constructor(bot: MicrosoftRewardsBot) {
|
||||
this.bot = bot
|
||||
}
|
||||
|
||||
// Daily Set
|
||||
export async function doDailySet(page: Page, data: DashboardData) {
|
||||
|
||||
const todayData = data.dailySetPromotions[getFormattedDate()]
|
||||
async doDailySet(page: Page, data: DashboardData) {
|
||||
const todayData = data.dailySetPromotions[this.bot.utils.getFormattedDate()]
|
||||
|
||||
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
||||
|
||||
if (!activitiesUncompleted.length) {
|
||||
log('DAILY-SET', 'All Daily Set" items have already been completed')
|
||||
this.bot.log('DAILY-SET', 'All Daily Set" items have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
// Solve Activities
|
||||
log('DAILY-SET', 'Started solving "Daily Set" items')
|
||||
this.bot.log('DAILY-SET', 'Started solving "Daily Set" items')
|
||||
|
||||
await solveActivities(page, activitiesUncompleted)
|
||||
await this.solveActivities(page, activitiesUncompleted)
|
||||
|
||||
log('DAILY-SET', 'All "Daily Set" items have been completed')
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
// Always return to the homepage if not already
|
||||
await this.bot.browser.func.goHome(page)
|
||||
|
||||
this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
|
||||
}
|
||||
|
||||
// Punch Card
|
||||
export async function doPunchCard(page: Page, data: DashboardData) {
|
||||
async 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')
|
||||
this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
for (const punchCard of punchCardsUncompleted) {
|
||||
// Get latest page for each card
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
|
||||
|
||||
// Solve Activities
|
||||
log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
|
||||
|
||||
const browser = page.browser()
|
||||
page = await browser.newPage()
|
||||
this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
|
||||
|
||||
// Got to punch card index page in a new tab
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: baseURL })
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||
|
||||
await solveActivities(page, activitiesUncompleted, punchCard)
|
||||
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||
await page.waitForNetworkIdle({ timeout: 5_000 }).catch(() => { })
|
||||
|
||||
// Close the punch card index page
|
||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
const pages = await (page.browser()).pages()
|
||||
|
||||
if (pages.length > 3) {
|
||||
await page.close()
|
||||
|
||||
log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
||||
} else {
|
||||
await this.bot.browser.func.goHome(page)
|
||||
}
|
||||
|
||||
log('PUNCH-CARD', 'All "Punch Card" items have been completed')
|
||||
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')
|
||||
}
|
||||
|
||||
// More Promotions
|
||||
export async function doMorePromotions(page: Page, data: DashboardData) {
|
||||
async doMorePromotions(page: Page, data: DashboardData) {
|
||||
const morePromotions = data.morePromotions
|
||||
|
||||
// Check if there is a promotional item
|
||||
@@ -81,45 +90,70 @@ export async function doMorePromotions(page: Page, data: DashboardData) {
|
||||
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
||||
|
||||
if (!activitiesUncompleted.length) {
|
||||
log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
// Solve Activities
|
||||
log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
|
||||
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
|
||||
|
||||
await solveActivities(page, activitiesUncompleted)
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||
await this.solveActivities(page, activitiesUncompleted)
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
// Always return to the homepage if not already
|
||||
await this.bot.browser.func.goHome(page)
|
||||
|
||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||
}
|
||||
|
||||
// Solve all the different types of activities
|
||||
async function solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
||||
private async solveActivities(activityPage: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
||||
const activityInitial = activityPage.url() // Homepage for Daily/More and Index for promotions
|
||||
|
||||
for (const activity of activities) {
|
||||
try {
|
||||
// Reselect the worker page
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
|
||||
const pages = await activityPage.browser().pages()
|
||||
if (pages.length > 3) {
|
||||
await activityPage.close()
|
||||
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
if (activityPage.url() !== activityInitial) {
|
||||
await activityPage.goto(activityInitial)
|
||||
}
|
||||
|
||||
|
||||
let selector = `[data-bi-id="${activity.offerId}"]`
|
||||
|
||||
if (punchCard) {
|
||||
selector = await getPunchCardActivity(page, activity)
|
||||
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
||||
|
||||
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
||||
|
||||
// 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)
|
||||
await activityPage.click(selector)
|
||||
|
||||
// Select the new activity page
|
||||
const activityPage = await getLatestTab(page)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
|
||||
// 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(() => { })
|
||||
await this.bot.utils.wait(5000)
|
||||
|
||||
switch (activity.promotionType) {
|
||||
// Quiz (Poll, Quiz or ABC)
|
||||
@@ -129,45 +163,49 @@ async function solveActivities(page: Page, activities: PromotionalItem[] | MoreP
|
||||
case 10:
|
||||
// Normal poll
|
||||
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
|
||||
log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
|
||||
await doPoll(activityPage)
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
|
||||
await this.bot.activities.doPoll(activityPage)
|
||||
} else { // ABC
|
||||
log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
|
||||
await doABC(activityPage)
|
||||
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:
|
||||
log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
|
||||
await doThisOrThat(activityPage)
|
||||
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:
|
||||
log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
|
||||
await doQuiz(activityPage)
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
|
||||
await this.bot.activities.doQuiz(activityPage)
|
||||
break
|
||||
}
|
||||
break
|
||||
|
||||
// UrlReward (Visit)
|
||||
case 'urlreward':
|
||||
log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
|
||||
await doUrlReward(activityPage)
|
||||
this.bot.log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
|
||||
await this.bot.activities.doUrlReward(activityPage)
|
||||
break
|
||||
|
||||
// Misc, Usually UrlReward Type
|
||||
default:
|
||||
log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
|
||||
await doUrlReward(activityPage)
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
|
||||
await this.bot.activities.doUrlReward(activityPage)
|
||||
break
|
||||
}
|
||||
|
||||
// Cooldown
|
||||
await wait(1500)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
} catch (error) {
|
||||
log('ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,52 +1,52 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { refreshCheerio } from '../../browser/BrowserFunc'
|
||||
import { getLatestTab } from '../../browser/BrowserUtil'
|
||||
import { log } from '../../util/Logger'
|
||||
import { randomNumber, wait } from '../../util/Utils'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
export async function doABC(page: Page) {
|
||||
log('ABC', 'Trying to complete poll')
|
||||
|
||||
export class ABC extends Workers {
|
||||
|
||||
async doABC(page: Page) {
|
||||
this.bot.log('ABC', 'Trying to complete poll')
|
||||
|
||||
try {
|
||||
await wait(2000)
|
||||
let $ = await refreshCheerio(page)
|
||||
let $ = await this.bot.browser.func.refreshCheerio(page)
|
||||
|
||||
// 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: 5000 })
|
||||
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 10_000 })
|
||||
|
||||
const answers = $('.wk_OptionClickClass')
|
||||
const answer = answers[randomNumber(0, 2)]?.attribs['id']
|
||||
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
|
||||
|
||||
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 5000 })
|
||||
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 10_000 })
|
||||
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.click(`#${answer}`) // Click answer
|
||||
|
||||
await wait(4000)
|
||||
await page.waitForSelector('div.wk_button', { visible: true, timeout: 5000 })
|
||||
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 getLatestTab(page)
|
||||
$ = await refreshCheerio(page)
|
||||
await wait(1000)
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
$ = await this.bot.browser.func.refreshCheerio(page)
|
||||
await this.bot.utils.wait(1000)
|
||||
}
|
||||
|
||||
await wait(4000)
|
||||
await this.bot.utils.wait(4000)
|
||||
await page.close()
|
||||
|
||||
if (i === maxIterations) {
|
||||
log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
|
||||
this.bot.log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
|
||||
} else {
|
||||
log('ABC', 'Completed the ABC successfully')
|
||||
this.bot.log('ABC', 'Completed the ABC successfully')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
log('ABC', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('ABC', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,26 +1,29 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { log } from '../../util/Logger'
|
||||
import { randomNumber, wait } from '../../util/Utils'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
export async function doPoll(page: Page) {
|
||||
log('POLL', 'Trying to complete poll')
|
||||
|
||||
export class Poll extends Workers {
|
||||
|
||||
async doPoll(page: Page) {
|
||||
this.bot.log('POLL', 'Trying to complete poll')
|
||||
|
||||
try {
|
||||
const buttonId = `#btoption${Math.floor(randomNumber(0, 1))}`
|
||||
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||
|
||||
await page.waitForNetworkIdle({ timeout: 5000 })
|
||||
await page.waitForSelector(buttonId, { visible: true, timeout: 5000 })
|
||||
await wait(2000)
|
||||
await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.click(buttonId)
|
||||
|
||||
await wait(4000)
|
||||
await this.bot.utils.wait(4000)
|
||||
await page.close()
|
||||
|
||||
log('POLL', 'Completed the poll successfully')
|
||||
this.bot.log('POLL', 'Completed the poll successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
log('POLL', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('POLL', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { getQuizData, waitForQuizRefresh } from '../../browser/BrowserFunc'
|
||||
import { wait } from '../../util/Utils'
|
||||
import { log } from '../../util/Logger'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
export async function doQuiz(page: Page) {
|
||||
log('QUIZ', 'Trying to complete quiz')
|
||||
|
||||
export class Quiz extends Workers {
|
||||
|
||||
async doQuiz(page: Page) {
|
||||
this.bot.log('QUIZ', 'Trying to complete quiz')
|
||||
|
||||
try {
|
||||
await page.waitForNetworkIdle({ timeout: 5000 })
|
||||
await wait(2000)
|
||||
|
||||
// Check if the quiz has been started or not
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
|
||||
if (quizNotStarted) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
log('QUIZ', 'Quiz has already been started, trying to finish it')
|
||||
this.bot.log('QUIZ', 'Quiz has already been started, trying to finish it')
|
||||
}
|
||||
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
let quizData = await getQuizData(page)
|
||||
let quizData = await this.bot.browser.func.getQuizData(page)
|
||||
const questionsRemaining = quizData.maxQuestions - quizData.CorrectlyAnsweredQuestionCount // Amount of questions remaining
|
||||
|
||||
// All questions
|
||||
@@ -31,7 +29,7 @@ export async function doQuiz(page: Page) {
|
||||
const answers: string[] = []
|
||||
|
||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 5000 })
|
||||
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') {
|
||||
@@ -46,49 +44,48 @@ export async function doQuiz(page: Page) {
|
||||
// Click the answer on page
|
||||
await page.click(answer)
|
||||
|
||||
const refreshSuccess = await waitForQuizRefresh(page)
|
||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||
if (!refreshSuccess) {
|
||||
await page.close()
|
||||
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Other type quiz
|
||||
// Other type quiz, lightspeed
|
||||
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
|
||||
quizData = await getQuizData(page) // Refresh Quiz Data
|
||||
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: 5000 })
|
||||
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 waitForQuizRefresh(page)
|
||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||
if (!refreshSuccess) {
|
||||
await page.close()
|
||||
log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
await wait(2000)
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Done with
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.close()
|
||||
log('QUIZ', 'Completed the quiz successfully')
|
||||
this.bot.log('QUIZ', 'Completed the quiz successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
log('QUIZ', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('QUIZ', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +1,54 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import axios from 'axios'
|
||||
|
||||
import { getLatestTab } from '../../browser/BrowserUtil'
|
||||
import { getSearchPoints } from '../../browser/BrowserFunc'
|
||||
import { log } from '../../util/Logger'
|
||||
import { randomNumber, shuffleArray, wait } from '../../util/Utils'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { searchSettings } from '../../config.json'
|
||||
|
||||
import { DashboardData, DashboardImpression } from '../../interface/DashboardData'
|
||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
|
||||
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
|
||||
const edgeData = data.userStatus.counters.pcSearch[1] as DashboardImpression // Edge searches
|
||||
const genericData = data.userStatus.counters.pcSearch[0] as DashboardImpression // Normal searches
|
||||
export class Search extends Workers {
|
||||
|
||||
let missingPoints = (mobile && mobileData) ?
|
||||
(mobileData.pointProgressMax - mobileData.pointProgress) :
|
||||
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
|
||||
private searchPageURL = 'https://bing.com'
|
||||
|
||||
if (missingPoints == 0) {
|
||||
log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
|
||||
public async doSearch(page: Page, data: DashboardData) {
|
||||
this.bot.log('SEARCH-BING', 'Starting bing searches')
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
let searchCounters: Counters = await this.bot.browser.func.getSearchPoints()
|
||||
let missingPoints = this.calculatePoints(searchCounters)
|
||||
|
||||
if (missingPoints === 0) {
|
||||
this.bot.log('SEARCH-BING', `Bing searches for ${this.bot.isMobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate search queries
|
||||
let googleSearchQueries = await getGoogleTrends(data.userProfile.attributes.country, missingPoints)
|
||||
googleSearchQueries = shuffleArray(googleSearchQueries)
|
||||
let googleSearchQueries = await this.getGoogleTrends(data.userProfile.attributes.country, missingPoints)
|
||||
googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
|
||||
|
||||
// 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('https://bing.com')
|
||||
await page.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))
|
||||
// Mobile search doesn't seem to like related queries?
|
||||
googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : 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}`)
|
||||
this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${this.bot.isMobile}`)
|
||||
|
||||
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)
|
||||
searchCounters = await this.bingSearch(page, query)
|
||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||
|
||||
// If the new point amount is the same as before
|
||||
if (newMissingPoints == missingPoints) {
|
||||
@@ -72,13 +59,13 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
||||
|
||||
missingPoints = newMissingPoints
|
||||
|
||||
if (missingPoints == 0) {
|
||||
if (missingPoints === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
// If we didn't gain points for 10 iterations, assume it's stuck
|
||||
if (maxLoop > 10) {
|
||||
log('SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn')
|
||||
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
|
||||
}
|
||||
@@ -86,27 +73,21 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
||||
|
||||
// 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`)
|
||||
this.bot.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 GoogleSearch
|
||||
|
||||
// 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) {
|
||||
// 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.topic)
|
||||
this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${this.bot.isMobile}`)
|
||||
|
||||
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)
|
||||
searchCounters = await this.bingSearch(page, query.topic)
|
||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||
|
||||
// If the new point amount is the same as before
|
||||
if (newMissingPoints == missingPoints) {
|
||||
@@ -118,13 +99,13 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
||||
missingPoints = newMissingPoints
|
||||
|
||||
// If we satisfied the searches
|
||||
if (missingPoints == 0) {
|
||||
if (missingPoints === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -132,17 +113,17 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
||||
}
|
||||
}
|
||||
|
||||
log('SEARCH-BING', 'Completed searches')
|
||||
this.bot.log('SEARCH-BING', 'Completed searches')
|
||||
}
|
||||
|
||||
async function bingSearch(page: Page, searchPage: Page, query: string) {
|
||||
private async bingSearch(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 wait(500)
|
||||
await this.bot.utils.wait(500)
|
||||
await searchPage.keyboard.down('Control')
|
||||
await searchPage.keyboard.press('A')
|
||||
await searchPage.keyboard.press('Backspace')
|
||||
@@ -150,50 +131,66 @@ async function bingSearch(page: Page, searchPage: Page, query: string) {
|
||||
await searchPage.keyboard.type(query)
|
||||
await searchPage.keyboard.press('Enter')
|
||||
|
||||
if (searchSettings.scrollRandomResults) {
|
||||
await wait(2000)
|
||||
await randomScroll(searchPage)
|
||||
if (this.bot.config.searchSettings.scrollRandomResults) {
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.randomScroll(searchPage)
|
||||
}
|
||||
|
||||
if (searchSettings.clickRandomResults) {
|
||||
await wait(2000)
|
||||
await clickRandomLink(searchPage)
|
||||
if (this.bot.config.searchSettings.clickRandomResults) {
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.clickRandomLink(searchPage)
|
||||
}
|
||||
|
||||
await wait(Math.floor(randomNumber(10_000, 20_000)))
|
||||
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 getSearchPoints(page)
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
|
||||
} catch (error) {
|
||||
if (i === 5) {
|
||||
log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
|
||||
this.bot.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')
|
||||
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
|
||||
|
||||
await wait(4000)
|
||||
// Reset the tabs
|
||||
const browser = searchPage.browser()
|
||||
const tabs = await browser.pages()
|
||||
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||
|
||||
if (tabs.length === 4) {
|
||||
await lastTab.close()
|
||||
|
||||
} else if (tabs.length === 2) {
|
||||
const newPage = await browser.newPage()
|
||||
await newPage.goto(this.searchPageURL)
|
||||
|
||||
} else {
|
||||
await lastTab.goBack()
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(4000)
|
||||
}
|
||||
}
|
||||
|
||||
log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
|
||||
return await getSearchPoints(page)
|
||||
this.bot.log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
}
|
||||
|
||||
async function getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
||||
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
||||
const queryTerms: GoogleSearch[] = []
|
||||
let i = 0
|
||||
|
||||
geoLocale = (searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
||||
|
||||
log('SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
||||
this.bot.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 = formatDate(date)
|
||||
const formattedDate = this.formatDate(date)
|
||||
|
||||
try {
|
||||
const request = {
|
||||
@@ -211,19 +208,19 @@ async function getGoogleTrends(geoLocale: string, queryCount: number): Promise<G
|
||||
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
|
||||
queryTerms.push({
|
||||
topic: topic.title.query.toLowerCase(),
|
||||
related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase())
|
||||
related: topic.relatedQueries.map(x => x.query.toLowerCase())
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
return queryTerms
|
||||
}
|
||||
|
||||
async function getRelatedTerms(term: string): Promise<string[]> {
|
||||
private async getRelatedTerms(term: string): Promise<string[]> {
|
||||
try {
|
||||
const request = {
|
||||
url: `https://api.bing.com/osjson.aspx?query=${term}`,
|
||||
@@ -237,12 +234,12 @@ async function getRelatedTerms(term: string): Promise<string[]> {
|
||||
|
||||
return response.data[1] as string[]
|
||||
} catch (error) {
|
||||
log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
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')
|
||||
@@ -250,82 +247,70 @@ function formatDate(date: Date): string {
|
||||
return `${year}${month}${day}`
|
||||
}
|
||||
|
||||
async function randomScroll(page: Page) {
|
||||
private async randomScroll(page: Page) {
|
||||
try {
|
||||
// Press the arrow down key to scroll
|
||||
for (let i = 0; i < randomNumber(5, 100); i++) {
|
||||
for (let i = 0; i < this.bot.utils.randomNumber(5, 400); i++) {
|
||||
await page.keyboard.press('ArrowDown')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async function clickRandomLink(page: Page) {
|
||||
private async clickRandomLink(page: Page) {
|
||||
try {
|
||||
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
|
||||
|
||||
// Wait for website to load
|
||||
await wait(3000)
|
||||
|
||||
// Will get current tab if no new one is created
|
||||
let lastTab = await getLatestTab(page)
|
||||
let lastTab = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
// Wait for the body of the new page to be loaded
|
||||
await lastTab.waitForSelector('body', { timeout: 10_000 }).catch(() => { })
|
||||
// Let website load, if it doesn't load within 5 sec. exit regardless
|
||||
await this.bot.utils.wait(5000)
|
||||
|
||||
// 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()
|
||||
const browser = page.browser()
|
||||
const tabs = await browser.pages()
|
||||
|
||||
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()) {
|
||||
if (tabs.length === 4) {
|
||||
await lastTab.close()
|
||||
}
|
||||
|
||||
} else if (lastTabURL.href !== searchListingURL.href) {
|
||||
} else if (tabs.length === 2) {
|
||||
const newPage = await browser.newPage()
|
||||
await newPage.goto(searchListingURL.href)
|
||||
|
||||
} else {
|
||||
await lastTab.goBack()
|
||||
|
||||
lastTab = await 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(searchListingURL.href)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastTab = await getLatestTab(page) // Finally update the lastTab var again
|
||||
// End of loop, refresh lastPage
|
||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
||||
lastTabURL = new URL(lastTab.url()) // Get new tab info
|
||||
i++
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private calculatePoints(counters: Counters) {
|
||||
const mobileData = counters.mobileSearch?.[0] // Mobile searches
|
||||
const genericData = counters.pcSearch?.[0] // Normal searches
|
||||
const edgeData = counters.pcSearch?.[1] // Edge searches
|
||||
|
||||
const missingPoints = (this.bot.isMobile && mobileData)
|
||||
? mobileData.pointProgressMax - mobileData.pointProgress
|
||||
: (edgeData ? edgeData.pointProgressMax - edgeData.pointProgress : 0)
|
||||
+ (genericData ? genericData.pointProgressMax - genericData.pointProgress : 0)
|
||||
|
||||
return missingPoints
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,47 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { wait } from '../../util/Utils'
|
||||
import { log } from '../../util/Logger'
|
||||
import { getQuizData } from '../../browser/BrowserFunc'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
|
||||
export class ThisOrThat extends Workers {
|
||||
|
||||
async doThisOrThat(page: Page) {
|
||||
this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
||||
|
||||
export async function doThisOrThat(page: Page) {
|
||||
log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
||||
|
||||
try {
|
||||
await page.waitForNetworkIdle({ timeout: 5000 })
|
||||
await wait(2000)
|
||||
|
||||
// Check if the quiz has been started or not
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
|
||||
if (quizNotStarted) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||
this.bot.log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||
}
|
||||
|
||||
await wait(2000)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
// Solving
|
||||
const quizData = await getQuizData(page)
|
||||
quizData // correctAnswer property is always null?
|
||||
const quizData = await this.bot.browser.func.getQuizData(page)
|
||||
const questionsRemaining = quizData.maxQuestions - (quizData.currentQuestionNumber - 1) // Amount of questions remaining
|
||||
|
||||
log('THIS-OR-THAT', 'Completed the ThisOrthat successfully')
|
||||
for (let question = 0; question < questionsRemaining; question++) {
|
||||
// Since there's no solving logic yet, randomly guess to complete
|
||||
const buttonId = `#rqAnswerOption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||
await page.click(buttonId)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
log('THIS-OR-THAT', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('THIS-OR-THAT', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,21 +1,23 @@
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
import { wait } from '../../util/Utils'
|
||||
import { log } from '../../util/Logger'
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
export async function doUrlReward(page: Page) {
|
||||
log('URL-REWARD', 'Trying to complete UrlReward')
|
||||
|
||||
export class UrlReward extends Workers {
|
||||
|
||||
async doUrlReward(page: Page) {
|
||||
this.bot.log('URL-REWARD', 'Trying to complete UrlReward')
|
||||
|
||||
try {
|
||||
// After waiting, close the page
|
||||
await page.waitForNetworkIdle({ timeout: 10_000 })
|
||||
await wait(2000)
|
||||
this.bot.utils.wait(2000)
|
||||
|
||||
await page.close()
|
||||
|
||||
log('URL-REWARD', 'Completed the UrlReward successfully')
|
||||
this.bot.log('URL-REWARD', 'Completed the UrlReward successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
log('URL-REWARD', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('URL-REWARD', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
147
src/index.ts
147
src/index.ts
@@ -1,40 +1,63 @@
|
||||
import cluster from 'cluster'
|
||||
import { Page } from 'puppeteer'
|
||||
|
||||
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 { loadAccounts } from './util/Account'
|
||||
import { chunkArray } from './util/Utils'
|
||||
import Util from './util/Utils'
|
||||
import { loadAccounts, loadConfig } from './util/Load'
|
||||
|
||||
import { login } from './functions/Login'
|
||||
import { doDailySet, doMorePromotions, doPunchCard } from './functions/Workers'
|
||||
import { doSearch } from './functions/activities/Search'
|
||||
import { Login } from './functions/Login'
|
||||
import { Workers } from './functions/Workers'
|
||||
import Activities from './functions/Activities'
|
||||
|
||||
import { Account } from './interface/Account'
|
||||
|
||||
import { runOnZeroPoints, workers, clusters } from './config.json'
|
||||
|
||||
// Main bot class
|
||||
class MicrosoftRewardsBot {
|
||||
private activeWorkers: number = clusters
|
||||
export class MicrosoftRewardsBot {
|
||||
public log: typeof log
|
||||
public config
|
||||
public utils: Util
|
||||
public activities: Activities = new Activities(this)
|
||||
public browser: {
|
||||
func: BrowserFunc,
|
||||
utils: BrowserUtil
|
||||
}
|
||||
public isMobile: boolean = false
|
||||
public homePage!: Page
|
||||
|
||||
private collectedPoints: number = 0
|
||||
private browserFactory: Browser = new Browser()
|
||||
private activeWorkers: number
|
||||
private browserFactory: Browser = new Browser(this)
|
||||
private accounts: Account[]
|
||||
private workers: Workers
|
||||
private login = new Login(this)
|
||||
|
||||
constructor() {
|
||||
this.log = log
|
||||
|
||||
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() {
|
||||
this.accounts = await loadAccounts()
|
||||
this.accounts = loadAccounts()
|
||||
}
|
||||
|
||||
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
|
||||
if (clusters > 1) {
|
||||
if (this.config.clusters > 1) {
|
||||
if (cluster.isPrimary) {
|
||||
this.runMaster()
|
||||
} else {
|
||||
@@ -48,7 +71,7 @@ class MicrosoftRewardsBot {
|
||||
private runMaster() {
|
||||
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++) {
|
||||
const worker = cluster.fork()
|
||||
@@ -71,7 +94,6 @@ class MicrosoftRewardsBot {
|
||||
|
||||
private runWorker() {
|
||||
log('MAIN-WORKER', `Worker ${process.pid} spawned`)
|
||||
|
||||
// Receive the chunk of accounts from the master
|
||||
process.on('message', async ({ chunk }) => {
|
||||
await this.runTasks(chunk)
|
||||
@@ -86,7 +108,7 @@ class MicrosoftRewardsBot {
|
||||
await this.Desktop(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
|
||||
}
|
||||
|
||||
@@ -103,8 +125,8 @@ class MicrosoftRewardsBot {
|
||||
|
||||
// Desktop
|
||||
async Desktop(account: Account) {
|
||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, false)
|
||||
const page = await browser.newPage()
|
||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
|
||||
this.homePage = await browser.newPage()
|
||||
let pages = await browser.pages()
|
||||
|
||||
// If for some reason the browser initializes with more than 2 pages, close these
|
||||
@@ -114,51 +136,53 @@ class MicrosoftRewardsBot {
|
||||
}
|
||||
|
||||
// Log into proxy
|
||||
await page.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
||||
await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
||||
|
||||
log('MAIN', 'Starting DESKTOP browser')
|
||||
|
||||
// Login into MS Rewards
|
||||
await login(page, account.email, account.password)
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const wentHome = await goHome(page)
|
||||
if (!wentHome) {
|
||||
throw log('MAIN', 'Unable to get dashboard page', 'error')
|
||||
}
|
||||
|
||||
const data = await getDashboardData(page)
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
||||
|
||||
const earnablePoints = await getEarnablePoints(data)
|
||||
const earnablePoints = await this.browser.func.getEarnablePoints()
|
||||
this.collectedPoints = earnablePoints
|
||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!runOnZeroPoints && this.collectedPoints === 0) {
|
||||
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping')
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!')
|
||||
|
||||
// Close desktop browser
|
||||
return await browser.close()
|
||||
}
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Complete daily set
|
||||
if (workers.doDailySet) {
|
||||
await doDailySet(page, data)
|
||||
if (this.config.workers.doDailySet) {
|
||||
await this.workers.doDailySet(workerPage, data)
|
||||
}
|
||||
|
||||
// Complete more promotions
|
||||
if (workers.doMorePromotions) {
|
||||
await doMorePromotions(page, data)
|
||||
if (this.config.workers.doMorePromotions) {
|
||||
await this.workers.doMorePromotions(workerPage, data)
|
||||
}
|
||||
|
||||
// Complete punch cards
|
||||
if (workers.doPunchCards) {
|
||||
await doPunchCard(page, data)
|
||||
if (this.config.workers.doPunchCards) {
|
||||
await this.workers.doPunchCard(workerPage, data)
|
||||
}
|
||||
|
||||
// Do desktop searches
|
||||
if (workers.doDesktopSearch) {
|
||||
await doSearch(page, data, false)
|
||||
if (this.config.workers.doDesktopSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
}
|
||||
|
||||
// Close desktop browser
|
||||
@@ -167,8 +191,10 @@ class MicrosoftRewardsBot {
|
||||
|
||||
// Mobile
|
||||
async Mobile(account: Account) {
|
||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, true)
|
||||
const page = await browser.newPage()
|
||||
this.isMobile = true
|
||||
|
||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
|
||||
this.homePage = await browser.newPage()
|
||||
let pages = await browser.pages()
|
||||
|
||||
// If for some reason the browser initializes with more than 2 pages, close these
|
||||
@@ -177,32 +203,51 @@ class MicrosoftRewardsBot {
|
||||
pages = await browser.pages()
|
||||
}
|
||||
// Log into proxy
|
||||
await page.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
||||
await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
||||
|
||||
log('MAIN', 'Starting MOBILE browser')
|
||||
|
||||
// Login into MS Rewards
|
||||
await login(page, account.email, account.password)
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
await goHome(page)
|
||||
|
||||
const data = await getDashboardData(page)
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
|
||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
||||
if (!data.userStatus.counters.mobileSearch) {
|
||||
log('MAIN', 'No mobile searches found, stopping')
|
||||
log('MAIN', 'No mobile searches found, stopping!')
|
||||
|
||||
// Close mobile browser
|
||||
return await browser.close()
|
||||
}
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Do mobile searches
|
||||
if (workers.doMobileSearch) {
|
||||
await doSearch(page, data, true)
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
||||
if (mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
|
||||
log('MAIN', 'Unable to complete mobile searches, bad User-Agent?, retrying...')
|
||||
|
||||
// Close mobile browser
|
||||
await browser.close()
|
||||
|
||||
// Retry
|
||||
await this.Mobile(account)
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch new points
|
||||
const earnablePoints = await getEarnablePoints(data, page)
|
||||
const earnablePoints = await this.browser.func.getEarnablePoints()
|
||||
|
||||
// If the new earnable is 0, means we got all the points, else retract
|
||||
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)
|
||||
|
||||
35
src/interface/Config.ts
Normal file
35
src/interface/Config.ts
Normal 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;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import * as fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { Account } from '../interface/Account'
|
||||
|
||||
export async function loadAccounts(): Promise<Account[]> {
|
||||
try {
|
||||
let file = 'accounts.json'
|
||||
|
||||
// If dev mode, use dev account(s)
|
||||
if (process.argv.includes('-dev')) {
|
||||
file = 'accounts.dev.json'
|
||||
}
|
||||
|
||||
const accountDir = path.join(__dirname, '../', file)
|
||||
const accounts = fs.readFileSync(accountDir, 'utf-8')
|
||||
|
||||
return JSON.parse(accounts)
|
||||
} catch (error) {
|
||||
throw new Error(error as string)
|
||||
}
|
||||
}
|
||||
51
src/util/Load.ts
Normal file
51
src/util/Load.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { Account } from '../interface/Account'
|
||||
import { Config } from '../interface/Config'
|
||||
|
||||
|
||||
export function loadAccounts(): Account[] {
|
||||
try {
|
||||
let file = 'accounts.json'
|
||||
|
||||
// If dev mode, use dev account(s)
|
||||
if (process.argv.includes('-dev')) {
|
||||
file = 'accounts.dev.json'
|
||||
}
|
||||
|
||||
const accountDir = path.join(__dirname, '../', file)
|
||||
const accounts = fs.readFileSync(accountDir, 'utf-8')
|
||||
|
||||
return JSON.parse(accounts)
|
||||
} catch (error) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSesion(sessionPath: string, email: string): Promise<string> {
|
||||
try {
|
||||
// Fetch path
|
||||
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
||||
|
||||
// Create session dir
|
||||
if (!fs.existsSync(sessionDir)) {
|
||||
await fs.promises.mkdir(sessionDir, { recursive: true })
|
||||
}
|
||||
|
||||
return sessionDir
|
||||
} catch (error) {
|
||||
throw new Error(error as string)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Webhook } from './Webhook'
|
||||
|
||||
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
|
||||
const currentTime = new Date().toISOString()
|
||||
const currentTime = new Date().toLocaleString()
|
||||
|
||||
let str = ''
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
export async function wait(ms: number): Promise<void> {
|
||||
export default class Util {
|
||||
|
||||
async wait(ms: number): Promise<void> {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
export function getFormattedDate(ms = Date.now()): string {
|
||||
getFormattedDate(ms = Date.now()): string {
|
||||
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')
|
||||
@@ -13,7 +15,7 @@ export function getFormattedDate(ms = Date.now()): string {
|
||||
return `${month}/${day}/${year}`
|
||||
}
|
||||
|
||||
export function shuffleArray<T>(array: T[]): T[] {
|
||||
shuffleArray<T>(array: T[]): T[] {
|
||||
const shuffledArray = array.slice()
|
||||
|
||||
shuffledArray.sort(() => Math.random() - 0.5)
|
||||
@@ -21,11 +23,11 @@ export function shuffleArray<T>(array: T[]): T[] {
|
||||
return shuffledArray
|
||||
}
|
||||
|
||||
export function randomNumber(min: number, max: number): number {
|
||||
randomNumber(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
export function chunkArray<T>(arr: T[], numChunks: number): T[][] {
|
||||
chunkArray<T>(arr: T[], numChunks: number): T[][] {
|
||||
const chunkSize = Math.ceil(arr.length / numChunks)
|
||||
const chunks: T[][] = []
|
||||
|
||||
@@ -36,3 +38,5 @@ export function chunkArray<T>(arr: T[], numChunks: number): T[][] {
|
||||
|
||||
return chunks
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { webhook } from '../config.json'
|
||||
import { loadConfig } from './Load'
|
||||
|
||||
|
||||
export async function Webhook(content: string) {
|
||||
const webhook = loadConfig().webhook
|
||||
|
||||
if (!webhook.enabled || webhook.url.length < 10) return
|
||||
|
||||
const request = {
|
||||
|
||||
@@ -64,7 +64,8 @@
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/accounts.json"
|
||||
"src/accounts.json",
|
||||
"src/config.json"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user