mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-12 03:16:19 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6ab80fe54 | ||
|
|
9b1eed526f | ||
|
|
9a144b2e60 | ||
|
|
28b1881642 | ||
|
|
ef6ad569ff | ||
|
|
da9ba91c5c | ||
|
|
deb2d58b1b | ||
|
|
66a82c2584 | ||
|
|
8a022d5983 | ||
|
|
64048e35d7 | ||
|
|
cf7f7ac790 | ||
|
|
f7aa5039f9 | ||
|
|
e082fb03f0 | ||
|
|
0303b8c605 | ||
|
|
2fea17c415 | ||
|
|
c5beccb54b | ||
|
|
b566ccaece | ||
|
|
15b2b827eb | ||
|
|
02518ee4ba | ||
|
|
69819b5631 | ||
|
|
b389b87792 | ||
|
|
9ea7f5c452 | ||
|
|
f3fb641ecd |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ package-lock.json
|
||||
accounts.json
|
||||
notes
|
||||
accounts.dev.json
|
||||
accounts.main.json
|
||||
accounts.main.json
|
||||
.DS_Store
|
||||
|
||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# 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, cron, and gettext-base
|
||||
RUN apt-get update && apt-get install -y jq cron gettext-base
|
||||
|
||||
# Copy all files to the working directory
|
||||
COPY . .
|
||||
|
||||
# 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
|
||||
|
||||
# Copy cron file to cron directory
|
||||
COPY src/crontab.template /etc/cron.d/microsoft-rewards-cron.template
|
||||
|
||||
# Create the log file to be able to run tail
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Define the command to run your application with cron optionally
|
||||
CMD sh -c 'echo "$TZ" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata && if [ "$RUN_ON_START" = "true" ]; then npm start; fi && envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && crontab /etc/cron.d/microsoft-rewards-cron && cron && tail -f /var/log/cron.log'
|
||||
68
README.md
68
README.md
@@ -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,71 @@ 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-win` script. (Windows)
|
||||
- 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 kill-chrome-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 (Experimental) ##
|
||||
1. Download the source code
|
||||
2. Make changes to your `accounts.json`
|
||||
3. **Headless mode must be enabled when using Docker.** You can do this using the `HEADLESS=true` environmental variable in docker run or docker compose.yaml (see below). Environmental variables are always prioritized over the values in config.json.
|
||||
4. The container will run scheduled. Customize your schedule using the `CRON_START_TIME` environmental variable. Use [crontab.guru](crontab.guru) if you're unsure how to create a cron schedule.
|
||||
5. **Note:** the container will add between 5 and 50 minutes of randomized variability to your scheduled start times.
|
||||
### Option 1: build and run with docker run
|
||||
|
||||
1. Build or re-build the container image with: `docker build -t microsoft-rewards-script-docker .`
|
||||
|
||||
2. Run the container with:
|
||||
|
||||
```bash
|
||||
docker run --name netsky -d \
|
||||
-e TZ=America/New_York \
|
||||
-e HEADLESS=true \
|
||||
-e SEARCH_DELAY_MIN=10000 \
|
||||
-e SEARCH_DELAY_MAX=20000 \
|
||||
-e CLUSTERS=1 \
|
||||
-e CRON_START_TIME="0 5,11 * * *" \
|
||||
microsoft-rewards-script-docker
|
||||
```
|
||||
|
||||
3. Optionally, change any environmental variables other than `HEADLESS`, which must stay `=true`
|
||||
|
||||
4. You can view logs with `docker logs netsky`.
|
||||
|
||||
### Option 2: use docker compose
|
||||
|
||||
1. A basic docker compose.yaml has been provided.
|
||||
|
||||
2. Optionally, change any environmental variables other than `HEADLESS`, which must stay `=true`
|
||||
|
||||
3. Build or rebuild and start the container using `docker compose up -d --build`
|
||||
|
||||
4. You can view logs with `docker logs netsky`
|
||||
|
||||
|
||||
## Config ##
|
||||
| Setting | Description | Default | Docker Environmental Variable |
|
||||
| :------------- |:-------------| :-----| :-----|
|
||||
| baseURL | MS Rewards page | `https://rewards.bing.com` | BASE_URL |
|
||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) | SESSION_PATH |
|
||||
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) | HEADLESS *(must be set to `=true` for docker)* |
|
||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) | RUN_ON_ZERO_POINTS |
|
||||
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) | CLUSTERS |
|
||||
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) | SAVE_FINGERPRINT |
|
||||
| workers.doDailySet | Complete daily set items | `true` | WORKERS_DO_DAILY_SET |
|
||||
| workers.doMorePromotions | Complete promotional items | `true` | WORKERS_DO_MORE_PROMOTIONS |
|
||||
| workers.doPunchCards | Complete punchcards | `true` | WORKERS_DO_PUNCH_CARDS |
|
||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` | WORKERS_DO_DESKTOP_SEARCH |
|
||||
| workers.doMobileSearch | Complete daily mobile searches | `true` | WORKERS_DO_MOBILE_SEARCH |
|
||||
| globalTimeout | The length before the action gets timeout | `30000` (30 seconds) | GLOBAL_TIMEOUT |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) | SEARCH_SETTINGS_USE_GEO_LOCALE_QUERIES |
|
||||
| scrollRandomResults | Scroll randomly in search results | `true` | SEARCH_SETTINGS_SCROLL_RANDOM_RESULTS |
|
||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` | SEARCH_SETTINGS_CLICK_RANDOM_RESULTS |
|
||||
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) | SEARCH_DELAY_MIN SEARCH_DELAY_MAX |
|
||||
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` | SEARCH_SETTINGS_RETRY_MOBILE_SEARCH |
|
||||
| webhook.enabled | Enable or disable your set webhook | `false` | WEBHOOK_ENABLED |
|
||||
| webhook.url | Your Discord webhook URL | `null` | WEBHOOK_URL="" |
|
||||
| cronStartTime | Scheduled script run-time, *only available for docker implementation* | `0 5,11 * * *` (5:00 am, 11:00 am daily) | CRON_START_TIME="" |
|
||||
| | Run the script immediately when the Docker container starts | `true` | RUN_ON_START |
|
||||
|
||||
## Features ##
|
||||
- [x] Multi-Account Support
|
||||
@@ -42,6 +104,8 @@ Under development, however mainly for personal use!
|
||||
- [ ] Completing Gaming Tab
|
||||
- [x] Clustering Support
|
||||
- [x] Proxy Support
|
||||
- [x] Docker Support (experimental)
|
||||
- [x] Automatic scheduling (via Docker)
|
||||
|
||||
## Disclaimer ##
|
||||
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
||||
|
||||
34
compose.yaml
Normal file
34
compose.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
services:
|
||||
microsoft-rewards-script:
|
||||
build: .
|
||||
container_name: netsky
|
||||
environment:
|
||||
- TZ=America/Toronto #change to your local timezone
|
||||
- NODE_ENV=production
|
||||
- HEADLESS=true #do not change
|
||||
### the following are optional, you only need to include them if you want to enter a custom value, removing them will use the default values
|
||||
- BASE_URL=https://rewards.bing.com
|
||||
- SESSION_PATH=sessions
|
||||
- RUN_ON_ZERO_POINTS=false
|
||||
- CLUSTERS=1
|
||||
- SAVE_FINGERPRINT=false
|
||||
- WORKERS_DO_DAILY_SET=true
|
||||
- WORKERS_DO_MORE_PROMOTIONS=true
|
||||
- WORKERS_DO_PUNCH_CARDS=true
|
||||
- WORKERS_DO_DESKTOP_SEARCH=true
|
||||
- WORKERS_DO_MOBILE_SEARCH=true
|
||||
- SEARCH_SETTINGS_USE_GEO_LOCALE_QUERIES=false
|
||||
- SEARCH_SETTINGS_SCROLL_RANDOM_RESULTS=true
|
||||
- SEARCH_SETTINGS_CLICK_RANDOM_RESULTS=true
|
||||
- SEARCH_SETTINGS_SEARCH_DELAY_MIN=10000 # Set the search delay longer, e.g. MIN=180000 and MAX=270000 if you live in a region where MS enforces a search cooldown
|
||||
- SEARCH_SETTINGS_SEARCH_DELAY_MAX=20000
|
||||
- SEARCH_SETTINGS_RETRY_MOBILE_SEARCH=true
|
||||
- WEBHOOK_ENABLED=false
|
||||
- WEBHOOK_URL=
|
||||
### Customize your run schedule, default 5:00 am and 11:00 am, use crontab.guru if you're not sure
|
||||
- CRON_START_TIME=0 5,11 * * *
|
||||
### Run on start, set as false to only run the script per the cron schedule
|
||||
- RUN_ON_START=true
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- .:/usr/src/microsoft-rewards-script
|
||||
21
package.json
21
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "microsoft-rewards-script",
|
||||
"version": "1.3.1",
|
||||
"version": "1.4.7",
|
||||
"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,23 +20,23 @@
|
||||
"Bot",
|
||||
"Script",
|
||||
"TypeScript",
|
||||
"Puppeteer",
|
||||
"Playwright",
|
||||
"Cheerio"
|
||||
],
|
||||
"author": "Netsky",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-modules-newline": "^0.0.6",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"axios": "^1.7.2",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"fingerprint-generator": "^2.1.45",
|
||||
"fingerprint-injector": "^2.1.45",
|
||||
"puppeteer": "^21.6.1",
|
||||
"fingerprint-generator": "^2.1.51",
|
||||
"fingerprint-injector": "^2.1.51",
|
||||
"playwright": "^1.44.1",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,50 @@ 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 })
|
||||
|
||||
// Set timeout to preferred amount
|
||||
context.setDefaultTimeout(this.bot.config?.globalTimeout ?? 30_000)
|
||||
|
||||
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
|
||||
|
||||
@@ -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,19 +89,19 @@ 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'))
|
||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||
|
||||
if (targetScript) {
|
||||
return targetScript.innerText
|
||||
} else {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
||||
}
|
||||
return targetScript?.innerText ? targetScript.innerText : null
|
||||
})
|
||||
|
||||
if (!scriptContent) {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
|
||||
// Extract the dashboard object from the script content
|
||||
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
||||
// Extract the dashboard object using regex
|
||||
@@ -110,11 +110,13 @@ export default class BrowserFunc {
|
||||
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
} else {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
}, scriptContent)
|
||||
|
||||
if (!dashboardData) {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||
}
|
||||
|
||||
return dashboardData
|
||||
}
|
||||
|
||||
@@ -140,7 +142,9 @@ export default class BrowserFunc {
|
||||
let totalEarnablePoints = 0
|
||||
|
||||
// Desktop Search Points
|
||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
if (data.userStatus.counters.pcSearch?.length) {
|
||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
}
|
||||
|
||||
// Mobile Search Points
|
||||
if (data.userStatus.counters.mobileSearch?.length) {
|
||||
@@ -151,12 +155,14 @@ export default class BrowserFunc {
|
||||
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)
|
||||
}
|
||||
})
|
||||
if (data.morePromotions?.length) {
|
||||
data.morePromotions.forEach(x => {
|
||||
// Only count points from supported activities
|
||||
if (['quiz', 'urlreward'].includes(x.promotionType)) {
|
||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return totalEarnablePoints
|
||||
} catch (error) {
|
||||
@@ -180,7 +186,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 +220,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 +232,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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class BrowserUtil {
|
||||
|
||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
const buttons = [
|
||||
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||
{ selector: '#iNext', label: 'iNext' },
|
||||
@@ -24,7 +25,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 +74,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 +90,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
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"headless": false,
|
||||
"runOnZeroPoints": false,
|
||||
"clusters": 1,
|
||||
"saveFingerprint": false,
|
||||
"workers": {
|
||||
"doDailySet": true,
|
||||
"doMorePromotions": true,
|
||||
@@ -11,6 +12,7 @@
|
||||
"doDesktopSearch": true,
|
||||
"doMobileSearch": true
|
||||
},
|
||||
"globalTimeout": 30000,
|
||||
"searchSettings": {
|
||||
"useGeoLocaleQueries": false,
|
||||
"scrollRandomResults": true,
|
||||
@@ -18,10 +20,12 @@
|
||||
"searchDelay": {
|
||||
"min": 10000,
|
||||
"max": 20000
|
||||
}
|
||||
},
|
||||
"retryMobileSearch": true
|
||||
},
|
||||
"webhook": {
|
||||
"enabled": false,
|
||||
"url": ""
|
||||
}
|
||||
},
|
||||
"cronStartTime": "0 5,11 * * *"
|
||||
}
|
||||
1
src/crontab.template
Normal file
1
src/crontab.template
Normal file
@@ -0,0 +1 @@
|
||||
${CRON_START_TIME} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /var/log/cron.log 2>&1
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
import readline from 'readline'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -26,14 +27,12 @@ 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: 1000 }).then(() => true).catch(() => false)
|
||||
if (isLocked) {
|
||||
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
||||
throw new Error('Account has been locked!')
|
||||
}
|
||||
|
||||
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
|
||||
|
||||
await this.execLogin(page, email, password)
|
||||
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
||||
} else {
|
||||
@@ -43,6 +42,9 @@ export class Login {
|
||||
// Check if logged in to bing
|
||||
await this.checkBingLogin(page)
|
||||
|
||||
// Save session
|
||||
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
||||
|
||||
// We're done logging in
|
||||
this.bot.log('LOGIN', 'Logged in successfully')
|
||||
|
||||
@@ -53,35 +55,41 @@ export class Login {
|
||||
}
|
||||
|
||||
private async execLogin(page: Page, email: string, password: string) {
|
||||
await page.type('#i0116', email)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
this.bot.log('LOGIN', 'Email entered successfully')
|
||||
|
||||
try {
|
||||
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.type('#i0118', password)
|
||||
// Enter email
|
||||
await page.fill('#i0116', email)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
// When erroring at this stage it means a 2FA code is required
|
||||
} catch (error) {
|
||||
this.bot.log('LOGIN', '2FA code required')
|
||||
this.bot.log('LOGIN', 'Email entered successfully')
|
||||
|
||||
// Wait for user input
|
||||
const code = await new Promise<string>((resolve) => {
|
||||
rl.question('Enter 2FA code:\n', (input) => {
|
||||
rl.close()
|
||||
resolve(input)
|
||||
try {
|
||||
// Enter password
|
||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.fill('#i0118', password)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
// When erroring at this stage it means a 2FA code is required
|
||||
} catch (error) {
|
||||
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()
|
||||
resolve(input)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
await page.type('input[name="otc"]', code)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.fill('input[name="otc"]', code)
|
||||
await page.keyboard.press('Enter')
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.bot.log('LOGIN', 'Password entered successfully')
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
const currentURL = new URL(page.url())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
|
||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||
|
||||
@@ -38,7 +38,7 @@ export class Workers {
|
||||
// Punch Card
|
||||
async doPunchCard(page: Page, data: DashboardData) {
|
||||
|
||||
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
|
||||
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion?.complete) ?? [] // Only return uncompleted punch cards
|
||||
|
||||
if (!punchCardsUncompleted.length) {
|
||||
this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }).catch(() => { }) // We're gonna click regardless or not
|
||||
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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
import axios from 'axios'
|
||||
import { platform } from 'os'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -63,6 +64,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 +78,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`)
|
||||
@@ -117,17 +129,27 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
private async bingSearch(searchPage: Page, query: string) {
|
||||
const platformControlKey = platform() === 'darwin' ? 'Meta' : 'Control'
|
||||
|
||||
// Try a max of 5 times
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
|
||||
// Go to top of the page
|
||||
await searchPage.evaluate(() => {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
|
||||
await this.bot.utils.wait(500)
|
||||
|
||||
const searchBar = '#sb_form_q'
|
||||
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
|
||||
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||
await searchPage.click(searchBar) // Focus on the textarea
|
||||
await this.bot.utils.wait(500)
|
||||
await searchPage.keyboard.down('Control')
|
||||
await searchPage.keyboard.down(platformControlKey)
|
||||
await searchPage.keyboard.press('A')
|
||||
await searchPage.keyboard.press('Backspace')
|
||||
await searchPage.keyboard.up('Control')
|
||||
await searchPage.keyboard.up(platformControlKey)
|
||||
await searchPage.keyboard.type(query)
|
||||
await searchPage.keyboard.press('Enter')
|
||||
|
||||
@@ -141,6 +163,7 @@ export class Search extends Workers {
|
||||
await this.clickRandomLink(searchPage)
|
||||
}
|
||||
|
||||
// Delay between searches
|
||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
|
||||
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
@@ -155,20 +178,8 @@ export class Search extends Workers {
|
||||
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
|
||||
|
||||
// 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.closeTabs(lastTab, this.searchPageURL)
|
||||
|
||||
await this.bot.utils.wait(4000)
|
||||
}
|
||||
@@ -249,10 +260,11 @@ 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++) {
|
||||
await page.keyboard.press('ArrowDown')
|
||||
}
|
||||
const scrollAmount = this.bot.utils.randomNumber(5, 5000)
|
||||
|
||||
await page.evaluate((scrollAmount) => {
|
||||
window.scrollBy(0, scrollAmount)
|
||||
}, scrollAmount)
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
@@ -276,19 +288,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
|
||||
@@ -301,6 +302,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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'puppeteer'
|
||||
import { Page } from 'playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
|
||||
57
src/index.ts
57
src/index.ts
@@ -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,10 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
// Desktop
|
||||
async Desktop(account: Account) {
|
||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy)
|
||||
this.isMobile = false
|
||||
|
||||
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 +148,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 +177,20 @@ export class MicrosoftRewardsBot {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
}
|
||||
|
||||
// Save cookies
|
||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||
|
||||
// Close desktop browser
|
||||
await browser.close()
|
||||
await this.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// 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 +205,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 +222,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 +242,21 @@ export class MicrosoftRewardsBot {
|
||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
private async closeBrowser(browser: BrowserContext, email: string) {
|
||||
// Save cookies
|
||||
await saveSessionData(this.config.sessionPath, browser, email, this.isMobile)
|
||||
|
||||
// Close browser
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const bot = new MicrosoftRewardsBot()
|
||||
|
||||
// Initialize accounts first and then start the bot
|
||||
|
||||
@@ -5,8 +5,10 @@ export interface Config {
|
||||
runOnZeroPoints: boolean;
|
||||
clusters: number;
|
||||
workers: Workers;
|
||||
globalTimeout: number;
|
||||
searchSettings: SearchSettings;
|
||||
webhook: Webhook;
|
||||
saveFingerprint: boolean;
|
||||
}
|
||||
|
||||
export interface SearchSettings {
|
||||
@@ -14,6 +16,7 @@ export interface SearchSettings {
|
||||
scrollRandomResults: boolean;
|
||||
clickRandomResults: boolean;
|
||||
searchDelay: SearchDelay;
|
||||
retryMobileSearch: boolean;
|
||||
}
|
||||
|
||||
export interface SearchDelay {
|
||||
|
||||
29
src/run_daily.sh
Normal file
29
src/run_daily.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set up environment variables
|
||||
export PATH=$PATH:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
|
||||
|
||||
# Change directory to the application directory
|
||||
cd /usr/src/microsoft-rewards-script
|
||||
|
||||
# Define the minimum and maximum wait times in seconds
|
||||
MINWAIT=$((5*60)) # 5 minutes
|
||||
MAXWAIT=$((50*60)) # 50 minutes
|
||||
|
||||
# Calculate a random sleep time within the specified range
|
||||
SLEEPTIME=$((MINWAIT + RANDOM % (MAXWAIT - MINWAIT)))
|
||||
|
||||
# Convert the sleep time to minutes for logging
|
||||
SLEEP_MINUTES=$((SLEEPTIME / 60))
|
||||
|
||||
# Log the sleep duration
|
||||
echo "Sleeping for $SLEEP_MINUTES minutes ($SLEEPTIME seconds)..."
|
||||
|
||||
# Sleep for the calculated time
|
||||
sleep $SLEEPTIME
|
||||
|
||||
# Log the start of the script
|
||||
echo "Starting script..."
|
||||
|
||||
# Execute the Node.js script directly
|
||||
npm run start
|
||||
@@ -1,6 +1,9 @@
|
||||
import { BrowserContext, 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,58 @@ 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, browser: BrowserContext, email: string, isMobile: boolean): Promise<string> {
|
||||
try {
|
||||
const cookies = await browser.cookies()
|
||||
|
||||
// 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 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)
|
||||
@@ -44,6 +98,9 @@ export async function loadSesion(sessionPath: string, email: string): Promise<st
|
||||
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)
|
||||
|
||||
@@ -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`
|
||||
|
||||
Reference in New Issue
Block a user