9 Commits

Author SHA1 Message Date
TheNetsky
15b2b827eb 1.4.1
- Fixed typos
- Added Dockerfile (experimental)
- Increased scroll amount
2024-01-14 14:09:57 +01:00
Netsky
02518ee4ba Update README.md 2024-01-07 17:00:23 +01:00
Netsky
69819b5631 Add config explaination 2024-01-07 16:59:15 +01:00
Netsky
b389b87792 Set runOnZeroPoints to false by default 2024-01-07 16:38:47 +01:00
TheNetsky
9ea7f5c452 1.4.0
- Switched from Puppeteer to Playwright
- Fixed mobile searches not working
- Added fingerprint saving in config
- Added mobile search retry in config
2024-01-07 16:33:48 +01:00
TheNetsky
f3fb641ecd 1.3.2
- Better generation of mobile user agents
2023-12-27 11:38:52 +01:00
TheNetsky
bca1e7c896 1.3.1
- Poll clicking will be attempted regardless of the selector being found
- Fix tabs sticking upon search error
- Updated dependencies
2023-12-20 13:03:08 +01:00
Netsky
fd7c8e36d4 Update README.md 2023-12-01 12:12:26 +01:00
Netsky
dcb0c25d46 Remove the feature of Promo Items 2023-12-01 12:02:17 +01:00
20 changed files with 274 additions and 123 deletions

42
Dockerfile Normal file
View File

@@ -0,0 +1,42 @@
# Use an official Node.js runtime as a base image
FROM node:18
# Set the working directory in the container
WORKDIR /usr/src/microsoft-rewards-script
# Install jq
RUN apt-get update && apt-get install -y jq
# Copy all files to the working directory
COPY . .
# Check if "headless" is set to "true" in the config.json file
RUN HEADLESS=$(cat src/config.json | jq -r .headless) \
&& if [ "$HEADLESS" != "true" ]; then \
echo "Error: 'headless' in src/config.json is not true."; \
exit 1; \
fi
# Install dependencies including Playwright
RUN apt-get install -y \
xvfb \
libgbm-dev \
libnss3 \
libasound2 \
libxss1 \
libatk-bridge2.0-0 \
libgtk-3-0 \
&& rm -rf /var/lib/apt/lists/*
# Install application dependencies
RUN npm install
# Build the script
RUN npm run build
# Install playwright chromium
RUN npx playwright install chromium
# Define the command to run your application
CMD ["npm", "start"]

View File

@@ -1,5 +1,5 @@
# Microsoft-Rewards-Script
Automated Microsoft Rewards script, however this time using TypeScript, Cheerio and Puppeteer.
Automated Microsoft Rewards script, however this time using TypeScript, Cheerio and Playwright.
Under development, however mainly for personal use!
@@ -12,9 +12,31 @@ Under development, however mainly for personal use!
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` script. WINDOWS ONLY
- 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.
- Docker container has to be recreated for any changes regardings the `config.json` and/or `accounts.json`.
## Config ##
| Setting | Description | Default |
| :------------- |:-------------| :-----|
| baseURL | MS Rewards page | `https://rewards.bing.com` |
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In src/browser/sessions) |
| headless | If the browser window should be visable be ran in the background | `false` (Browser is visable) |
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) |
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) |
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
| workers.doDailySet | Complete daily set items | `true` |
| workers.doMorePromotions | Complete promotional items | `true` |
| workers.doPunchCards | Complete punchcards | `true` |
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
| workers.doMobileSearch | Complete daily mobile searches | `true` |
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
| scrollRandomResults | Scroll randomly in search results | `true` |
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) |
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` |
| webhook.enabled | Enable or disable your set webhook | `false` |
| webhook.url | Your Discord webhook URL | `null` |
## Features ##
- [x] Multi-Account Support
@@ -37,7 +59,6 @@ Under development, however mainly for personal use!
- [x] Completing Polls
- [x] Completing Punchcards
- [x] Solving This Or That Quiz (Random)
- [x] Clicking Promotional Items
- [x] Solving ABC Quiz
- [ ] Completing Shopping Game
- [ ] Completing Gaming Tab

View File

@@ -1,6 +1,6 @@
{
"name": "microsoft-rewards-script",
"version": "1.3.0",
"version": "1.4.1",
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
"main": "index.js",
"engines": {
@@ -11,7 +11,8 @@
"start": "node ./dist/index.js",
"ts-start": "ts-node ./src/index.ts",
"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 }\""
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"",
"create-docker" : "docker build -t microsoft-rewards-script-docker ."
},
"keywords": [
"Bing Rewards",
@@ -19,7 +20,7 @@
"Bot",
"Script",
"TypeScript",
"Puppeteer",
"Playwright",
"Cheerio"
],
"author": "Netsky",
@@ -28,14 +29,14 @@
"@typescript-eslint/eslint-plugin": "^6.7.0",
"eslint": "^8.54.0",
"eslint-plugin-modules-newline": "^0.0.6",
"typescript": "^5.2.2"
"typescript": "^5.3.3"
},
"dependencies": {
"axios": "^1.6.2",
"cheerio": "^1.0.0-rc.12",
"fingerprint-generator": "^2.1.45",
"fingerprint-injector": "^2.1.45",
"puppeteer": "^21.5.2",
"ts-node": "^10.9.1"
"fingerprint-generator": "^2.1.46",
"fingerprint-injector": "^2.1.46",
"playwright": "^1.40.1",
"ts-node": "^10.9.2"
}
}

View File

@@ -1,9 +1,11 @@
import puppeteer from 'puppeteer'
import { FingerprintInjector } from 'fingerprint-injector'
import playwright from 'playwright'
import { BrowserContext } from 'playwright'
import { newInjectedContext } from 'fingerprint-injector'
import { FingerprintGenerator } from 'fingerprint-generator'
import { MicrosoftRewardsBot } from '../index'
import { loadSesion } from '../util/Load'
import { loadSessionData, saveFingerprintData } from '../util/Load'
import { AccountProxy } from '../interface/Account'
@@ -13,7 +15,6 @@ https://botcheck.luminati.io/
http://f.vision/
*/
class Browser {
private bot: MicrosoftRewardsBot
@@ -21,41 +22,47 @@ class Browser {
this.bot = bot
}
async createBrowser(email: string, proxy: AccountProxy) {
// const userAgent = await getUserAgent(isMobile)
const browser = await puppeteer.launch({
headless: this.bot.config.headless ? 'new' : false,
userDataDir: await loadSesion(this.bot.config.sessionPath, email),
async createBrowser(proxy: AccountProxy, email: string): Promise<BrowserContext> {
const browser = await playwright.chromium.launch({
//channel: 'msedge', // Uses Edge instead of chrome
headless: this.bot.config.headless,
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
args: [
'--no-sandbox',
'--mute-audio',
'--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors',
proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : ''
'--ignore-ssl-errors'
]
})
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
browsers: ['edge'],
browserListQuery: 'last 2 Edge versions'
})
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)
// Modify the newPage function to attach the fingerprint
const originalNewPage = browser.newPage
browser.newPage = async function () {
const page = await originalNewPage.apply(browser)
await new FingerprintInjector().attachFingerprintToPuppeteer(page, { fingerprint, headers })
return page
const fingerpint = sessionData.fingerprint ? sessionData.fingerprint : this.generateFingerprint()
const context = await newInjectedContext(browser, { fingerprint: fingerpint })
await context.addCookies(sessionData.cookies)
if (this.bot.config.saveFingerprint) {
await saveFingerprintData(this.bot.config.sessionPath, email, this.bot.isMobile, fingerpint)
}
return browser
this.bot.log('BROWSER', `Created browser with User-Agent: "${fingerpint.fingerprint.navigator.userAgent}"`)
return context
}
generateFingerprint() {
const fingerPrintData = new FingerprintGenerator().getFingerprint({
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
browsers: ['edge']
})
return fingerPrintData
}
}
export default Browser
export default Browser

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { CheerioAPI, load } from 'cheerio'
import { MicrosoftRewardsBot } from '../index'
@@ -17,7 +17,7 @@ export default class BrowserFunc {
/**
* Navigate the provided page to rewards homepage
* @param {Page} page Puppeteer page
* @param {Page} page Playwright page
*/
async goHome(page: Page) {
@@ -37,7 +37,7 @@ export default class BrowserFunc {
await this.bot.browser.utils.tryDismissCookieBanner(page)
// Check if account is suspended
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
if (isSuspended) {
this.bot.log('GO-HOME', 'This account is suspended!', 'error')
throw new Error('Account has been suspended!')
@@ -89,7 +89,7 @@ export default class BrowserFunc {
}
// Reload the page to get new data
await this.bot.homePage.reload({ waitUntil: 'networkidle2' })
await this.bot.homePage.reload({ waitUntil: 'domcontentloaded' })
const scriptContent = await this.bot.homePage.evaluate(() => {
const scripts = Array.from(document.querySelectorAll('script'))
@@ -180,7 +180,7 @@ export default class BrowserFunc {
/**
* Parse quiz data from provided page
* @param {Page} page Puppeteer page
* @param {Page} page Playwright page
* @returns {QuizData} Quiz data object
*/
async getQuizData(page: Page): Promise<QuizData> {
@@ -214,7 +214,7 @@ export default class BrowserFunc {
async waitForQuizRefresh(page: Page): Promise<boolean> {
try {
await page.waitForSelector('span.rqMCredits', { visible: true, timeout: 10_000 })
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 })
await this.bot.utils.wait(2000)
return true
@@ -226,7 +226,7 @@ export default class BrowserFunc {
async checkQuizCompleted(page: Page): Promise<boolean> {
try {
await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 2000 })
await page.waitForSelector('#quizCompleteContainer', { state: 'visible', timeout: 2000 })
await this.bot.utils.wait(2000)
return true

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { MicrosoftRewardsBot } from '../index'
@@ -24,7 +24,7 @@ export default class BrowserUtil {
for (const button of buttons) {
try {
const element = await page.waitForSelector(button.selector, { visible: true, timeout: 1000 })
const element = await page.waitForSelector(button.selector, { state: 'visible', timeout: 1000 })
if (element) {
await element.click()
result = true
@@ -73,8 +73,8 @@ export default class BrowserUtil {
try {
await this.bot.utils.wait(500)
const browser = page.browser()
const pages = await browser.pages()
const browser = page.context()
const pages = browser.pages()
const newTab = pages[pages.length - 1]
if (newTab) {
@@ -89,8 +89,8 @@ export default class BrowserUtil {
async getTabs(page: Page) {
try {
const browser = page.browser()
const pages = await browser.pages()
const browser = page.context()
const pages = browser.pages()
const homeTab = pages[1]
let homeTabURL: URL

View File

@@ -4,6 +4,7 @@
"headless": false,
"runOnZeroPoints": false,
"clusters": 1,
"saveFingerprint": false,
"workers": {
"doDailySet": true,
"doMorePromotions": true,
@@ -18,10 +19,11 @@
"searchDelay": {
"min": 10000,
"max": 20000
}
},
"retryMobileSearch": true
},
"webhook": {
"enabled": false,
"url": ""
}
}
}

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { MicrosoftRewardsBot } from '../index'

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import readline from 'readline'
import { MicrosoftRewardsBot } from '../index'
@@ -26,13 +26,13 @@ export class Login {
if (!isLoggedIn) {
// Check if account is locked
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false)
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { state: 'visible', timeout: 10_000 }).then(() => true).catch(() => false)
if (isLocked) {
this.bot.log('LOGIN', 'This account has been locked!', 'error')
throw new Error('Account has been locked!')
}
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
await page.waitForSelector('#loginHeader', { state: 'visible', timeout: 10_000 })
await this.execLogin(page, email, password)
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
@@ -59,7 +59,7 @@ export class Login {
this.bot.log('LOGIN', 'Email entered successfully')
try {
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
await this.bot.utils.wait(2000)
await page.type('#i0118', password)

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
@@ -58,13 +58,13 @@ export class Workers {
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
// Wait for new page to load, max 10 seconds, however try regardless in case of error
await page.waitForNetworkIdle({ timeout: 5_000 }).catch(() => { })
await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => { })
await this.solveActivities(page, activitiesUncompleted, punchCard)
page = await this.bot.browser.utils.getLatestTab(page)
const pages = await (page.browser()).pages()
const pages = page.context().pages()
if (pages.length > 3) {
await page.close()
@@ -95,7 +95,7 @@ export class Workers {
}
// Solve Activities
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" items')
page = await this.bot.browser.utils.getLatestTab(page)
@@ -118,7 +118,7 @@ export class Workers {
// Reselect the worker page
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
const pages = await activityPage.browser().pages()
const pages = activityPage.context().pages()
if (pages.length > 3) {
await activityPage.close()
@@ -152,7 +152,7 @@ export class Workers {
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 activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
await this.bot.utils.wait(5000)
switch (activity.promotionType) {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -15,18 +15,18 @@ export class ABC extends Workers {
const maxIterations = 15
let i
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 10_000 })
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10_000 })
const answers = $('.wk_OptionClickClass')
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 10_000 })
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10_000 })
await this.bot.utils.wait(2000)
await page.click(`#${answer}`) // Click answer
await this.bot.utils.wait(4000)
await page.waitForSelector('div.wk_button', { visible: true, timeout: 10_000 })
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10_000 })
await page.click('div.wk_button') // Click next question button
page = await this.bot.browser.utils.getLatestTab(page)

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -11,7 +11,7 @@ export class Poll extends Workers {
try {
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 })
await page.waitForSelector(buttonId, { state: 'visible', timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
await this.bot.utils.wait(2000)
await page.click(buttonId)

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -10,7 +10,7 @@ export class Quiz extends Workers {
try {
// Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {
@@ -29,7 +29,7 @@ export class Quiz extends Workers {
const answers: string[] = []
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
@@ -39,7 +39,7 @@ export class Quiz extends Workers {
// Click the answers
for (const answer of answers) {
await page.waitForSelector(answer, { visible: true, timeout: 2000 })
await page.waitForSelector(answer, { state: 'visible', timeout: 2000 })
// Click the answer on page
await page.click(answer)
@@ -59,7 +59,7 @@ export class Quiz extends Workers {
for (let i = 0; i < quizData.numberOfOptions; i++) {
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
if (dataOption === correctOption) {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import axios from 'axios'
import { Workers } from '../Workers'
@@ -63,6 +63,12 @@ export class Search extends Workers {
break
}
// Only for mobile searches
if (maxLoop > 3 && this.bot.isMobile) {
this.bot.log('SEARCH-BING-MOBILE', 'Search didn\'t gain point for 3 iterations, likely bad User-Agent', 'warn')
break
}
// If we didn't gain points for 10 iterations, assume it's stuck
if (maxLoop > 10) {
this.bot.log('SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn')
@@ -71,6 +77,11 @@ export class Search extends Workers {
}
}
// Only for mobile searches
if (missingPoints > 0 && this.bot.isMobile) {
return
}
// If we still got remaining search queries, generate extra ones
if (missingPoints > 0) {
this.bot.log('SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`)
@@ -121,7 +132,7 @@ export class Search extends Workers {
for (let i = 0; i < 5; i++) {
try {
const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
await searchPage.waitForSelector(searchBar, { state: 'attached', timeout: 10_000 })
await searchPage.click(searchBar) // Focus on the textarea
await this.bot.utils.wait(500)
await searchPage.keyboard.down('Control')
@@ -154,6 +165,10 @@ export class Search extends Workers {
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
// Reset the tabs
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
await this.closeTabs(lastTab, this.searchPageURL)
await this.bot.utils.wait(4000)
}
}
@@ -234,7 +249,7 @@ export class Search extends Workers {
private async randomScroll(page: Page) {
try {
// Press the arrow down key to scroll
for (let i = 0; i < this.bot.utils.randomNumber(5, 400); i++) {
for (let i = 0; i < this.bot.utils.randomNumber(5, 600); i++) {
await page.keyboard.press('ArrowDown')
}
@@ -260,19 +275,8 @@ export class Search extends Workers {
// 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) {
const browser = page.browser()
const tabs = await browser.pages()
if (tabs.length === 4) {
await lastTab.close()
} else if (tabs.length === 2) {
const newPage = await browser.newPage()
await newPage.goto(searchListingURL.href)
} else {
await lastTab.goBack()
}
await this.closeTabs(lastTab, searchListingURL.href)
// End of loop, refresh lastPage
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
@@ -285,6 +289,26 @@ export class Search extends Workers {
}
}
private async closeTabs(lastTab: Page, url: string) {
const browser = lastTab.context()
const tabs = browser.pages()
// If more than 3 tabs are open, close the last tab
if (tabs.length > 2) {
await lastTab.close()
// If only 1 tab is open, open a new one to search in
} else if (tabs.length === 1) {
const newPage = await browser.newPage()
await newPage.goto(url)
// Else go back one page
} else {
await lastTab.goBack()
}
}
private calculatePoints(counters: Counters) {
const mobileData = counters.mobileSearch?.[0] // Mobile searches
const genericData = counters.pcSearch?.[0] // Normal searches

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'
@@ -11,7 +11,7 @@ export class ThisOrThat extends Workers {
try {
// Check if the quiz has been started or not
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 2000 }).then(() => true).catch(() => false)
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
if (quizNotStarted) {
await page.click('#rqStartQuiz')
} else {

View File

@@ -1,4 +1,4 @@
import { Page } from 'puppeteer'
import { Page } from 'playwright'
import { Workers } from '../Workers'

View File

@@ -1,5 +1,5 @@
import cluster from 'cluster'
import { Page } from 'puppeteer'
import { BrowserContext, Page } from 'playwright'
import Browser from './browser/Browser'
import BrowserFunc from './browser/BrowserFunc'
@@ -7,7 +7,7 @@ import BrowserUtil from './browser/BrowserUtil'
import { log } from './util/Logger'
import Util from './util/Utils'
import { loadAccounts, loadConfig } from './util/Load'
import { loadAccounts, loadConfig, saveSessionData } from './util/Load'
import { Login } from './functions/Login'
import { Workers } from './functions/Workers'
@@ -125,18 +125,8 @@ export class MicrosoftRewardsBot {
// Desktop
async Desktop(account: Account) {
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()
let pages = await browser.pages()
// If for some reason the browser initializes with more than 2 pages, close these
while (pages.length > 2) {
await pages[0]?.close()
pages = await browser.pages()
}
// Log into proxy
await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
log('MAIN', 'Starting DESKTOP browser')
@@ -156,7 +146,7 @@ export class MicrosoftRewardsBot {
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!')
// Close desktop browser
return await browser.close()
return await this.closeBrowser(browser, account.email)
}
// Open a new tab to where the tasks are going to be completed
@@ -185,25 +175,20 @@ export class MicrosoftRewardsBot {
await this.activities.doSearch(workerPage, data)
}
// Save cookies
const cookies = await browser.cookies()
await saveSessionData(this.config.sessionPath, account.email, this.isMobile, cookies)
// Close desktop browser
await browser.close()
return await this.closeBrowser(browser, account.email)
}
// Mobile
async Mobile(account: Account) {
this.isMobile = true
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()
let pages = await browser.pages()
// If for some reason the browser initializes with more than 2 pages, close these
while (pages.length > 2) {
await pages[0]?.close()
pages = await browser.pages()
}
// Log into proxy
await this.homePage.authenticate({ username: account.proxy.username, password: account.proxy.password })
log('MAIN', 'Starting MOBILE browser')
@@ -218,7 +203,7 @@ export class MicrosoftRewardsBot {
log('MAIN', 'No mobile searches found, stopping!')
// Close mobile browser
return await browser.close()
return await this.closeBrowser(browser, account.email)
}
// Open a new tab to where the tasks are going to be completed
@@ -235,11 +220,12 @@ export class MicrosoftRewardsBot {
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...')
// Retry until all points are gathered when (retryMobileSearch is enabled)
if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...')
// Close mobile browser
await browser.close()
await this.closeBrowser(browser, account.email)
// Retry
await this.Mobile(account)
@@ -254,10 +240,21 @@ export class MicrosoftRewardsBot {
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
// Close mobile browser
return await this.closeBrowser(browser, account.email)
}
private async closeBrowser(browser: BrowserContext, email: string) {
// Save cookies
const cookies = await browser.cookies()
await saveSessionData(this.config.sessionPath, email, this.isMobile, cookies)
// Close browser
await browser.close()
}
}
const bot = new MicrosoftRewardsBot()
// Initialize accounts first and then start the bot

View File

@@ -7,6 +7,7 @@ export interface Config {
workers: Workers;
searchSettings: SearchSettings;
webhook: Webhook;
saveFingerprint: boolean;
}
export interface SearchSettings {
@@ -14,6 +15,7 @@ export interface SearchSettings {
scrollRandomResults: boolean;
clickRandomResults: boolean;
searchDelay: SearchDelay;
retryMobileSearch: boolean;
}
export interface SearchDelay {

View File

@@ -1,6 +1,9 @@
import { Cookie } from 'playwright'
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
import fs from 'fs'
import path from 'path'
import { Account } from '../interface/Account'
import { Config } from '../interface/Config'
@@ -34,7 +37,37 @@ export function loadConfig(): Config {
}
}
export async function loadSesion(sessionPath: string, email: string): Promise<string> {
export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, getFingerprint: boolean) {
try {
// Fetch cookie file
const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`)
let cookies: Cookie[] = []
if (fs.existsSync(cookieFile)) {
const cookiesData = await fs.promises.readFile(cookieFile, 'utf-8')
cookies = JSON.parse(cookiesData)
}
// Fetch fingerprint file
const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`)
let fingerprint!: BrowserFingerprintWithHeaders
if (getFingerprint && fs.existsSync(fingerprintFile)) {
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
fingerprint = JSON.parse(fingerprintData)
}
return {
cookies: cookies,
fingerprint: fingerprint
}
} catch (error) {
throw new Error(error as string)
}
}
export async function saveSessionData(sessionPath: string, email: string, isMobile: boolean, cookies: Cookie[]): Promise<string> {
try {
// Fetch path
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
@@ -44,6 +77,28 @@ export async function loadSesion(sessionPath: string, email: string): Promise<st
await fs.promises.mkdir(sessionDir, { recursive: true })
}
// Save cookies to a file
await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`), JSON.stringify(cookies))
return sessionDir
} catch (error) {
throw new Error(error as string)
}
}
export async function saveFingerprintData(sessionPath: string, email: string, isMobile: boolean, fingerpint: BrowserFingerprintWithHeaders): 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 })
}
// Save fingerprint to a file
await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`), JSON.stringify(fingerpint))
return sessionDir
} catch (error) {
throw new Error(error as string)

View File

@@ -81,7 +81,7 @@ export async function getEdgeVersions() {
export function getSystemComponents(mobile: boolean): string {
const osId: string = mobile ? 'Linux' : 'Windows NT 10.0'
const uaPlatform: string = mobile ? 'Android 13' : 'Win64; x64'
const uaPlatform: string = mobile ? `Android 1${Math.floor(Math.random() * 5)}` : 'Win64; x64'
if (mobile) {
return `${uaPlatform}; ${osId}; K`