mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-11 10:56:17 +00:00
1.2.0
This commit is contained in:
@@ -15,11 +15,14 @@ Under development, however mainly for personal use!
|
|||||||
- [x] Multi-Account Support
|
- [x] Multi-Account Support
|
||||||
- [x] Session Storing
|
- [x] Session Storing
|
||||||
- [x] 2FA Support
|
- [x] 2FA Support
|
||||||
|
- [x] Headless Support
|
||||||
- [x] Discord Webhook Support
|
- [x] Discord Webhook Support
|
||||||
- [x] Desktop Searches
|
- [x] Desktop Searches
|
||||||
|
- [x] Configurable Tasks
|
||||||
- [x] Microsoft Edge Searches
|
- [x] Microsoft Edge Searches
|
||||||
- [x] Mobile Searches
|
- [x] Mobile Searches
|
||||||
- [x] Emulate scrolling and link clicking (Optional)
|
- [x] Emulated Scrolling Support
|
||||||
|
- [x] Emulated Link Clicking Support
|
||||||
- [x] Completing Daily Set
|
- [x] Completing Daily Set
|
||||||
- [x] Completing More Promotions
|
- [x] Completing More Promotions
|
||||||
- [x] Solving Quiz (10 point variant)
|
- [x] Solving Quiz (10 point variant)
|
||||||
@@ -30,7 +33,9 @@ Under development, however mainly for personal use!
|
|||||||
- [ ] Solving This Or That Quiz
|
- [ ] Solving This Or That Quiz
|
||||||
- [x] Clicking Promotional Items
|
- [x] Clicking Promotional Items
|
||||||
- [x] Solving ABC Quiz
|
- [x] Solving ABC Quiz
|
||||||
- [ ] Completing Shop And Earn
|
- [ ] Completing Shopping Game
|
||||||
|
- [ ] Completing Gaming Tab
|
||||||
|
- [x] Clustering Support
|
||||||
- [ ] Proxy Support
|
- [ ] Proxy Support
|
||||||
|
|
||||||
## Disclaimer ##
|
## Disclaimer ##
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "microsoft-rewards-script",
|
"name": "microsoft-rewards-script",
|
||||||
"version": "1.1.1",
|
"version": "1.2.0",
|
||||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -9,14 +9,17 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node ./dist/index.js",
|
"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"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Bing Rewards",
|
"Bing Rewards",
|
||||||
"Microsoft Rewards",
|
"Microsoft Rewards",
|
||||||
"Bot",
|
"Bot",
|
||||||
|
"Script",
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
"Puppeteer"
|
"Puppeteer",
|
||||||
|
"Cheerio"
|
||||||
],
|
],
|
||||||
"author": "Netsky",
|
"author": "Netsky",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class Browser {
|
|||||||
userDataDir: await loadSesion(email),
|
userDataDir: await loadSesion(email),
|
||||||
args: [
|
args: [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
|
'--mute-audio',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
`--user-agent=${userAgent.userAgent}`,
|
`--user-agent=${userAgent.userAgent}`,
|
||||||
isMobile ? '--window-size=568,1024' : ''
|
isMobile ? '--window-size=568,1024' : ''
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export async function goHome(page: Page): Promise<boolean> {
|
|||||||
// Check if account is suspended
|
// 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: 3000 }).then(() => true).catch(() => false)
|
||||||
if (isSuspended) {
|
if (isSuspended) {
|
||||||
log('GO-HOME', 'This account is suspended!')
|
log('GO-HOME', 'This account is suspended!', 'error')
|
||||||
throw new Error('Account has been suspended!')
|
throw new Error('Account has been suspended!')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"sessionPath": "sessions",
|
"sessionPath": "sessions",
|
||||||
"headless": false,
|
"headless": false,
|
||||||
"runOnZeroPoints": false,
|
"runOnZeroPoints": false,
|
||||||
|
"clusters": 1,
|
||||||
"workers": {
|
"workers": {
|
||||||
"doDailySet": true,
|
"doDailySet": true,
|
||||||
"doMorePromotions": true,
|
"doMorePromotions": true,
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ export async function login(page: Page, email: string, password: string) {
|
|||||||
try {
|
try {
|
||||||
// Navigate to the Bing login page
|
// Navigate to the Bing login page
|
||||||
await page.goto('https://login.live.com/')
|
await page.goto('https://login.live.com/')
|
||||||
|
|
||||||
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="MeePortal"]', { timeout: 5000 }).then(() => true).catch(() => false)
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
|
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 5000 }).then(() => true).catch(() => false)
|
||||||
|
if (isLocked) {
|
||||||
|
log('LOGIN', 'This account is suspended!', 'error')
|
||||||
|
throw new Error('Account has been locked!')
|
||||||
|
}
|
||||||
|
|
||||||
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
|
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
|
||||||
|
|
||||||
await execLogin(page, email, password)
|
await execLogin(page, email, password)
|
||||||
@@ -34,13 +40,15 @@ export async function login(page: Page, email: string, password: string) {
|
|||||||
log('LOGIN', 'Logged in successfully')
|
log('LOGIN', 'Logged in successfully')
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('LOGIN', 'An error occurred:' + error, 'error')
|
// Throw and don't continue
|
||||||
|
throw log('LOGIN', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function execLogin(page: Page, email: string, password: string) {
|
async function execLogin(page: Page, email: string, password: string) {
|
||||||
await page.type('#i0116', email)
|
await page.type('#i0116', email)
|
||||||
await page.click('#idSIButton9')
|
await page.click('#idSIButton9')
|
||||||
|
|
||||||
log('LOGIN', 'Email entered successfully')
|
log('LOGIN', 'Email entered successfully')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { log } from '../util/Logger'
|
|||||||
|
|
||||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||||
|
|
||||||
|
import { baseURL } from '../config.json'
|
||||||
|
|
||||||
// Daily Set
|
// Daily Set
|
||||||
export async function doDailySet(page: Page, data: DashboardData) {
|
export async function doDailySet(page: Page, data: DashboardData) {
|
||||||
@@ -55,7 +56,7 @@ export async function doPunchCard(page: Page, data: DashboardData) {
|
|||||||
page = await browser.newPage()
|
page = await browser.newPage()
|
||||||
|
|
||||||
// Got to punch card index page in a new tab
|
// Got to punch card index page in a new tab
|
||||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: 'https://rewards.bing.com/' })
|
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: baseURL })
|
||||||
|
|
||||||
await solveActivities(page, activitiesUncompleted, punchCard)
|
await solveActivities(page, activitiesUncompleted, punchCard)
|
||||||
|
|
||||||
@@ -94,24 +95,29 @@ export async function doMorePromotions(page: Page, data: DashboardData) {
|
|||||||
|
|
||||||
// Solve all the different types of activities
|
// Solve all the different types of activities
|
||||||
async function solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
async function solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
||||||
try {
|
for (const activity of activities) {
|
||||||
for (const activity of activities) {
|
try {
|
||||||
|
|
||||||
|
let selector = `[data-bi-id="${activity.offerId}"]`
|
||||||
|
|
||||||
if (punchCard) {
|
if (punchCard) {
|
||||||
const selector = await getPunchCardActivity(page, activity)
|
selector = await getPunchCardActivity(page, activity)
|
||||||
|
|
||||||
// Wait for page to load and click to load the activity in a new tab
|
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
||||||
await page.waitForSelector(selector, { timeout: 5000 })
|
|
||||||
await page.click(selector)
|
|
||||||
|
|
||||||
} else {
|
// Promotion
|
||||||
const selector = `[data-bi-id="${activity.offerId}"]`
|
if (activity.priority === 1) {
|
||||||
|
selector = '#promo-item'
|
||||||
// Wait for page to load and click to load the activity in a new tab
|
} else {
|
||||||
await page.waitForSelector(selector, { timeout: 5000 })
|
selector = `[data-bi-id="${activity.name}"]`
|
||||||
await page.click(selector)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for element to load
|
||||||
|
await page.waitForSelector(selector, { timeout: 5000 })
|
||||||
|
// Click element, it will be opened in a new tab
|
||||||
|
await page.click(selector)
|
||||||
|
|
||||||
// Select the new activity page
|
// Select the new activity page
|
||||||
const activityPage = await getLatestTab(page)
|
const activityPage = await getLatestTab(page)
|
||||||
|
|
||||||
@@ -134,7 +140,7 @@ async function solveActivities(page: Page, activities: PromotionalItem[] | MoreP
|
|||||||
// This Or That Quiz (Usually 50 points)
|
// This Or That Quiz (Usually 50 points)
|
||||||
case 50:
|
case 50:
|
||||||
log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
|
log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
|
||||||
await doThisOrThat(activityPage, activity)
|
await doThisOrThat(activityPage)
|
||||||
break
|
break
|
||||||
|
|
||||||
// Quizzes are usually 30-40 points
|
// Quizzes are usually 30-40 points
|
||||||
@@ -151,13 +157,16 @@ async function solveActivities(page: Page, activities: PromotionalItem[] | MoreP
|
|||||||
await doUrlReward(activityPage)
|
await doUrlReward(activityPage)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
// Misc
|
||||||
default:
|
default:
|
||||||
|
log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
|
||||||
|
await doUrlReward(activityPage)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
await wait(1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
await wait(1500)
|
||||||
log('ACTIVITY', 'An error occurred:' + error, 'error')
|
} catch (error) {
|
||||||
|
log('ACTIVITY', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
|||||||
// Go to bing
|
// Go to bing
|
||||||
await searchPage.goto('https://bing.com')
|
await searchPage.goto('https://bing.com')
|
||||||
|
|
||||||
let maxLoop = 0 // If the loop hits 20 this when not gaining any points, we're assuming it's stuck.
|
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[] = []
|
const queries: string[] = []
|
||||||
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related))
|
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related))
|
||||||
@@ -80,8 +80,9 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxLoop > 20) {
|
// If we didn't gain points for 10 iterations, assume it's stuck
|
||||||
log('SEARCH-BING', 'Search didn\'t gain point for 20 iterations aborting searches', 'warn')
|
if (maxLoop > 10) {
|
||||||
|
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
|
maxLoop = 0 // Reset to 0 so we can retry with related searches below
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -125,10 +126,10 @@ export async function doSearch(page: Page, data: DashboardData, mobile: boolean)
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try 5 more times
|
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
|
||||||
if (maxLoop > 5) {
|
if (maxLoop > 5) {
|
||||||
log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
|
log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +144,7 @@ async function bingSearch(page: Page, searchPage: Page, query: string) {
|
|||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
try {
|
try {
|
||||||
const searchBar = '#sb_form_q'
|
const searchBar = '#sb_form_q'
|
||||||
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 3000 })
|
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
|
||||||
await searchPage.click(searchBar) // Focus on the textarea
|
await searchPage.click(searchBar) // Focus on the textarea
|
||||||
await wait(500)
|
await wait(500)
|
||||||
await searchPage.keyboard.down('Control')
|
await searchPage.keyboard.down('Control')
|
||||||
@@ -252,7 +253,7 @@ function formatDate(date: Date): string {
|
|||||||
async function randomScroll(page: Page) {
|
async function randomScroll(page: Page) {
|
||||||
try {
|
try {
|
||||||
// Press the arrow down key to scroll
|
// Press the arrow down key to scroll
|
||||||
for (let i = 0; i < randomNumber(5, 50); i++) {
|
for (let i = 0; i < randomNumber(5, 100); i++) {
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -262,7 +263,7 @@ async function randomScroll(page: Page) {
|
|||||||
|
|
||||||
async function clickRandomLink(page: Page) {
|
async function clickRandomLink(page: Page) {
|
||||||
try {
|
try {
|
||||||
const searchListingURL = new URL(page.url()) // Get page info before clicking
|
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
|
await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not
|
||||||
|
|
||||||
@@ -272,15 +273,16 @@ async function clickRandomLink(page: Page) {
|
|||||||
// Will get current tab if no new one is created
|
// Will get current tab if no new one is created
|
||||||
let lastTab = await getLatestTab(page)
|
let lastTab = await getLatestTab(page)
|
||||||
|
|
||||||
// Wait for website to finish loading, don't break loop however
|
// Wait for the body of the new page to be loaded
|
||||||
await lastTab.waitForNetworkIdle({ idleTime: 1000, timeout: 5000 }).catch(() => { })
|
await lastTab.waitForSelector('body', { timeout: 10_000 }).catch(() => { })
|
||||||
|
|
||||||
// Check if the tab is closed or not
|
// Check if the tab is closed or not
|
||||||
if (!lastTab.isClosed()) {
|
if (!lastTab.isClosed()) {
|
||||||
let lastTabURL = new URL(lastTab.url()) // Get new tab info
|
let lastTabURL = new URL(lastTab.url()) // Get new tab info
|
||||||
|
|
||||||
// Check if the URL is different from the original one
|
// Check if the URL is different from the original one, don't loop more than 5 times.
|
||||||
while (lastTabURL.href !== searchListingURL.href) {
|
let i = 0
|
||||||
|
while (lastTabURL.href !== searchListingURL.href && i < 5) {
|
||||||
|
|
||||||
// If hostname is still bing, (Bing images/news etc)
|
// If hostname is still bing, (Bing images/news etc)
|
||||||
if (lastTabURL.hostname == searchListingURL.hostname) {
|
if (lastTabURL.hostname == searchListingURL.hostname) {
|
||||||
@@ -294,7 +296,6 @@ async function clickRandomLink(page: Page) {
|
|||||||
await lastTab.goto(searchListingURL.href)
|
await lastTab.goto(searchListingURL.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
|
||||||
} else { // No longer on bing, likely opened a new tab, close this tab
|
} else { // No longer on bing, likely opened a new tab, close this tab
|
||||||
lastTab = await getLatestTab(page) // Get last opened tab
|
lastTab = await getLatestTab(page) // Get last opened tab
|
||||||
lastTabURL = new URL(lastTab.url())
|
lastTabURL = new URL(lastTab.url())
|
||||||
@@ -303,8 +304,13 @@ async function clickRandomLink(page: Page) {
|
|||||||
|
|
||||||
// If the browser has more than 3 tabs open, it has opened a new one, we need to close this one.
|
// 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) {
|
if (tabs.length > 3) {
|
||||||
await lastTab.close()
|
// Make sure the page is still open!
|
||||||
} else {
|
if (!lastTab.isClosed()) {
|
||||||
|
await lastTab.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (lastTabURL.href !== searchListingURL.href) {
|
||||||
|
|
||||||
await lastTab.goBack()
|
await lastTab.goBack()
|
||||||
|
|
||||||
lastTab = await getLatestTab(page) // Get last opened tab
|
lastTab = await getLatestTab(page) // Get last opened tab
|
||||||
@@ -315,10 +321,11 @@ async function clickRandomLink(page: Page) {
|
|||||||
await lastTab.goto(searchListingURL.href)
|
await lastTab.goto(searchListingURL.href)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTab = await getLatestTab(page) // Finally update the lastTab var again
|
||||||
|
i++
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'puppeteer'
|
||||||
|
|
||||||
import { getLatestTab } from '../../browser/BrowserUtil'
|
|
||||||
import { wait } from '../../util/Utils'
|
import { wait } from '../../util/Utils'
|
||||||
import { log } from '../../util/Logger'
|
import { log } from '../../util/Logger'
|
||||||
|
|
||||||
import { PromotionalItem, MorePromotion } from '../../interface/DashboardData'
|
export async function doThisOrThat(page: Page) {
|
||||||
|
|
||||||
export async function doThisOrThat(page: Page, data: PromotionalItem | MorePromotion) {
|
|
||||||
return // Todo
|
return // Todo
|
||||||
log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const selector = `[data-bi-id="${data.offerId}"]`
|
await page.waitForNetworkIdle({ timeout: 5000 })
|
||||||
|
|
||||||
// Wait for page to load and click to load the this or that quiz in a new tab
|
|
||||||
await page.waitForSelector(selector, { timeout: 5000 })
|
|
||||||
await page.click(selector)
|
|
||||||
|
|
||||||
const thisorthatPage = await getLatestTab(page)
|
|
||||||
await thisorthatPage.waitForNetworkIdle({ timeout: 5000 })
|
|
||||||
await wait(2000)
|
await wait(2000)
|
||||||
|
|
||||||
// Check if the quiz has been started or not
|
// Check if the quiz has been started or not
|
||||||
const quizNotStarted = await thisorthatPage.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
||||||
if (quizNotStarted) {
|
if (quizNotStarted) {
|
||||||
await thisorthatPage.click('#rqStartQuiz')
|
await page.click('#rqStartQuiz')
|
||||||
} else {
|
} else {
|
||||||
log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||||
}
|
}
|
||||||
|
|||||||
81
src/index.ts
81
src/index.ts
@@ -1,7 +1,10 @@
|
|||||||
|
import cluster from 'cluster'
|
||||||
|
|
||||||
import Browser from './browser/Browser'
|
import Browser from './browser/Browser'
|
||||||
import { getDashboardData, getEarnablePoints, goHome } from './browser/BrowserFunc'
|
import { getDashboardData, getEarnablePoints, goHome } from './browser/BrowserFunc'
|
||||||
import { log } from './util/Logger'
|
import { log } from './util/Logger'
|
||||||
import { loadAccounts } from './util/Account'
|
import { loadAccounts } from './util/Account'
|
||||||
|
import { chunkArray } from './util/Utils'
|
||||||
|
|
||||||
import { login } from './functions/Login'
|
import { login } from './functions/Login'
|
||||||
import { doDailySet, doMorePromotions, doPunchCard } from './functions/Workers'
|
import { doDailySet, doMorePromotions, doPunchCard } from './functions/Workers'
|
||||||
@@ -9,20 +12,74 @@ import { doSearch } from './functions/activities/Search'
|
|||||||
|
|
||||||
import { Account } from './interface/Account'
|
import { Account } from './interface/Account'
|
||||||
|
|
||||||
import { runOnZeroPoints, workers } from './config.json'
|
import { runOnZeroPoints, workers, clusters } from './config.json'
|
||||||
|
|
||||||
// Main bot class
|
// Main bot class
|
||||||
class MicrosoftRewardsBot {
|
class MicrosoftRewardsBot {
|
||||||
|
private activeWorkers: number = clusters
|
||||||
private collectedPoints: number = 0
|
private collectedPoints: number = 0
|
||||||
private browserFactory: Browser = new Browser()
|
private browserFactory: Browser = new Browser()
|
||||||
|
private accounts: Account[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.accounts = []
|
||||||
|
}
|
||||||
|
|
||||||
|
async initialize() {
|
||||||
|
this.accounts = await loadAccounts()
|
||||||
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
log('MAIN', 'Bot started')
|
log('MAIN', `Bot started with ${clusters} clusters`)
|
||||||
|
|
||||||
const accounts = await loadAccounts()
|
// Only cluster when there's more than 1 cluster demanded
|
||||||
|
if (clusters > 1) {
|
||||||
|
if (cluster.isPrimary) {
|
||||||
|
this.runMaster()
|
||||||
|
} else {
|
||||||
|
this.runWorker()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.runTasks(this.accounts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private runMaster() {
|
||||||
|
log('MAIN-PRIMARY', 'Primary process started')
|
||||||
|
|
||||||
|
const accountChunks = chunkArray(this.accounts, clusters)
|
||||||
|
|
||||||
|
for (let i = 0; i < accountChunks.length; i++) {
|
||||||
|
const worker = cluster.fork()
|
||||||
|
const chunk = accountChunks[i]
|
||||||
|
worker.send({ chunk })
|
||||||
|
}
|
||||||
|
|
||||||
|
cluster.on('exit', (worker, code) => {
|
||||||
|
this.activeWorkers -= 1
|
||||||
|
|
||||||
|
log('MAIN-WORKER', `Worker ${worker.process.pid} destroyed | Code: ${code} | Active workers: ${this.activeWorkers}`, 'warn')
|
||||||
|
|
||||||
|
// Check if all workers have exited
|
||||||
|
if (this.activeWorkers === 0) {
|
||||||
|
log('MAIN-WORKER', 'All workers destroyed. Exiting main process!', 'warn')
|
||||||
|
process.exit(0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runTasks(accounts: Account[]) {
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
log('MAIN', `Started tasks for account ${account.email}`)
|
log('MAIN-WORKER', `Started tasks for account ${account.email}`)
|
||||||
|
|
||||||
// Desktop Searches, DailySet and More Promotions
|
// Desktop Searches, DailySet and More Promotions
|
||||||
await this.Desktop(account)
|
await this.Desktop(account)
|
||||||
@@ -35,13 +92,11 @@ class MicrosoftRewardsBot {
|
|||||||
// Mobile Searches
|
// Mobile Searches
|
||||||
await this.Mobile(account)
|
await this.Mobile(account)
|
||||||
|
|
||||||
log('MAIN', `Completed tasks for account ${account.email}`)
|
log('MAIN-WORKER', `Completed tasks for account ${account.email}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log('MAIN-PRIMARY', 'Completed tasks for ALL accounts')
|
||||||
// Clean exit
|
log('MAIN-PRIMARY', 'All workers destroyed!')
|
||||||
log('MAIN', 'Completed tasks for ALL accounts')
|
|
||||||
log('MAIN', 'Bot exited')
|
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +154,7 @@ class MicrosoftRewardsBot {
|
|||||||
await browser.close()
|
await browser.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mobile
|
||||||
async Mobile(account: Account) {
|
async Mobile(account: Account) {
|
||||||
const browser = await this.browserFactory.createBrowser(account.email, true)
|
const browser = await this.browserFactory.createBrowser(account.email, true)
|
||||||
const page = await browser.newPage()
|
const page = await browser.newPage()
|
||||||
@@ -137,4 +193,9 @@ class MicrosoftRewardsBot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new MicrosoftRewardsBot().run()
|
const bot = new MicrosoftRewardsBot()
|
||||||
|
|
||||||
|
// Initialize accounts first and then start the bot
|
||||||
|
bot.initialize().then(() => {
|
||||||
|
bot.run()
|
||||||
|
})
|
||||||
@@ -7,17 +7,17 @@ export function log(title: string, message: string, type?: 'log' | 'warn' | 'err
|
|||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'warn':
|
case 'warn':
|
||||||
str = `[${currentTime}] [WARN] [${title}] ${message}`
|
str = `[${currentTime}] [PID: ${process.pid}] [WARN] [${title}] ${message}`
|
||||||
console.warn(str)
|
console.warn(str)
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
str = `[${currentTime}] [ERROR] [${title}] ${message}`
|
str = `[${currentTime}] [PID: ${process.pid}] [ERROR] [${title}] ${message}`
|
||||||
console.error(str)
|
console.error(str)
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
str = `[${currentTime}] [LOG] [${title}] ${message}`
|
str = `[${currentTime}] [PID: ${process.pid}] [LOG] [${title}] ${message}`
|
||||||
console.log(str)
|
console.log(str)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ export function getFormattedDate(ms = Date.now()) {
|
|||||||
return `${month}/${day}/${year}`
|
return `${month}/${day}/${year}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export function shuffleArray<T>(array: T[]): T[] {
|
||||||
export function shuffleArray(array: any[]): any[] {
|
|
||||||
const shuffledArray = array.slice()
|
const shuffledArray = array.slice()
|
||||||
|
|
||||||
shuffledArray.sort(() => Math.random() - 0.5)
|
shuffledArray.sort(() => Math.random() - 0.5)
|
||||||
@@ -24,4 +23,16 @@ export function shuffleArray(array: any[]): any[] {
|
|||||||
|
|
||||||
export function randomNumber(min: number, max: number) {
|
export function randomNumber(min: number, max: number) {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
export function chunkArray<T>(arr: T[], numChunks: number): T[][] {
|
||||||
|
const chunkSize = Math.ceil(arr.length / numChunks)
|
||||||
|
const chunks: T[][] = []
|
||||||
|
|
||||||
|
for (let i = 0; i < arr.length; i += chunkSize) {
|
||||||
|
const chunk = arr.slice(i, i + chunkSize)
|
||||||
|
chunks.push(chunk)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user