mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-10 02:16:18 +00:00
1.5 inital (#234)
* 1.4.12 * Update README.md * Update package.json * Update package.json * 1.5 initial - Added parallel mode (experimental, likely no Docker supported) - Added chalk for clearer logging - Added support for "SearchOnBing" Activities - Added more configurable options for certain things - Redone some of the popup/banner clicking for searching (Redo the entire "popup" clicking, so they're more specifically targeted) - Axios proxy is now optional in the config - Fingerprint saving is now optional for desktop and mobile There needs to be many changes for Docker support, including parallel, the new config settings and general testing! This is still highly experimental, if you use Docker or want something more stable, use the version before this commit! * Add queries.json to build * fix(Login): update URL within authorization loop to reflect current page (#210) * Many changes - Updated Packages - Fixed mobile searches erroring out for dashboard data - Reworked "bad page" detection - Catching more errors - Reworked the search and "close tabs" - More fixes to the login - Fixed to paralell and clustering, thanks to @AariaX * Docker 1.5 preliminary support (#211) * Basic docker functionality for 1.5 Preliminary docker support for 1.5. Requires headless=true, clusters=1 * Tidy up timezone, add TZ to compose file Minor changes that should improve timezone handling, and (hopefully) improve scheduling function * updated readme to simplify and clarify docker instructions also removed env vars from table * Fix syntax for cron * Fix scheduling, add .gitattributes to normalize line endings fixed line endings caused by Windows in crontab.template and run_daily.sh, which were breaking cron and script execution in the Docker container. * Removed unnecessary scheduling key from config.json This key isn't necessary for docker or the base script. * Basic docker functionality for 1.5 Preliminary docker support for 1.5. Requires headless=true, clusters=1 Tidied up timezone, add TZ to compose file Minor changes that should improve timezone handling, and (hopefully) improve scheduling function updated readme to simplify and clarify docker instructions also removed env vars from table Fixed syntax for cron Fixed scheduling, add .gitattributes to normalize line endings Fixed line endings caused by Windows in crontab.template and run_daily.sh, which were breaking cron and script execution in the Docker container. Removed unnecessary scheduling key from config.json This key isn't necessary for docker or the base script. * Improve scheduling handling, show logs in console Fixes scheduling when RUN_ON_START=true, and fixes scheduled runs not appearing in docker logs. * Update compose.yaml revert service and container name, revert volumes for better generalization, add tips to environment to set scheduling, timezone and whether to run on container start * Update README.md proper container name Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com> --------- Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com> * Fixes - Reworked some of the point counting - Reverted back to the "playwright" package - Fixed error throw for emailPrefill * Update config.json * Add pre-build script * Update package.json * Handle 2FA in parallel mode (#219) * catch error in reloadBadPage (#220) * Use pre-build and simplify dockerfile (#218) This uses the new pre-build script included in package.json to handle deps greatly simplifying the dockerfile. * Small improvements * Small fixes - Fixed log spam for "Waiting for authorization" - Increased wait from 2 to 5 seconds - Increased search to "safer" values for default * Experimenting with selectors Seeing #223 I want to try if this is a good new addition, since for most user this SHOULD work just as good as clicking the entire box. * More stuff - Added ability to exclude logs by their function name - Now caching config settings * fix: don't retry on 0 (#228) * Improvements - Check if searches for mobile are enabled before creating the new page in the browser - Return message if mobile search data cannot be found - Added more selectors for coupons * Improve Popup Dismissal - Now executes in Parallel - Respects a timeout of 1 second --------- Co-authored-by: AariaX <196196201+AariaX@users.noreply.github.com> Co-authored-by: mgrimace <55518507+mgrimace@users.noreply.github.com>
This commit is contained in:
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.sh text eol=lf
|
||||
*.template text eol=lf
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@ accounts.json
|
||||
notes
|
||||
accounts.dev.json
|
||||
accounts.main.json
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
96
Dockerfile
96
Dockerfile
@@ -1,50 +1,46 @@
|
||||
# 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, gettext-base, Playwright dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
jq \
|
||||
cron \
|
||||
gettext-base \
|
||||
xvfb \
|
||||
libgbm-dev \
|
||||
libnss3 \
|
||||
libasound2 \
|
||||
libxss1 \
|
||||
libatk-bridge2.0-0 \
|
||||
libgtk-3-0 \
|
||||
tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy all files to the working directory
|
||||
COPY . .
|
||||
|
||||
# Install application dependencies
|
||||
RUN npm install && \
|
||||
# Ensure correct permissions for node_modules
|
||||
chmod -R 755 /usr/src/microsoft-rewards-script/node_modules && \
|
||||
# Install Playwright Chromium directly from local node_modules
|
||||
./node_modules/.bin/playwright install chromium && \
|
||||
# Ensure correct permissions for the working directory
|
||||
chmod -R 755 /usr/src/microsoft-rewards-script
|
||||
|
||||
# Build the script
|
||||
RUN npm run build
|
||||
|
||||
# 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", "node src/updateConfig.js && \
|
||||
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"]
|
||||
# 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 necessary dependencies for Playwright and cron
|
||||
RUN apt-get update && apt-get install -y \
|
||||
jq \
|
||||
cron \
|
||||
gettext-base \
|
||||
xvfb \
|
||||
libgbm-dev \
|
||||
libnss3 \
|
||||
libasound2 \
|
||||
libxss1 \
|
||||
libatk-bridge2.0-0 \
|
||||
libgtk-3-0 \
|
||||
tzdata \
|
||||
wget \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy all files to the working directory
|
||||
COPY . .
|
||||
|
||||
# Install dependencies, set permissions, and build the script
|
||||
RUN npm install && \
|
||||
chmod -R 755 /usr/src/microsoft-rewards-script/node_modules && \
|
||||
npm run pre-build && \
|
||||
npm run build
|
||||
|
||||
# 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 && \
|
||||
envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && \
|
||||
chmod 0644 /etc/cron.d/microsoft-rewards-cron && \
|
||||
crontab /etc/cron.d/microsoft-rewards-cron && \
|
||||
cron -f & \
|
||||
([ \"$RUN_ON_START\" = \"true\" ] && npm start) && \
|
||||
tail -f /var/log/cron.log"]
|
||||
107
README.md
107
README.md
@@ -16,71 +16,68 @@ Under development, however mainly for personal use!
|
||||
- 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) ##
|
||||
**Note:** If you had previously built and run the script locally, remove the `/node_modules` and `/dist` from your Microsoft-Rewards-Script folder.
|
||||
### **Before Starting**
|
||||
|
||||
1. Download the source code
|
||||
2. Make changes to your `accounts.json` and `config.json`.
|
||||
3. Use volume mapping (see sample compose.yaml) to ensure these files are stored on your local machine and not inside the container. This way, your data (like account settings and config options) will persist even if the container is restarted or updated.
|
||||
4. **Headless mode must be enabled.** You can do this in `config.json` or by using the `HEADLESS=true` environmental variable in docker run or docker compose.yaml (see below). Environmental variables are prioritized over the values in config.json.
|
||||
5. The container has in-built scheduling. 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.
|
||||
6. **Note:** the container will add between 5 and 50 minutes of randomized variability to your scheduled start times.
|
||||
- If you had previously built and run the script locally, **remove** the `/node_modules` and `/dist` folders from your `Microsoft-Rewards-Script` directory.
|
||||
- If you had used Docker with an older version of the script (e.g., 1.4), **remove** any persistently saved `config.json` and session folders. Old `accounts.json` files can be reused.
|
||||
|
||||
### Option 1: build and run with docker run
|
||||
### **Setup the Source Files**
|
||||
|
||||
1. Build or re-build the container image with: `docker build -t microsoft-rewards-script-docker .`
|
||||
1. **Download the Source Code**
|
||||
|
||||
2. Run the container with:
|
||||
2. **Update `accounts.json`**
|
||||
|
||||
```bash
|
||||
docker run --name netsky -d \
|
||||
-e TZ=America/New_York \
|
||||
-e HEADLESS=true \
|
||||
-e RUN_ON_START=true \
|
||||
-e CRON_START_TIME="0 5,11 * * *" \
|
||||
microsoft-rewards-script-docker
|
||||
3. **Edit `config.json`,** ensuring the following values are set (other settings are up to your preference):
|
||||
|
||||
```json
|
||||
"headless": true,
|
||||
"clusters": 1,
|
||||
```
|
||||
|
||||
3. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||
|
||||
4. You can view logs with `docker logs netsky`.
|
||||
### **Customize the `compose.yaml` File**
|
||||
|
||||
### Option 2: use docker compose
|
||||
A basic docker `compose.yaml` is provided. Follow these steps to configure and run the container:
|
||||
|
||||
1. A basic docker compose.yaml has been provided.
|
||||
|
||||
2. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||
|
||||
3. Build and start the container using `docker compose up -d`.
|
||||
|
||||
4. You can view logs with `docker logs netsky`
|
||||
1. **Set Your Timezone:** Adjust the `TZ` variable to ensure correct scheduling.
|
||||
2. **Configure Persistent Storage:**
|
||||
- Map `config.json` and `accounts.json` to retain settings and accounts.
|
||||
- (Optional) Use a persistent `sessions` folder to save login sessions.
|
||||
3. **Customize the Schedule:**
|
||||
- Modify `CRON_SCHEDULE` to set run times. Use [crontab.guru](https://crontab.guru) for help.
|
||||
- **Note:** The container adds 5–50 minutes of random variability to each scheduled start time.
|
||||
4. **(Optional) Run on Startup:**
|
||||
- Set `RUN_ON_START=true` to execute the script immediately when the container starts.
|
||||
5. **Start the Container:** Run `docker compose up -d` to build and launch.
|
||||
6. **Monitor Logs:** Use `docker logs microsoft-rewards-script` to view script execution and to retrieve 'passwordless' login codes.
|
||||
|
||||
|
||||
## 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` | DO_DAILY_SET |
|
||||
| workers.doMorePromotions | Complete promotional items | `true` | DO_MORE_PROMOTIONS |
|
||||
| workers.doPunchCards | Complete punchcards | `true` | DO_PUNCH_CARDS |
|
||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` | DO_DESKTOP_SEARCH |
|
||||
| workers.doMobileSearch | Complete daily mobile searches | `true` | DO_MOBILE_SEARCH |
|
||||
| workers.doDailyCheckIn | Complete daily check-in activity | `true` | DO_DAILY_CHECK_IN |
|
||||
| workers.doReadToEarn | Complete read to earn activity | `true` | DO_READ_TO_EARN |
|
||||
| globalTimeout | The length before the action gets timeout | `30000` (30 seconds) | GLOBAL_TIMEOUT |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `true` (Uses EN-US generated queries) | USE_GEO_LOCALE_QUERIES |
|
||||
| scrollRandomResults | Scroll randomly in search results | `true` | SCROLL_RANDOM_RESULTS |
|
||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` | 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` | 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 |
|
||||
| Setting | Description | Default |
|
||||
| :------------- |:-------------| :-----|
|
||||
| baseURL | MS Rewards page | `https://rewards.bing.com` |
|
||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) |
|
||||
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) |
|
||||
| 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` |
|
||||
| workers.doDailyCheckIn | Complete daily check-in activity | `true` |
|
||||
| workers.doReadToEarn | Complete read to earn activity | `true` |
|
||||
| globalTimeout | The length before the action gets timeout | `30s` |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `true` (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: 1min` `max: 2min` |
|
||||
| searchSettings.retryMobileSearchAmount | Keep retrying mobile searches for specified amount | `3` |
|
||||
| logExcludeFunc | Functions to exclude out of the logs and webhooks | `SEARCH-CLOSE-TABS` |
|
||||
| webhook.enabled | Enable or disable your set webhook | `false` |
|
||||
| webhook.url | Your Discord webhook URL | `null` |
|
||||
| cronStartTime | Scheduled script run-time, *only available for docker implementation* | `0 5,11 * * *` (5:00 am, 11:00 am daily) |
|
||||
| | Run the script immediately when the Docker container starts | `true` |
|
||||
|
||||
## Features ##
|
||||
- [x] Multi-Account Support
|
||||
@@ -107,8 +104,6 @@ Under development, however mainly for personal use!
|
||||
- [x] Solving ABC Quiz
|
||||
- [x] Completing Daily Check In
|
||||
- [x] Completing Read To Earn
|
||||
- [ ] Completing Shopping Game
|
||||
- [ ] Completing Gaming Tab
|
||||
- [x] Clustering Support
|
||||
- [x] Proxy Support
|
||||
- [x] Docker Support (experimental)
|
||||
@@ -117,4 +112,4 @@ Under development, however mainly for personal use!
|
||||
## Disclaimer ##
|
||||
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
||||
<br />
|
||||
Use this script at your own risk!
|
||||
Use this script at your own risk!
|
||||
23
compose.yaml
23
compose.yaml
@@ -1,22 +1,15 @@
|
||||
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
|
||||
### Customize your run schedule, default 5:00 am and 11:00 am, use crontab.guru for formatting
|
||||
- CRON_START_TIME=0 5,11 * * *
|
||||
### Run on start, set to false to only run the script per the cron schedule
|
||||
- RUN_ON_START=true
|
||||
container_name: microsoft-rewards-script
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
### Replace "/path/to/" with the actual path to where you want to save the files on your local machine.
|
||||
- /path/to/accounts.json:/usr/src/microsoft-rewards-script/dist/accounts.json
|
||||
- /path/to/config.json:/usr/src/microsoft-rewards-script/dist/config.json
|
||||
- /path/to/sessions:/usr/src/microsoft-rewards-script/dist/browser/sessions #optional, saves your login session
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 2048M
|
||||
restart: unless-stopped
|
||||
- /path/to/sessions:/usr/src/microsoft-rewards-script/dist/browser/sessions # Optional, saves your login session
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- CRON_SCHEDULE=0 7,15,20 * * * # Customize your schedule, use crontab.guru for formatting
|
||||
- RUN_ON_START=true # Runs the script on container startup
|
||||
- TZ=America/Toronto # Set your timezone for proper scheduling
|
||||
19
package.json
19
package.json
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "microsoft-rewards-script",
|
||||
"version": "1.4.11",
|
||||
"version": "1.5.0",
|
||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"pre-build": "npm i && rimraf dist && npx playwright install chromium",
|
||||
"build": "tsc",
|
||||
"start": "node ./dist/index.js",
|
||||
"ts-start": "ts-node ./src/index.ts",
|
||||
@@ -26,17 +27,25 @@
|
||||
"author": "Netsky",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ms": "^0.7.34",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-modules-newline": "^0.0.6",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.5",
|
||||
"axios": "^1.7.9",
|
||||
"chalk": "^4.1.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"fingerprint-generator": "^2.1.56",
|
||||
"fingerprint-injector": "^2.1.56",
|
||||
"playwright": "^1.47.2",
|
||||
"fingerprint-generator": "^2.1.62",
|
||||
"fingerprint-injector": "^2.1.62",
|
||||
"http-proxy-agent": "^7.0.2",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"ms": "^2.1.3",
|
||||
"playwright": "1.47.2",
|
||||
"rebrowser-playwright": "1.47.2",
|
||||
"socks-proxy-agent": "^8.0.5",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"email": "email_1",
|
||||
"password": "password_1",
|
||||
"proxy": {
|
||||
"proxyAxios": true,
|
||||
"url": "",
|
||||
"port": 0,
|
||||
"username": "",
|
||||
@@ -13,6 +14,7 @@
|
||||
"email": "email_2",
|
||||
"password": "password_2",
|
||||
"proxy": {
|
||||
"proxyAxios": true,
|
||||
"url": "",
|
||||
"port": 0,
|
||||
"username": "",
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import playwright from 'playwright'
|
||||
import { BrowserContext } from 'playwright'
|
||||
import playwright, { BrowserContext } from 'rebrowser-playwright'
|
||||
|
||||
import { newInjectedContext } from 'fingerprint-injector'
|
||||
import { FingerprintGenerator } from 'fingerprint-generator'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { loadSessionData, saveFingerprintData } from '../util/Load'
|
||||
import { updateFingerprintUserAgent } from '../util/UserAgent'
|
||||
|
||||
import { AccountProxy } from '../interface/Account'
|
||||
|
||||
/* Test Stuff
|
||||
https://abrahamjuliot.github.io/creepjs/
|
||||
https://botcheck.luminati.io/
|
||||
http://f.vision/
|
||||
https://fv.pro/
|
||||
https://pixelscan.net/
|
||||
https://www.browserscan.net/
|
||||
*/
|
||||
|
||||
class Browser {
|
||||
@@ -40,33 +41,35 @@ class Browser {
|
||||
|
||||
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)
|
||||
|
||||
const fingerpint = sessionData.fingerprint ? sessionData.fingerprint : this.generateFingerprint()
|
||||
const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint()
|
||||
|
||||
const context = await newInjectedContext(browser, { fingerprint: fingerpint })
|
||||
const context = await newInjectedContext(browser as any, { fingerprint: fingerprint })
|
||||
|
||||
// Set timeout to preferred amount
|
||||
context.setDefaultTimeout(this.bot.config?.globalTimeout ?? 30_000)
|
||||
context.setDefaultTimeout(this.bot.utils.stringToMs(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)
|
||||
await saveFingerprintData(this.bot.config.sessionPath, email, this.bot.isMobile, fingerprint)
|
||||
}
|
||||
|
||||
this.bot.log('BROWSER', `Created browser with User-Agent: "${fingerpint.fingerprint.navigator.userAgent}"`)
|
||||
this.bot.log(this.bot.isMobile, 'BROWSER', `Created browser with User-Agent: "${fingerprint.fingerprint.navigator.userAgent}"`)
|
||||
|
||||
return context
|
||||
return context as BrowserContext
|
||||
}
|
||||
|
||||
generateFingerprint() {
|
||||
async generateFingerprint() {
|
||||
const fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||
browserListQuery: 'last 2 edge version'
|
||||
browsers: [{ name: 'edge' }]
|
||||
})
|
||||
|
||||
return fingerPrintData
|
||||
const updatedFingerPrintData = await updateFingerprintUserAgent(fingerPrintData, this.bot.isMobile)
|
||||
|
||||
return updatedFingerPrintData
|
||||
}
|
||||
}
|
||||
|
||||
export default Browser
|
||||
export default Browser
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Page } from 'playwright'
|
||||
import { BrowserContext, Page } from 'rebrowser-playwright'
|
||||
import { CheerioAPI, load } from 'cheerio'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
|
||||
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
||||
import { QuizData } from './../interface/QuizData'
|
||||
import { AppUserData } from '../interface/AppUserData'
|
||||
import { EarnablePoints } from '../interface/Points'
|
||||
|
||||
|
||||
export default class BrowserFunc {
|
||||
@@ -36,19 +38,19 @@ export default class BrowserFunc {
|
||||
|
||||
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
||||
await this.bot.utils.wait(3000)
|
||||
await this.bot.browser.utils.tryDismissCookieBanner(page)
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
// Check if account is suspended
|
||||
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')
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', 'This account is suspended!', 'error')
|
||||
throw new Error('Account has been suspended!')
|
||||
}
|
||||
|
||||
try {
|
||||
// If activities are found, exit the loop
|
||||
await page.waitForSelector('#more-activities', { timeout: 1000 })
|
||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||
break
|
||||
|
||||
} catch (error) {
|
||||
@@ -64,7 +66,7 @@ export default class BrowserFunc {
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.goto(this.bot.config.baseURL)
|
||||
} else {
|
||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
||||
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||
break
|
||||
}
|
||||
|
||||
@@ -72,7 +74,7 @@ export default class BrowserFunc {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GO-HOME', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,42 +86,49 @@ export default class BrowserFunc {
|
||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||
const currentURL = new URL(this.bot.homePage.url())
|
||||
|
||||
// Should never happen since tasks are opened in a new tab!
|
||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||
this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
|
||||
await this.goHome(this.bot.homePage)
|
||||
}
|
||||
|
||||
// Reload the page to get new data
|
||||
await 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'))
|
||||
|
||||
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
|
||||
const regex = /var dashboard = (\{.*?\});/s
|
||||
const match = regex.exec(scriptContent)
|
||||
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
try {
|
||||
// Should never happen since tasks are opened in a new tab!
|
||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||
this.bot.log(this.bot.isMobile, 'DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
|
||||
await this.goHome(this.bot.homePage)
|
||||
}
|
||||
}, scriptContent)
|
||||
|
||||
if (!dashboardData) {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||
// Reload the page to get new data
|
||||
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'))
|
||||
|
||||
return targetScript?.innerText ? targetScript.innerText : null
|
||||
})
|
||||
|
||||
if (!scriptContent) {
|
||||
throw this.bot.log(this.bot.isMobile, '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
|
||||
const regex = /var dashboard = (\{.*?\});/s
|
||||
const match = regex.exec(scriptContent)
|
||||
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
}
|
||||
|
||||
}, scriptContent)
|
||||
|
||||
if (!dashboardData) {
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||
}
|
||||
|
||||
return dashboardData
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Error fetching dashboard data: ${error}`, 'error')
|
||||
}
|
||||
|
||||
return dashboardData
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,39 +145,49 @@ export default class BrowserFunc {
|
||||
* Get total earnable points with web browser
|
||||
* @returns {number} Total earnable points
|
||||
*/
|
||||
async getBrowserEarnablePoints(): Promise<number> {
|
||||
async getBrowserEarnablePoints(): Promise<EarnablePoints> {
|
||||
try {
|
||||
const data = await this.getDashboardData()
|
||||
let desktopSearchPoints = 0
|
||||
let mobileSearchPoints = 0
|
||||
let dailySetPoints = 0
|
||||
let morePromotionsPoints = 0
|
||||
|
||||
// These only include the points from tasks that the script can complete!
|
||||
let totalEarnablePoints = 0
|
||||
const data = await this.getDashboardData()
|
||||
|
||||
// Desktop Search Points
|
||||
if (data.userStatus.counters.pcSearch?.length) {
|
||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
data.userStatus.counters.pcSearch.forEach(x => desktopSearchPoints += (x.pointProgressMax - x.pointProgress))
|
||||
}
|
||||
|
||||
// Mobile Search Points
|
||||
if (data.userStatus.counters.mobileSearch?.length) {
|
||||
data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
data.userStatus.counters.mobileSearch.forEach(x => mobileSearchPoints += (x.pointProgressMax - x.pointProgress))
|
||||
}
|
||||
|
||||
// Daily Set
|
||||
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => dailySetPoints += (x.pointProgressMax - x.pointProgress))
|
||||
|
||||
// More Promotions
|
||||
if (data.morePromotions?.length) {
|
||||
data.morePromotions.forEach(x => {
|
||||
// Only count points from supported activities
|
||||
if (['quiz', 'urlreward'].includes(x.promotionType) && x.exclusiveLockedFeatureStatus !== 'locked') {
|
||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||
morePromotionsPoints += (x.pointProgressMax - x.pointProgress)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return totalEarnablePoints
|
||||
const totalEarnablePoints = desktopSearchPoints + mobileSearchPoints + dailySetPoints + morePromotionsPoints
|
||||
|
||||
return {
|
||||
dailySetPoints,
|
||||
morePromotionsPoints,
|
||||
desktopSearchPoints,
|
||||
mobileSearchPoints,
|
||||
totalEarnablePoints
|
||||
}
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,13 +195,18 @@ export default class BrowserFunc {
|
||||
* Get total earnable points with mobile app
|
||||
* @returns {number} Total earnable points
|
||||
*/
|
||||
async getAppEarnablePoints(accessToken: string): Promise<number> {
|
||||
async getAppEarnablePoints(accessToken: string) {
|
||||
try {
|
||||
const points = {
|
||||
readToEarn: 0,
|
||||
checkIn: 0,
|
||||
totalEarnablePoints: 0
|
||||
}
|
||||
|
||||
const eligibleOffers = [
|
||||
'ENUS_readarticle3_30points',
|
||||
'Gamification_Sapphire_DailyCheckIn'
|
||||
]
|
||||
let totalEarnablePoints = 0
|
||||
|
||||
const data = await this.getDashboardData()
|
||||
let geoLocale = data.userProfile.attributes.country
|
||||
@@ -198,27 +222,29 @@ export default class BrowserFunc {
|
||||
}
|
||||
}
|
||||
|
||||
const userDataResponse: AppUserData = (await axios(userDataRequest)).data
|
||||
const userDataResponse: AppUserData = (await this.bot.axios.request(userDataRequest)).data
|
||||
const userData = userDataResponse.response
|
||||
const eligibleActivities = userData.promotions.filter((x) => eligibleOffers.includes(x.attributes.offerid ?? ''))
|
||||
|
||||
for (const item of eligibleActivities) {
|
||||
if (item.attributes.type === 'msnreadearn') {
|
||||
totalEarnablePoints += parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '')
|
||||
points.readToEarn = parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '')
|
||||
break
|
||||
} else if (item.attributes.type === 'checkin') {
|
||||
const checkInDay = parseInt(item.attributes.progress ?? '') % 7
|
||||
|
||||
if (checkInDay < 6 && (new Date()).getDate() != (new Date(item.attributes.last_updated ?? '')).getDate()) {
|
||||
totalEarnablePoints += parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '')
|
||||
points.checkIn = parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return totalEarnablePoints
|
||||
points.totalEarnablePoints = points.readToEarn + points.checkIn
|
||||
|
||||
return points
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +258,7 @@ export default class BrowserFunc {
|
||||
|
||||
return data.userStatus.availablePoints
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,14 +284,14 @@ export default class BrowserFunc {
|
||||
const quizData = JSON.parse(match[1])
|
||||
return quizData
|
||||
} else {
|
||||
throw this.bot.log('GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Quiz data not found within script', 'error')
|
||||
}
|
||||
} else {
|
||||
throw this.bot.log('GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'Script containing quiz data not found', 'error')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-QUIZ-DATA', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
}
|
||||
@@ -277,7 +303,7 @@ export default class BrowserFunc {
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
this.bot.log('QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ-REFRESH', 'An error occurred:' + error, 'error')
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -293,7 +319,7 @@ export default class BrowserFunc {
|
||||
}
|
||||
}
|
||||
|
||||
async refreshCheerio(page: Page): Promise<CheerioAPI> {
|
||||
async loadInCheerio(page: Page): Promise<CheerioAPI> {
|
||||
const html = await page.content()
|
||||
const $ = load(html)
|
||||
|
||||
@@ -311,10 +337,24 @@ export default class BrowserFunc {
|
||||
selector = `a[href*="${element.attribs.href}"]`
|
||||
}
|
||||
} catch (error) {
|
||||
this.bot.log('GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'GET-PUNCHCARD-ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
return selector
|
||||
}
|
||||
|
||||
async closeBrowser(browser: BrowserContext, email: string) {
|
||||
try {
|
||||
// Save cookies
|
||||
await saveSessionData(this.bot.config.sessionPath, browser, email, this.bot.isMobile)
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
// Close browser
|
||||
await browser.close()
|
||||
this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'Browser closed cleanly!')
|
||||
} catch (error) {
|
||||
throw this.bot.log(this.bot.isMobile, 'CLOSE-BROWSER', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
import { load } from 'cheerio'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
@@ -13,66 +14,41 @@ export default class BrowserUtil {
|
||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
const buttons = [
|
||||
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||
{ selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' },
|
||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||
{ selector: '#iNext', label: 'iNext' },
|
||||
{ selector: '#iLooksGood', label: 'iLooksGood' },
|
||||
{ selector: '#idSIButton9', label: 'idSIButton9' },
|
||||
{ selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' }
|
||||
{ selector: '.ms-Button.ms-Button--primary', label: 'Primary Button' },
|
||||
{ selector: '.c-glyph.glyph-cancel', label: 'Mobile Welcome Button' },
|
||||
{ selector: '.maybe-later', label: 'Mobile Rewards App Banner' },
|
||||
{ selector: '//div[@id="cookieConsentContainer"]//button[contains(text(), "Accept")]', label: 'Accept Cookie Consent Container' },
|
||||
{ selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' },
|
||||
{ selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' }
|
||||
]
|
||||
|
||||
let result = false
|
||||
|
||||
for (const button of buttons) {
|
||||
const dismissTasks = buttons.map(async (button) => {
|
||||
try {
|
||||
const element = await page.waitForSelector(button.selector, { state: 'visible', timeout: 1000 })
|
||||
if (element) {
|
||||
await element.click()
|
||||
result = true
|
||||
const element = page.locator(button.selector)
|
||||
if (await element.first().isVisible({ timeout: 1000 })) {
|
||||
await element.first().click({ timeout: 1000 })
|
||||
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
|
||||
return true
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
continue
|
||||
// Ignore errors and continue
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async tryDismissCookieBanner(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.waitForSelector('#cookieConsentContainer', { timeout: 1000 })
|
||||
const cookieBanner = await page.$('#cookieConsentContainer')
|
||||
|
||||
if (cookieBanner) {
|
||||
const button = await cookieBanner.$('button')
|
||||
if (button) {
|
||||
await button.click()
|
||||
await this.bot.utils.wait(2000)
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
// Continue if element is not found or other error occurs
|
||||
}
|
||||
}
|
||||
|
||||
async tryDismissBingCookieBanner(page: Page): Promise<void> {
|
||||
try {
|
||||
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 })
|
||||
const cookieBanner = await page.$('#bnp_btn_accept')
|
||||
|
||||
if (cookieBanner) {
|
||||
await cookieBanner.click()
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue if element is not found or other error occurs
|
||||
}
|
||||
const results = await Promise.allSettled(dismissTasks)
|
||||
return results.some(result => result.status === 'fulfilled' && result.value === true)
|
||||
}
|
||||
|
||||
async getLatestTab(page: Page): Promise<Page> {
|
||||
try {
|
||||
await this.bot.utils.wait(500)
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
const browser = page.context()
|
||||
const pages = browser.pages()
|
||||
@@ -82,9 +58,9 @@ export default class BrowserUtil {
|
||||
return newTab
|
||||
}
|
||||
|
||||
throw this.bot.log('GET-NEW-TAB', 'Unable to get latest tab', 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-NEW-TAB', 'Unable to get latest tab', 'error')
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-NEW-TAB', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-NEW-TAB', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,19 +73,19 @@ export default class BrowserUtil {
|
||||
let homeTabURL: URL
|
||||
|
||||
if (!homeTab) {
|
||||
throw this.bot.log('GET-TABS', 'Home tab could not be found!', 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Home tab could not be found!', 'error')
|
||||
|
||||
} else {
|
||||
homeTabURL = new URL(homeTab.url())
|
||||
|
||||
if (homeTabURL.hostname !== 'rewards.bing.com') {
|
||||
throw this.bot.log('GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const workerTab = pages[2]
|
||||
if (!workerTab) {
|
||||
throw this.bot.log('GET-TABS', 'Worker tab could not be found!', 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Worker tab could not be found!', 'error')
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -118,7 +94,24 @@ export default class BrowserUtil {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-TABS', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
async reloadBadPage(page: Page): Promise<void> {
|
||||
try {
|
||||
const html = await page.content().catch(() => '')
|
||||
const $ = load(html)
|
||||
|
||||
const isNetworkError = $('body.neterror').length
|
||||
|
||||
if (isNetworkError) {
|
||||
this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'Bad page detected, reloading!')
|
||||
await page.reload()
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
throw this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"baseURL": "https://rewards.bing.com",
|
||||
"sessionPath": "sessions",
|
||||
"headless": false,
|
||||
"headless": true,
|
||||
"parallel": true,
|
||||
"runOnZeroPoints": false,
|
||||
"clusters": 1,
|
||||
"saveFingerprint": false,
|
||||
"saveFingerprint": {
|
||||
"mobile": false,
|
||||
"desktop": false
|
||||
},
|
||||
"workers": {
|
||||
"doDailySet": true,
|
||||
"doMorePromotions": true,
|
||||
@@ -14,20 +18,23 @@
|
||||
"doDailyCheckIn": true,
|
||||
"doReadToEarn": true
|
||||
},
|
||||
"globalTimeout": 30000,
|
||||
"searchOnBingLocalQueries": false,
|
||||
"globalTimeout": "30s",
|
||||
"searchSettings": {
|
||||
"useGeoLocaleQueries": true,
|
||||
"useGeoLocaleQueries": false,
|
||||
"scrollRandomResults": true,
|
||||
"clickRandomResults": true,
|
||||
"searchDelay": {
|
||||
"min": 10000,
|
||||
"max": 20000
|
||||
"min": "3min",
|
||||
"max": "5min"
|
||||
},
|
||||
"retryMobileSearch": true
|
||||
"retryMobileSearchAmount": 2
|
||||
},
|
||||
"logExcludeFunc": [
|
||||
"SEARCH-CLOSE-TABS"
|
||||
],
|
||||
"webhook": {
|
||||
"enabled": false,
|
||||
"url": ""
|
||||
},
|
||||
"cronStartTime": "0 5,11 * * *"
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
${CRON_START_TIME} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /var/log/cron.log 2>&1
|
||||
${CRON_SCHEDULE} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /proc/1/fd/1 2>> /proc/1/fd/2
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
@@ -8,10 +8,11 @@ import { Poll } from './activities/Poll'
|
||||
import { Quiz } from './activities/Quiz'
|
||||
import { ThisOrThat } from './activities/ThisOrThat'
|
||||
import { UrlReward } from './activities/UrlReward'
|
||||
import { SearchOnBing } from './activities/SearchOnBing'
|
||||
import { ReadToEarn } from './activities/ReadToEarn'
|
||||
import { DailyCheckIn } from './activities/DailyCheckIn'
|
||||
|
||||
import { DashboardData } from '../interface/DashboardData'
|
||||
import { DashboardData, MorePromotion, PromotionalItem } from '../interface/DashboardData'
|
||||
|
||||
|
||||
export default class Activities {
|
||||
@@ -51,6 +52,11 @@ export default class Activities {
|
||||
await urlReward.doUrlReward(page)
|
||||
}
|
||||
|
||||
doSearchOnBing = async (page: Page, activity: MorePromotion | PromotionalItem): Promise<void> => {
|
||||
const searchOnBing = new SearchOnBing(this.bot)
|
||||
await searchOnBing.doSearchOnBing(page, activity)
|
||||
}
|
||||
|
||||
doReadToEarn = async (accessToken: string, data: DashboardData): Promise<void> => {
|
||||
const readToEarn = new ReadToEarn(this.bot)
|
||||
await readToEarn.doReadToEarn(accessToken, data)
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
import readline from 'readline'
|
||||
import * as crypto from 'crypto'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
import axios from 'axios'
|
||||
|
||||
import { OAuth } from '../interface/OAuth'
|
||||
import * as crypto from 'crypto'
|
||||
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
|
||||
export class Login {
|
||||
private bot: MicrosoftRewardsBot
|
||||
private clientId: string = '0000000040170455'
|
||||
@@ -31,20 +32,23 @@ export class Login {
|
||||
// Navigate to the Bing login page
|
||||
await page.goto('https://rewards.bing.com/signin')
|
||||
|
||||
await page.waitForLoadState('domcontentloaded').catch(() => { })
|
||||
|
||||
await this.bot.browser.utils.reloadBadPage(page)
|
||||
|
||||
// Check if account is locked
|
||||
await this.checkAccountLocked(page)
|
||||
|
||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
|
||||
|
||||
if (!isLoggedIn) {
|
||||
// Check if account is locked
|
||||
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 this.execLogin(page, email, password)
|
||||
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Logged into Microsoft successfully')
|
||||
} else {
|
||||
this.bot.log('LOGIN', 'Already logged in')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Already logged in')
|
||||
|
||||
// Check if account is locked
|
||||
await this.checkAccountLocked(page)
|
||||
}
|
||||
|
||||
// Check if logged in to bing
|
||||
@@ -54,28 +58,43 @@ export class Login {
|
||||
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
||||
|
||||
// We're done logging in
|
||||
this.bot.log('LOGIN', 'Logged in successfully')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Logged in successfully, saved login session!')
|
||||
|
||||
} catch (error) {
|
||||
// Throw and don't continue
|
||||
throw this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async execLogin(page: Page, email: string, password: string) {
|
||||
try {
|
||||
await this.enterEmail(page, email)
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.bot.browser.utils.reloadBadPage(page)
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.enterPassword(page, password)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
// Check if account is locked
|
||||
await this.checkAccountLocked(page)
|
||||
|
||||
await this.bot.browser.utils.reloadBadPage(page)
|
||||
await this.checkLoggedIn(page)
|
||||
} catch (error: any) {
|
||||
this.bot.log('LOGIN', 'An error occurred: ' + error.message, 'error')
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred: ' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async enterEmail(page: Page, email: string) {
|
||||
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 2_000 }).catch(() => null)
|
||||
if (emailPrefilled) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
|
||||
return
|
||||
}
|
||||
|
||||
await page.fill('#i0116', email)
|
||||
await page.click('#idSIButton9')
|
||||
this.bot.log('LOGIN', 'Email entered successfully')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
|
||||
}
|
||||
|
||||
private async enterPassword(page: Page, password: string) {
|
||||
@@ -84,9 +103,9 @@ export class Login {
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.fill('#i0118', password)
|
||||
await page.click('#idSIButton9')
|
||||
this.bot.log('LOGIN', 'Password entered successfully')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
|
||||
} catch {
|
||||
this.bot.log('LOGIN', 'Password entry failed or 2FA required')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entry failed or 2FA required')
|
||||
await this.handle2FA(page)
|
||||
}
|
||||
}
|
||||
@@ -101,8 +120,8 @@ export class Login {
|
||||
// SMS verification
|
||||
await this.authSMSVerification(page)
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.bot.log('LOGIN', `2FA handling failed: ${error.message}`)
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `2FA handling failed: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +130,25 @@ export class Login {
|
||||
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
|
||||
return await element.textContent()
|
||||
} catch {
|
||||
await page.click('button[aria-describedby="confirmSendTitle"]')
|
||||
if (this.bot.config.parallel) {
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Script running in parallel, can only send 1 2FA request per account at a time!', 'log', 'yellow')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Trying again in 60 seconds! Please wait...', 'log', 'yellow')
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const button = await page.waitForSelector('button[aria-describedby="pushNotificationsTitle errorDescription"]', { state: 'visible', timeout: 2000 }).catch(() => null)
|
||||
if (button) {
|
||||
await this.bot.utils.wait(60_000)
|
||||
await button.click()
|
||||
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await page.click('button[aria-describedby="confirmSendTitle"]').catch(() => {})
|
||||
await this.bot.utils.wait(2000)
|
||||
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
|
||||
return await element.textContent()
|
||||
@@ -122,15 +159,15 @@ export class Login {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
try {
|
||||
this.bot.log('LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`)
|
||||
this.bot.log('LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`)
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds')
|
||||
|
||||
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60000 })
|
||||
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60_000 })
|
||||
|
||||
this.bot.log('LOGIN', 'Login successfully approved!')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!')
|
||||
break
|
||||
} catch {
|
||||
this.bot.log('LOGIN', 'The code is expired. Trying to get a new code...')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'The code is expired. Trying to get a new code...')
|
||||
await page.click('button[aria-describedby="pushNotificationsTitle errorDescription"]')
|
||||
numberToPress = await this.get2FACode(page)
|
||||
}
|
||||
@@ -138,7 +175,7 @@ export class Login {
|
||||
}
|
||||
|
||||
private async authSMSVerification(page: Page) {
|
||||
this.bot.log('LOGIN', 'SMS 2FA code required. Waiting for user input...')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'SMS 2FA code required. Waiting for user input...')
|
||||
|
||||
const code = await new Promise<string>((resolve) => {
|
||||
rl.question('Enter 2FA code:\n', (input) => {
|
||||
@@ -149,7 +186,7 @@ export class Login {
|
||||
|
||||
await page.fill('input[name="otc"]', code)
|
||||
await page.keyboard.press('Enter')
|
||||
this.bot.log('LOGIN', '2FA code entered successfully')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', '2FA code entered successfully')
|
||||
}
|
||||
|
||||
private async checkLoggedIn(page: Page) {
|
||||
@@ -167,12 +204,12 @@ export class Login {
|
||||
|
||||
// Wait for login to complete
|
||||
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
|
||||
this.bot.log('LOGIN', 'Successfully logged into the rewards portal')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal')
|
||||
}
|
||||
|
||||
private async checkBingLogin(page: Page): Promise<void> {
|
||||
try {
|
||||
this.bot.log('LOGIN-BING', 'Verifying Bing login')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Verifying Bing login')
|
||||
await page.goto('https://www.bing.com/fd/auth/signin?action=interactive&provider=windows_live_id&return_url=https%3A%2F%2Fwww.bing.com%2F')
|
||||
|
||||
const maxIterations = 5
|
||||
@@ -181,12 +218,12 @@ export class Login {
|
||||
const currentUrl = new URL(page.url())
|
||||
|
||||
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
|
||||
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
const loggedIn = await this.checkBingLoginStatus(page)
|
||||
// If mobile browser, skip this step
|
||||
if (loggedIn || this.bot.isMobile) {
|
||||
this.bot.log('LOGIN-BING', 'Bing login verification passed!')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing login verification passed!')
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -195,7 +232,7 @@ export class Login {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('LOGIN-BING', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,25 +258,28 @@ export class Login {
|
||||
|
||||
await page.goto(authorizeUrl.href)
|
||||
|
||||
const currentUrl = new URL(page.url())
|
||||
let currentUrl = new URL(page.url())
|
||||
let code: string
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'Waiting for authorization...')
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
this.bot.log('LOGIN-APP', 'Waiting for authorization')
|
||||
if (currentUrl.hostname === 'login.live.com' && currentUrl.pathname === '/oauth20_desktop.srf') {
|
||||
code = currentUrl.searchParams.get('code')!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
currentUrl = new URL(page.url())
|
||||
await this.bot.utils.wait(5000)
|
||||
}
|
||||
|
||||
const body = new URLSearchParams()
|
||||
body.append('grant_type', 'authorization_code')
|
||||
body.append('client_id', this.clientId)
|
||||
body.append('code', code)
|
||||
body.append('redirect_uri', this.redirectUrl)
|
||||
|
||||
const tokenRequest = {
|
||||
const tokenRequest: AxiosRequestConfig = {
|
||||
url: this.tokenUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -248,10 +288,18 @@ export class Login {
|
||||
data: body.toString()
|
||||
}
|
||||
|
||||
const tokenResponse = await axios(tokenRequest)
|
||||
const tokenResponse = await this.bot.axios.request(tokenRequest)
|
||||
const tokenData: OAuth = await tokenResponse.data
|
||||
|
||||
this.bot.log('LOGIN-APP', 'Successfully authorized')
|
||||
this.bot.log(this.bot.isMobile, 'LOGIN-APP', 'Successfully authorized')
|
||||
return tokenData.access_token
|
||||
}
|
||||
|
||||
private async checkAccountLocked(page: Page) {
|
||||
await this.bot.utils.wait(2000)
|
||||
const isLocked = await page.waitForSelector('#serviceAbuseLandingTitle', { state: 'visible', timeout: 1000 }).then(() => true).catch(() => false)
|
||||
if (isLocked) {
|
||||
throw this.bot.log(this.bot.isMobile, 'CHECK-LOCKED', 'This account has been locked! Remove the account from "accounts.json" and restart!', 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||
|
||||
@@ -18,12 +18,12 @@ export class Workers {
|
||||
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
||||
|
||||
if (!activitiesUncompleted.length) {
|
||||
this.bot.log('DAILY-SET', 'All Daily Set" items have already been completed')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All Daily Set" items have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
// Solve Activities
|
||||
this.bot.log('DAILY-SET', 'Started solving "Daily Set" items')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'Started solving "Daily Set" items')
|
||||
|
||||
await this.solveActivities(page, activitiesUncompleted)
|
||||
|
||||
@@ -32,27 +32,34 @@ export class Workers {
|
||||
// Always return to the homepage if not already
|
||||
await this.bot.browser.func.goHome(page)
|
||||
|
||||
this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have been completed')
|
||||
}
|
||||
|
||||
// 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 && !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
|
||||
|
||||
if (!punchCardsUncompleted.length) {
|
||||
this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
|
||||
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Cards" have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
for (const punchCard of punchCardsUncompleted) {
|
||||
|
||||
// Ensure parentPromotion exists before proceeding
|
||||
if (!punchCard.parentPromotion?.title) {
|
||||
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Skipped punchcard "${punchCard.name}" | Reason: Parent promotion is missing!`, 'warn')
|
||||
continue
|
||||
}
|
||||
|
||||
// Get latest page for each card
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
|
||||
|
||||
// Solve Activities
|
||||
this.bot.log('PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
|
||||
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `Started solving "Punch Card" items for punchcard: "${punchCard.parentPromotion.title}"`)
|
||||
|
||||
// Got to punch card index page in a new tab
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||
@@ -72,10 +79,10 @@ export class Workers {
|
||||
await this.bot.browser.func.goHome(page)
|
||||
}
|
||||
|
||||
this.bot.log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
||||
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
||||
}
|
||||
|
||||
this.bot.log('PUNCH-CARD', 'All "Punch Card" items have been completed')
|
||||
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Card" items have been completed')
|
||||
}
|
||||
|
||||
// More Promotions
|
||||
@@ -90,12 +97,12 @@ export class Workers {
|
||||
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0 && x.exclusiveLockedFeatureStatus !== 'locked') ?? []
|
||||
|
||||
if (!activitiesUncompleted.length) {
|
||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
||||
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
// Solve Activities
|
||||
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" items')
|
||||
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'Started solving "More Promotions" items')
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -106,7 +113,7 @@ export class Workers {
|
||||
// Always return to the homepage if not already
|
||||
await this.bot.browser.func.goHome(page)
|
||||
|
||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||
}
|
||||
|
||||
// Solve all the different types of activities
|
||||
@@ -132,28 +139,22 @@ export class Workers {
|
||||
}
|
||||
|
||||
|
||||
let selector = `[data-bi-id^="${activity.offerId}"]`
|
||||
let selector = `[data-bi-id^="${activity.offerId}"] .pointLink:not(.contentContainer .pointLink)`
|
||||
|
||||
if (punchCard) {
|
||||
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
||||
|
||||
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
||||
selector = `[data-bi-id^="${activity.name}"]`
|
||||
} else if (activity.name.toLowerCase().includes('membercenter') || activity.name.toLowerCase().includes('exploreonbing')) {
|
||||
selector = `[data-bi-id^="${activity.name}"] .pointLink:not(.contentContainer .pointLink)`
|
||||
}
|
||||
|
||||
// Click element, it will be opened in a new tab
|
||||
await activityPage.click(selector)
|
||||
|
||||
// Select the new activity page
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
|
||||
// Wait for the new tab to fully load, ignore error.
|
||||
/*
|
||||
Due to common false timeout on this function, we're ignoring the error regardless, if it worked then it's faster,
|
||||
if it didn't then it gave enough time for the page to load.
|
||||
*/
|
||||
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
|
||||
await this.bot.utils.wait(5000)
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
switch (activity.promotionType) {
|
||||
// Quiz (Poll, Quiz or ABC)
|
||||
@@ -163,23 +164,31 @@ export class Workers {
|
||||
case 10:
|
||||
// Normal poll
|
||||
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Poll" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doPoll(activityPage)
|
||||
} else { // ABC
|
||||
this.bot.log('ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ABC" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doABC(activityPage)
|
||||
}
|
||||
break
|
||||
|
||||
// This Or That Quiz (Usually 50 points)
|
||||
case 50:
|
||||
this.bot.log('ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "ThisOrThat" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doThisOrThat(activityPage)
|
||||
break
|
||||
|
||||
// Quizzes are usually 30-40 points
|
||||
default:
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "Quiz" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doQuiz(activityPage)
|
||||
break
|
||||
}
|
||||
@@ -187,14 +196,24 @@ export class Workers {
|
||||
|
||||
// UrlReward (Visit)
|
||||
case 'urlreward':
|
||||
this.bot.log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
|
||||
await this.bot.activities.doUrlReward(activityPage)
|
||||
// Search on Bing are subtypes of "urlreward"
|
||||
if (activity.name.toLowerCase().includes('exploreonbing')) {
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "SearchOnBing" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doSearchOnBing(activityPage, activity)
|
||||
|
||||
} else {
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
|
||||
await activityPage.click(selector)
|
||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||
await this.bot.activities.doUrlReward(activityPage)
|
||||
}
|
||||
break
|
||||
|
||||
// Misc, Usually UrlReward Type
|
||||
// Unsupported types
|
||||
default:
|
||||
this.bot.log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
|
||||
await this.bot.activities.doUrlReward(activityPage)
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Skipped activity "${activity.title}" | Reason: Unsupported type: "${activity.promotionType}"!`, 'warn')
|
||||
break
|
||||
}
|
||||
|
||||
@@ -202,7 +221,7 @@ export class Workers {
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'ACTIVITY', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -6,10 +6,10 @@ import { Workers } from '../Workers'
|
||||
export class ABC extends Workers {
|
||||
|
||||
async doABC(page: Page) {
|
||||
this.bot.log('ABC', 'Trying to complete poll')
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'Trying to complete poll')
|
||||
|
||||
try {
|
||||
let $ = await this.bot.browser.func.refreshCheerio(page)
|
||||
let $ = await this.bot.browser.func.loadInCheerio(page)
|
||||
|
||||
// Don't loop more than 15 in case unable to solve, would lock otherwise
|
||||
const maxIterations = 15
|
||||
@@ -30,7 +30,7 @@ export class ABC extends Workers {
|
||||
await page.click('div.wk_button') // Click next question button
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
$ = await this.bot.browser.func.refreshCheerio(page)
|
||||
$ = await this.bot.browser.func.loadInCheerio(page)
|
||||
await this.bot.utils.wait(1000)
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ export class ABC extends Workers {
|
||||
await page.close()
|
||||
|
||||
if (i === maxIterations) {
|
||||
this.bot.log('ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'Failed to solve quiz, exceeded max iterations of 15', 'warn')
|
||||
} else {
|
||||
this.bot.log('ABC', 'Completed the ABC successfully')
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'Completed the ABC successfully')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log('ABC', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'ABC', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DashboardData } from '../../interface/DashboardData'
|
||||
|
||||
export class DailyCheckIn extends Workers {
|
||||
public async doDailyCheckIn(accessToken: string, data: DashboardData) {
|
||||
this.bot.log('DAILY-CHECK-IN', 'Starting Daily Check In')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'Starting Daily Check In')
|
||||
|
||||
try {
|
||||
let geoLocale = data.userProfile.attributes.country
|
||||
@@ -24,7 +24,7 @@ export class DailyCheckIn extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
const claimRequest = {
|
||||
const claimRequest: AxiosRequestConfig = {
|
||||
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -36,12 +36,12 @@ export class DailyCheckIn extends Workers {
|
||||
data: JSON.stringify(jsonData)
|
||||
}
|
||||
|
||||
const claimResponse = await axios(claimRequest)
|
||||
const claimedPoint = parseInt((await claimResponse.data).response.activity.p)
|
||||
const claimResponse = await this.bot.axios.request(claimRequest)
|
||||
const claimedPoint = parseInt((await claimResponse.data).response?.activity?.p) ?? 0
|
||||
|
||||
this.bot.log('DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today')
|
||||
} catch (error) {
|
||||
this.bot.log('DAILY-CHECK-IN', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Workers } from '../Workers'
|
||||
export class Poll extends Workers {
|
||||
|
||||
async doPoll(page: Page) {
|
||||
this.bot.log('POLL', 'Trying to complete poll')
|
||||
this.bot.log(this.bot.isMobile, 'POLL', 'Trying to complete poll')
|
||||
|
||||
try {
|
||||
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||
@@ -19,10 +19,10 @@ export class Poll extends Workers {
|
||||
await this.bot.utils.wait(4000)
|
||||
await page.close()
|
||||
|
||||
this.bot.log('POLL', 'Completed the poll successfully')
|
||||
this.bot.log(this.bot.isMobile, 'POLL', 'Completed the poll successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log('POLL', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'POLL', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Workers } from '../Workers'
|
||||
export class Quiz extends Workers {
|
||||
|
||||
async doQuiz(page: Page) {
|
||||
this.bot.log('QUIZ', 'Trying to complete quiz')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Trying to complete quiz')
|
||||
|
||||
try {
|
||||
// Check if the quiz has been started or not
|
||||
@@ -14,7 +14,7 @@ export class Quiz extends Workers {
|
||||
if (quizNotStarted) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
this.bot.log('QUIZ', 'Quiz has already been started, trying to finish it')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Quiz has already been started, trying to finish it')
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
@@ -47,7 +47,7 @@ export class Quiz extends Workers {
|
||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||
if (!refreshSuccess) {
|
||||
await page.close()
|
||||
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,7 @@ export class Quiz extends Workers {
|
||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||
if (!refreshSuccess) {
|
||||
await page.close()
|
||||
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -81,10 +81,11 @@ export class Quiz extends Workers {
|
||||
// Done with
|
||||
await this.bot.utils.wait(2000)
|
||||
await page.close()
|
||||
this.bot.log('QUIZ', 'Completed the quiz successfully')
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'Completed the quiz successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log('QUIZ', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from 'axios'
|
||||
import { randomBytes } from 'crypto'
|
||||
import { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -8,13 +8,13 @@ import { DashboardData } from '../../interface/DashboardData'
|
||||
|
||||
export class ReadToEarn extends Workers {
|
||||
public async doReadToEarn(accessToken: string, data: DashboardData) {
|
||||
this.bot.log('READ-TO-EARN', 'Starting Read to Earn')
|
||||
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Starting Read to Earn')
|
||||
|
||||
try {
|
||||
let geoLocale = data.userProfile.attributes.country
|
||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us'
|
||||
|
||||
const userDataRequest = {
|
||||
const userDataRequest: AxiosRequestConfig = {
|
||||
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
@@ -23,7 +23,7 @@ export class ReadToEarn extends Workers {
|
||||
'X-Rewards-Language': 'en'
|
||||
}
|
||||
}
|
||||
const userDataResponse = await axios(userDataRequest)
|
||||
const userDataResponse = await this.bot.axios.request(userDataRequest)
|
||||
const userData = (await userDataResponse.data).response
|
||||
let userBalance = userData.balance
|
||||
|
||||
@@ -52,22 +52,22 @@ export class ReadToEarn extends Workers {
|
||||
data: JSON.stringify(jsonData)
|
||||
}
|
||||
|
||||
const claimResponse = await axios(claimRequest)
|
||||
const claimResponse = await this.bot.axios.request(claimRequest)
|
||||
const newBalance = (await claimResponse.data).response.balance
|
||||
|
||||
if (newBalance == userBalance) {
|
||||
this.bot.log('READ-TO-EARN', 'Read all available articles')
|
||||
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Read all available articles')
|
||||
break
|
||||
} else {
|
||||
this.bot.log('READ-TO-EARN', `Read article ${i + 1} of ${articleCount} max | Gained ${newBalance - userBalance} Points`)
|
||||
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', `Read article ${i + 1} of ${articleCount} max | Gained ${newBalance - userBalance} Points`)
|
||||
userBalance = newBalance
|
||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
|
||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max))))
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('READ-TO-EARN', 'Completed Read to Earn')
|
||||
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Completed Read to Earn')
|
||||
} catch (error) {
|
||||
this.bot.log('READ-TO-EARN', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import axios from 'axios'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
import { platform } from 'os'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
@@ -14,7 +13,7 @@ export class Search extends Workers {
|
||||
private searchPageURL = ''
|
||||
|
||||
public async doSearch(page: Page, data: DashboardData) {
|
||||
this.bot.log('SEARCH-BING', 'Starting Bing searches')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Starting Bing searches')
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -22,7 +21,7 @@ export class Search extends Workers {
|
||||
let missingPoints = this.calculatePoints(searchCounters)
|
||||
|
||||
if (missingPoints === 0) {
|
||||
this.bot.log('SEARCH-BING', `Bing searches for ${this.bot.isMobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Bing searches have already been completed')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,19 +35,21 @@ export class Search extends Workers {
|
||||
// Go to bing
|
||||
await page.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
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[] = []
|
||||
// Mobile search doesn't seem to like related queries?
|
||||
googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : queries.push(x.topic, ...x.related) })
|
||||
|
||||
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
|
||||
|
||||
// Loop over Google search queries
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i] as string
|
||||
|
||||
this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${this.bot.isMobile}`)
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query}`)
|
||||
|
||||
searchCounters = await this.bingSearch(page, query)
|
||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||
@@ -67,14 +68,14 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
// 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')
|
||||
if (maxLoop > 5 && this.bot.isMobile) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 5 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')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search didn\'t gain point for 10 iterations aborting searches', 'warn')
|
||||
maxLoop = 0 // Reset to 0 so we can retry with related searches below
|
||||
break
|
||||
}
|
||||
@@ -87,7 +88,7 @@ export class Search extends Workers {
|
||||
|
||||
// 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`)
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Search completed but we're missing ${missingPoints} points, generating extra searches`)
|
||||
|
||||
let i = 0
|
||||
while (missingPoints > 0) {
|
||||
@@ -98,7 +99,7 @@ export class Search extends Workers {
|
||||
if (relatedTerms.length > 3) {
|
||||
// Search for the first 2 related terms
|
||||
for (const term of relatedTerms.slice(1, 3)) {
|
||||
this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${this.bot.isMobile}`)
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term}`)
|
||||
|
||||
searchCounters = await this.bingSearch(page, term)
|
||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||
@@ -119,7 +120,7 @@ export class Search extends Workers {
|
||||
|
||||
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
|
||||
if (maxLoop > 5) {
|
||||
this.bot.log('SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', 'Search didn\'t gain point for 5 iterations aborting searches', 'warn')
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -127,7 +128,7 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('SEARCH-BING', 'Completed searches')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Completed searches')
|
||||
}
|
||||
|
||||
private async bingSearch(searchPage: Page, query: string) {
|
||||
@@ -157,12 +158,14 @@ export class Search extends Workers {
|
||||
await searchPage.keyboard.type(query)
|
||||
await searchPage.keyboard.press('Enter')
|
||||
|
||||
await this.bot.utils.wait(1000)
|
||||
await this.bot.utils.wait(3000)
|
||||
|
||||
// Bing.com in Chrome opens a new tab when searching
|
||||
const resultPage = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||
this.searchPageURL = new URL(resultPage.url()).href // Set the results page
|
||||
|
||||
await this.bot.browser.utils.reloadBadPage(resultPage)
|
||||
|
||||
if (this.bot.config.searchSettings.scrollRandomResults) {
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.randomScroll(resultPage)
|
||||
@@ -174,19 +177,19 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
// 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)))
|
||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max))))
|
||||
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
|
||||
} catch (error) {
|
||||
if (i === 5) {
|
||||
this.bot.log('SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Failed after 5 retries... An error occurred:' + error, 'error')
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
|
||||
|
||||
// Reset the tabs
|
||||
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||
@@ -196,7 +199,7 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Search failed after 5 retries, ending', 'error')
|
||||
return await this.bot.browser.func.getSearchPoints()
|
||||
}
|
||||
|
||||
@@ -206,7 +209,7 @@ export class Search extends Workers {
|
||||
|
||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
||||
|
||||
this.bot.log('SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
||||
|
||||
while (queryCount > queryTerms.length) {
|
||||
i += 1
|
||||
@@ -223,7 +226,7 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios(request)
|
||||
const response = await this.bot.axios.request(request)
|
||||
|
||||
const data: GoogleTrends = JSON.parse((await response.data).slice(5))
|
||||
|
||||
@@ -235,7 +238,8 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +256,13 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios(request)
|
||||
const response = await this.bot.axios.request(request)
|
||||
|
||||
return response.data[1] as string[]
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-BING-RELATED', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-BING-RELATED', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -280,7 +285,7 @@ export class Search extends Workers {
|
||||
}, randomScrollPosition)
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +293,9 @@ export class Search extends Workers {
|
||||
try {
|
||||
await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { }) // Since we don't really care if it did it or not
|
||||
|
||||
// Only used if the browser is not the edge browser (continue on Edge popup)
|
||||
await this.closeContinuePopup(page)
|
||||
|
||||
// Stay for 10 seconds for page to load and "visit"
|
||||
await this.bot.utils.wait(10_000)
|
||||
|
||||
@@ -309,7 +317,7 @@ export class Search extends Workers {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,23 +325,35 @@ export class Search extends Workers {
|
||||
const browser = lastTab.context()
|
||||
const tabs = browser.pages()
|
||||
|
||||
// If more than 2 tabs are open, close the last tab
|
||||
if (tabs.length > 2) {
|
||||
await lastTab.close()
|
||||
try {
|
||||
if (tabs.length > 2) {
|
||||
// If more than 2 tabs are open, close the last tab
|
||||
|
||||
// If only 1 tab is open, open a new one to search in
|
||||
} else if (tabs.length === 1) {
|
||||
const newPage = await browser.newPage()
|
||||
await this.bot.utils.wait(1000)
|
||||
await newPage.goto(this.bingHome)
|
||||
await this.bot.utils.wait(3000)
|
||||
this.searchPageURL = newPage.url()
|
||||
await lastTab.close()
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', `More than 2 were open, closed the last tab: "${new URL(lastTab.url()).host}"`)
|
||||
|
||||
// Else reset the last tab back to the search listing or Bing.com
|
||||
} else {
|
||||
lastTab = await this.bot.browser.utils.getLatestTab(lastTab)
|
||||
await lastTab.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||
} else if (tabs.length === 1) {
|
||||
// If only 1 tab is open, open a new one to search in
|
||||
|
||||
const newPage = await browser.newPage()
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
await newPage.goto(this.bingHome)
|
||||
await this.bot.utils.wait(3000)
|
||||
this.searchPageURL = newPage.url()
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'There was only 1 tab open, crated a new one')
|
||||
} else {
|
||||
// Else reset the last tab back to the search listing or Bing.com
|
||||
|
||||
lastTab = await this.bot.browser.utils.getLatestTab(lastTab)
|
||||
await lastTab.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private calculatePoints(counters: Counters) {
|
||||
@@ -348,4 +368,18 @@ export class Search extends Workers {
|
||||
|
||||
return missingPoints
|
||||
}
|
||||
}
|
||||
|
||||
private async closeContinuePopup(page: Page) {
|
||||
try {
|
||||
await page.waitForSelector('#sacs_close', { timeout: 1000 })
|
||||
const continueButton = await page.$('#sacs_close')
|
||||
|
||||
if (continueButton) {
|
||||
await continueButton.click()
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue if element is not found or other error occurs
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
75
src/functions/activities/SearchOnBing.ts
Normal file
75
src/functions/activities/SearchOnBing.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
import * as fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
|
||||
|
||||
|
||||
export class SearchOnBing extends Workers {
|
||||
|
||||
async doSearchOnBing(page: Page, activity: MorePromotion | PromotionalItem) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Trying to complete SearchOnBing')
|
||||
|
||||
try {
|
||||
await this.bot.utils.wait(5000)
|
||||
|
||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||
|
||||
const query = await this.getSearchQuery(activity.title)
|
||||
|
||||
const searchBar = '#sb_form_q'
|
||||
await page.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||
await page.click(searchBar)
|
||||
await this.bot.utils.wait(500)
|
||||
await page.keyboard.type(query)
|
||||
await page.keyboard.press('Enter')
|
||||
await this.bot.utils.wait(3000)
|
||||
|
||||
await page.close()
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Completed the SearchOnBing successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async getSearchQuery(title: string): Promise<string> {
|
||||
interface Queries {
|
||||
title: string;
|
||||
queries: string[]
|
||||
}
|
||||
|
||||
let queries: Queries[] = []
|
||||
|
||||
try {
|
||||
if (this.bot.config.searchOnBingLocalQueries) {
|
||||
const data = fs.readFileSync(path.join(__dirname, '../queries.json'), 'utf8')
|
||||
queries = JSON.parse(data)
|
||||
} else {
|
||||
// Fetch from the repo directly so the user doesn't need to redownload the script for the new activities
|
||||
const response = await this.bot.axios.request({
|
||||
method: 'GET',
|
||||
url: 'https://raw.githubusercontent.com/TheNetsky/Microsoft-Rewards-Script/refs/heads/main/src/functions/queries.json'
|
||||
})
|
||||
queries = response.data
|
||||
}
|
||||
|
||||
const answers = queries.find(x => this.normalizeString(x.title) === this.normalizeString(title))
|
||||
const answer = answers ? this.bot.utils.shuffleArray(answers?.queries)[0] as string : title
|
||||
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', `Fetched answer: ${answer} | question: ${title}`)
|
||||
return answer
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', 'An error occurred:' + error, 'error')
|
||||
return title
|
||||
}
|
||||
}
|
||||
|
||||
private normalizeString(string: string): string {
|
||||
return string.normalize('NFD').trim().toLowerCase().replace(/[^\x20-\x7E]/g, '').replace(/[?!]/g, '')
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Workers } from '../Workers'
|
||||
export class ThisOrThat extends Workers {
|
||||
|
||||
async doThisOrThat(page: Page) {
|
||||
this.bot.log('THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
||||
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Trying to complete ThisOrThat')
|
||||
|
||||
|
||||
try {
|
||||
@@ -15,7 +15,7 @@ export class ThisOrThat extends Workers {
|
||||
if (quizNotStarted) {
|
||||
await page.click('#rqStartQuiz')
|
||||
} else {
|
||||
this.bot.log('THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'ThisOrThat has already been started, trying to finish it')
|
||||
}
|
||||
|
||||
await this.bot.utils.wait(2000)
|
||||
@@ -32,15 +32,15 @@ export class ThisOrThat extends Workers {
|
||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||
if (!refreshSuccess) {
|
||||
await page.close()
|
||||
this.bot.log('QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
||||
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log('THIS-OR-THAT', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
@@ -6,17 +6,17 @@ import { Workers } from '../Workers'
|
||||
export class UrlReward extends Workers {
|
||||
|
||||
async doUrlReward(page: Page) {
|
||||
this.bot.log('URL-REWARD', 'Trying to complete UrlReward')
|
||||
this.bot.log(this.bot.isMobile, 'URL-REWARD', 'Trying to complete UrlReward')
|
||||
|
||||
try {
|
||||
this.bot.utils.wait(2000)
|
||||
|
||||
await page.close()
|
||||
|
||||
this.bot.log('URL-REWARD', 'Completed the UrlReward successfully')
|
||||
this.bot.log(this.bot.isMobile, 'URL-REWARD', 'Completed the UrlReward successfully')
|
||||
} catch (error) {
|
||||
await page.close()
|
||||
this.bot.log('URL-REWARD', 'An error occurred:' + error, 'error')
|
||||
this.bot.log(this.bot.isMobile, 'URL-REWARD', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
187
src/index.ts
187
src/index.ts
@@ -1,5 +1,5 @@
|
||||
import cluster from 'cluster'
|
||||
import { BrowserContext, Page } from 'playwright'
|
||||
import { Page } from 'rebrowser-playwright'
|
||||
|
||||
import Browser from './browser/Browser'
|
||||
import BrowserFunc from './browser/BrowserFunc'
|
||||
@@ -14,6 +14,8 @@ import { Workers } from './functions/Workers'
|
||||
import Activities from './functions/Activities'
|
||||
|
||||
import { Account } from './interface/Account'
|
||||
import Axios from './util/Axios'
|
||||
|
||||
|
||||
// Main bot class
|
||||
export class MicrosoftRewardsBot {
|
||||
@@ -25,18 +27,25 @@ export class MicrosoftRewardsBot {
|
||||
func: BrowserFunc,
|
||||
utils: BrowserUtil
|
||||
}
|
||||
public isMobile: boolean = false
|
||||
public isMobile: boolean
|
||||
public homePage!: Page
|
||||
|
||||
private collectedPoints: number = 0
|
||||
private pointsCanCollect: number = 0
|
||||
private pointsInitial: number = 0
|
||||
|
||||
private activeWorkers: number
|
||||
private mobileRetryAttempts: number
|
||||
private browserFactory: Browser = new Browser(this)
|
||||
private accounts: Account[]
|
||||
private workers: Workers
|
||||
private login = new Login(this)
|
||||
private accessToken: string = ''
|
||||
|
||||
constructor() {
|
||||
//@ts-expect-error Will be initialized later
|
||||
public axios: Axios
|
||||
|
||||
constructor(isMobile: boolean) {
|
||||
this.isMobile = isMobile
|
||||
this.log = log
|
||||
|
||||
this.accounts = []
|
||||
@@ -48,6 +57,7 @@ export class MicrosoftRewardsBot {
|
||||
}
|
||||
this.config = loadConfig()
|
||||
this.activeWorkers = this.config.clusters
|
||||
this.mobileRetryAttempts = 0
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
@@ -55,7 +65,7 @@ export class MicrosoftRewardsBot {
|
||||
}
|
||||
|
||||
async run() {
|
||||
log('MAIN', `Bot started with ${this.config.clusters} clusters`)
|
||||
log('main', 'MAIN', `Bot started with ${this.config.clusters} clusters`)
|
||||
|
||||
// Only cluster when there's more than 1 cluster demanded
|
||||
if (this.config.clusters > 1) {
|
||||
@@ -65,12 +75,12 @@ export class MicrosoftRewardsBot {
|
||||
this.runWorker()
|
||||
}
|
||||
} else {
|
||||
this.runTasks(this.accounts)
|
||||
await this.runTasks(this.accounts)
|
||||
}
|
||||
}
|
||||
|
||||
private runMaster() {
|
||||
log('MAIN-PRIMARY', 'Primary process started')
|
||||
log('main', 'MAIN-PRIMARY', 'Primary process started')
|
||||
|
||||
const accountChunks = this.utils.chunkArray(this.accounts, this.config.clusters)
|
||||
|
||||
@@ -83,18 +93,18 @@ export class MicrosoftRewardsBot {
|
||||
cluster.on('exit', (worker, code) => {
|
||||
this.activeWorkers -= 1
|
||||
|
||||
log('MAIN-WORKER', `Worker ${worker.process.pid} destroyed | Code: ${code} | Active workers: ${this.activeWorkers}`, 'warn')
|
||||
log('main', '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')
|
||||
log('main', 'MAIN-WORKER', 'All workers destroyed. Exiting main process!', 'warn')
|
||||
process.exit(0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private runWorker() {
|
||||
log('MAIN-WORKER', `Worker ${process.pid} spawned`)
|
||||
log('main', 'MAIN-WORKER', `Worker ${process.pid} spawned`)
|
||||
// Receive the chunk of accounts from the master
|
||||
process.on('message', async ({ chunk }) => {
|
||||
await this.runTasks(chunk)
|
||||
@@ -103,58 +113,68 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
private async runTasks(accounts: Account[]) {
|
||||
for (const account of accounts) {
|
||||
log('MAIN-WORKER', `Started tasks for account ${account.email}`)
|
||||
log('main', 'MAIN-WORKER', `Started tasks for account ${account.email}`)
|
||||
|
||||
// Desktop Searches, DailySet and More Promotions
|
||||
await this.Desktop(account)
|
||||
this.axios = new Axios(account.proxy)
|
||||
if (this.config.parallel) {
|
||||
await Promise.all([
|
||||
this.Desktop(account),
|
||||
(() => {
|
||||
const mobileInstance = new MicrosoftRewardsBot(true)
|
||||
mobileInstance.axios = this.axios
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, stop and try the next account
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
continue
|
||||
return mobileInstance.Mobile(account)
|
||||
})()
|
||||
])
|
||||
} else {
|
||||
this.isMobile = false
|
||||
await this.Desktop(account)
|
||||
|
||||
this.isMobile = true
|
||||
await this.Mobile(account)
|
||||
}
|
||||
|
||||
// Mobile Searches
|
||||
await this.Mobile(account)
|
||||
|
||||
log('MAIN-WORKER', `Completed tasks for account ${account.email}`)
|
||||
log('main', 'MAIN-WORKER', `Completed tasks for account ${account.email}`, 'log', 'green')
|
||||
}
|
||||
|
||||
log('MAIN-PRIMARY', 'Completed tasks for ALL accounts')
|
||||
log('MAIN-PRIMARY', 'All workers destroyed!')
|
||||
process.exit(0)
|
||||
log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green')
|
||||
process.exit()
|
||||
}
|
||||
|
||||
// Desktop
|
||||
async Desktop(account: Account) {
|
||||
this.isMobile = false
|
||||
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log('MAIN', 'Starting DESKTOP browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
||||
|
||||
this.pointsInitial = data.userStatus.availablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${this.pointsInitial}`)
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
const earnablePoints = browserEnarablePoints + appEarnablePoints
|
||||
this.collectedPoints = earnablePoints
|
||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today (Browser: ${browserEnarablePoints} points, App: ${appEarnablePoints} points)`)
|
||||
// Tally all the desktop points
|
||||
this.pointsCanCollect = browserEnarablePoints.dailySetPoints +
|
||||
browserEnarablePoints.desktopSearchPoints
|
||||
+ browserEnarablePoints.morePromotionsPoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!')
|
||||
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
|
||||
// Close desktop browser
|
||||
return await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
@@ -187,25 +207,41 @@ export class MicrosoftRewardsBot {
|
||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||
|
||||
// Close desktop browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Mobile
|
||||
async Mobile(account: Account) {
|
||||
this.isMobile = true
|
||||
|
||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||
this.homePage = await browser.newPage()
|
||||
|
||||
log('MAIN', 'Starting MOBILE browser')
|
||||
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
this.pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||
|
||||
log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||
|
||||
// Close mobile browser
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Do daily check in
|
||||
if (this.config.workers.doDailyCheckIn) {
|
||||
await this.activities.doDailyCheckIn(this.accessToken, data)
|
||||
@@ -216,63 +252,68 @@ export class MicrosoftRewardsBot {
|
||||
await this.activities.doReadToEarn(this.accessToken, data)
|
||||
}
|
||||
|
||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
// If no mobile searches data found, stop (Does not always exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
||||
// 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...')
|
||||
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
|
||||
// Increment retry count
|
||||
this.mobileRetryAttempts++
|
||||
}
|
||||
|
||||
// Exit if retries are exhausted
|
||||
if (this.mobileRetryAttempts > this.config.searchSettings.retryMobileSearchAmount) {
|
||||
log(this.isMobile, 'MAIN', `Max retry limit of ${this.config.searchSettings.retryMobileSearchAmount} reached. Exiting retry loop`, 'warn')
|
||||
} else if (this.mobileRetryAttempts !== 0) {
|
||||
log(this.isMobile, 'MAIN', `Attempt ${this.mobileRetryAttempts}/${this.config.searchSettings.retryMobileSearchAmount}: Unable to complete mobile searches, bad User-Agent? Increase search delay? Retrying...`, 'log', 'yellow')
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.closeBrowser(browser, account.email)
|
||||
|
||||
// Retry
|
||||
// Create a new browser and try
|
||||
await this.Mobile(account)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log(this.isMobile, 'MAIN', 'Unable to fetch search points, your account is most likely too "new" for this! Try again later!', 'warn')
|
||||
}
|
||||
} else {
|
||||
log('MAIN', 'No mobile searches found!')
|
||||
}
|
||||
|
||||
// Fetch new points
|
||||
const earnablePoints = await this.browser.func.getBrowserEarnablePoints() + await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
const afterPointAmount = await this.browser.func.getCurrentPoints()
|
||||
|
||||
// If the new earnable is 0, means we got all the points, else retract
|
||||
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)
|
||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
||||
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - this.pointsInitial} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
await this.browser.func.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()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const rewardsBot = new MicrosoftRewardsBot(false)
|
||||
|
||||
const bot = new MicrosoftRewardsBot()
|
||||
try {
|
||||
await rewardsBot.initialize()
|
||||
await rewardsBot.run()
|
||||
} catch (error) {
|
||||
log(false, 'MAIN-ERROR', `Error running desktop bot: ${error}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize accounts first and then start the bot
|
||||
bot.initialize().then(() => {
|
||||
bot.run()
|
||||
// Start the bots
|
||||
main().catch(error => {
|
||||
log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error')
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -5,6 +5,7 @@ export interface Account {
|
||||
}
|
||||
|
||||
export interface AccountProxy {
|
||||
proxyAxios: boolean;
|
||||
url: string;
|
||||
port: number;
|
||||
password: string;
|
||||
|
||||
@@ -2,13 +2,21 @@ export interface Config {
|
||||
baseURL: string;
|
||||
sessionPath: string;
|
||||
headless: boolean;
|
||||
parallel: boolean;
|
||||
runOnZeroPoints: boolean;
|
||||
clusters: number;
|
||||
workers: Workers;
|
||||
globalTimeout: number;
|
||||
searchOnBingLocalQueries: boolean;
|
||||
globalTimeout: number | string;
|
||||
searchSettings: SearchSettings;
|
||||
webhook: Webhook;
|
||||
saveFingerprint: boolean;
|
||||
logExcludeFunc: string[];
|
||||
saveFingerprint: ConfigSaveFingerprint;
|
||||
}
|
||||
|
||||
export interface ConfigSaveFingerprint {
|
||||
mobile: boolean;
|
||||
desktop: boolean;
|
||||
}
|
||||
|
||||
export interface SearchSettings {
|
||||
@@ -16,12 +24,12 @@ export interface SearchSettings {
|
||||
scrollRandomResults: boolean;
|
||||
clickRandomResults: boolean;
|
||||
searchDelay: SearchDelay;
|
||||
retryMobileSearch: boolean;
|
||||
retryMobileSearchAmount: number;
|
||||
}
|
||||
|
||||
export interface SearchDelay {
|
||||
min: number;
|
||||
max: number;
|
||||
min: number | string;
|
||||
max: number | string;
|
||||
}
|
||||
|
||||
export interface Webhook {
|
||||
|
||||
@@ -357,7 +357,7 @@ export interface MorePromotion {
|
||||
|
||||
export interface PunchCard {
|
||||
name: string;
|
||||
parentPromotion: PromotionalItem;
|
||||
parentPromotion?: PromotionalItem;
|
||||
childPromotions: PromotionalItem[];
|
||||
}
|
||||
|
||||
|
||||
7
src/interface/Points.ts
Normal file
7
src/interface/Points.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export interface EarnablePoints {
|
||||
desktopSearchPoints: number
|
||||
mobileSearchPoints: number
|
||||
dailySetPoints: number
|
||||
morePromotionsPoints: number
|
||||
totalEarnablePoints: number
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const configPath = path.join(__dirname, '../dist/config.json')
|
||||
|
||||
// Read the existing config file
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
|
||||
// Update the config with environment variables if they are set
|
||||
config.baseURL = process.env.BASE_URL || config.baseURL
|
||||
config.sessionPath = process.env.SESSION_PATH || config.sessionPath
|
||||
config.headless = process.env.HEADLESS ? process.env.HEADLESS === 'true' : config.headless
|
||||
config.runOnZeroPoints = process.env.RUN_ON_ZERO_POINTS ? process.env.RUN_ON_ZERO_POINTS === 'true' : config.runOnZeroPoints
|
||||
config.clusters = process.env.CLUSTERS ? parseInt(process.env.CLUSTERS, 10) : config.clusters
|
||||
config.saveFingerprint = process.env.SAVE_FINGERPRINT ? process.env.SAVE_FINGERPRINT === 'true' : config.saveFingerprint
|
||||
config.globalTimeout = process.env.GLOBAL_TIMEOUT ? parseInt(process.env.GLOBAL_TIMEOUT, 10) : config.globalTimeout
|
||||
|
||||
config.workers.doDailySet = process.env.DO_DAILY_SET ? process.env.DO_DAILY_SET === 'true' : config.workers.doDailySet
|
||||
config.workers.doMorePromotions = process.env.DO_MORE_PROMOTIONS ? process.env.DO_MORE_PROMOTIONS === 'true' : config.workers.doMorePromotions
|
||||
config.workers.doPunchCards = process.env.DO_PUNCH_CARDS ? process.env.DO_PUNCH_CARDS === 'true' : config.workers.doPunchCards
|
||||
config.workers.doDesktopSearch = process.env.DO_DESKTOP_SEARCH ? process.env.DO_DESKTOP_SEARCH === 'true' : config.workers.doDesktopSearch
|
||||
config.workers.doMobileSearch = process.env.DO_MOBILE_SEARCH ? process.env.DO_MOBILE_SEARCH === 'true' : config.workers.doMobileSearch
|
||||
config.workers.doDailyCheckIn = process.env.DO_DAILY_CHECK_IN ? process.env.DO_DAILY_CHECK_IN === 'true' : config.workers.doDailyCheckIn
|
||||
config.workers.doReadToEarn = process.env.DO_READ_TO_EARN ? process.env.DO_READ_TO_EARN === 'true' : config.workers.doReadToEarn
|
||||
|
||||
config.searchSettings.useGeoLocaleQueries = process.env.USE_GEO_LOCALE_QUERIES ? process.env.USE_GEO_LOCALE_QUERIES === 'true' : config.searchSettings.useGeoLocaleQueries
|
||||
config.searchSettings.scrollRandomResults = process.env.SCROLL_RANDOM_RESULTS ? process.env.SCROLL_RANDOM_RESULTS === 'true' : config.searchSettings.scrollRandomResults
|
||||
config.searchSettings.clickRandomResults = process.env.CLICK_RANDOM_RESULTS ? process.env.CLICK_RANDOM_RESULTS === 'true' : config.searchSettings.clickRandomResults
|
||||
config.searchSettings.searchDelay.min = process.env.SEARCH_DELAY_MIN ? parseInt(process.env.SEARCH_DELAY_MIN, 10) : config.searchSettings.searchDelay.min
|
||||
config.searchSettings.searchDelay.max = process.env.SEARCH_DELAY_MAX ? parseInt(process.env.SEARCH_DELAY_MAX, 10) : config.searchSettings.searchDelay.max
|
||||
config.searchSettings.retryMobileSearch = process.env.RETRY_MOBILE_SEARCH ? process.env.RETRY_MOBILE_SEARCH === 'true' : config.searchSettings.retryMobileSearch
|
||||
|
||||
config.webhook.enabled = process.env.WEBHOOK_ENABLED ? process.env.WEBHOOK_ENABLED === 'true' : config.webhook.enabled
|
||||
config.webhook.url = process.env.WEBHOOK_URL || config.webhook.url
|
||||
|
||||
// Write the updated config back to the file
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||
console.log('Config file updated with environment variables')
|
||||
} catch (error) {
|
||||
console.error(`Failed to write updated config file to ${configPath}:`, error)
|
||||
process.exit(1)
|
||||
}
|
||||
44
src/util/Axios.ts
Normal file
44
src/util/Axios.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import { HttpProxyAgent } from 'http-proxy-agent'
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent'
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent'
|
||||
import { AccountProxy } from '../interface/Account'
|
||||
|
||||
class AxiosClient {
|
||||
private instance: AxiosInstance
|
||||
private account: AccountProxy
|
||||
|
||||
constructor(account: AccountProxy) {
|
||||
this.account = account
|
||||
this.instance = axios.create()
|
||||
|
||||
// If a proxy configuration is provided, set up the agent
|
||||
if (this.account.url && this.account.proxyAxios) {
|
||||
const agent = this.getAgentForProxy(this.account)
|
||||
this.instance.defaults.httpAgent = agent
|
||||
this.instance.defaults.httpsAgent = agent
|
||||
}
|
||||
}
|
||||
|
||||
private getAgentForProxy(proxyConfig: AccountProxy): HttpProxyAgent<string> | HttpsProxyAgent<string> | SocksProxyAgent {
|
||||
const { url, port } = proxyConfig
|
||||
|
||||
switch (true) {
|
||||
case proxyConfig.url.startsWith('http'):
|
||||
return new HttpProxyAgent(`${url}:${port}`)
|
||||
case proxyConfig.url.startsWith('https'):
|
||||
return new HttpsProxyAgent(`${url}:${port}`)
|
||||
case proxyConfig.url.startsWith('socks'):
|
||||
return new SocksProxyAgent(`${url}:${port}`)
|
||||
default:
|
||||
throw new Error(`Unsupported proxy protocol: ${url}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Generic method to make any Axios request
|
||||
public async request(config: AxiosRequestConfig): Promise<AxiosResponse> {
|
||||
return this.instance.request(config)
|
||||
}
|
||||
}
|
||||
|
||||
export default AxiosClient
|
||||
@@ -1,12 +1,13 @@
|
||||
import { BrowserContext, Cookie } from 'playwright'
|
||||
import { BrowserContext, Cookie } from 'rebrowser-playwright'
|
||||
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
|
||||
import { Account } from '../interface/Account'
|
||||
import { Config } from '../interface/Config'
|
||||
import { Config, ConfigSaveFingerprint } from '../interface/Config'
|
||||
|
||||
let configCache: Config
|
||||
|
||||
export function loadAccounts(): Account[] {
|
||||
try {
|
||||
@@ -28,16 +29,23 @@ export function loadAccounts(): Account[] {
|
||||
|
||||
export function loadConfig(): Config {
|
||||
try {
|
||||
if (configCache) {
|
||||
return configCache
|
||||
}
|
||||
|
||||
const configDir = path.join(__dirname, '../', 'config.json')
|
||||
const config = fs.readFileSync(configDir, 'utf-8')
|
||||
|
||||
return JSON.parse(config)
|
||||
const configData = JSON.parse(config)
|
||||
configCache = configData // Set as cache
|
||||
|
||||
return configData
|
||||
} catch (error) {
|
||||
throw new Error(error as string)
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, getFingerprint: boolean) {
|
||||
export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, saveFingerprint: ConfigSaveFingerprint) {
|
||||
try {
|
||||
// Fetch cookie file
|
||||
const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`)
|
||||
@@ -52,7 +60,7 @@ export async function loadSessionData(sessionPath: string, email: string, isMobi
|
||||
const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`)
|
||||
|
||||
let fingerprint!: BrowserFingerprintWithHeaders
|
||||
if (getFingerprint && fs.existsSync(fingerprintFile)) {
|
||||
if (((saveFingerprint.desktop && !isMobile) || (saveFingerprint.mobile && isMobile)) && fs.existsSync(fingerprintFile)) {
|
||||
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
||||
fingerprint = JSON.parse(fingerprintData)
|
||||
}
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { Webhook } from './Webhook'
|
||||
import { loadConfig } from './Load'
|
||||
|
||||
|
||||
export function log(isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof chalk): void {
|
||||
const configData = loadConfig()
|
||||
|
||||
if (configData.logExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) {
|
||||
return
|
||||
}
|
||||
|
||||
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
|
||||
const currentTime = new Date().toLocaleString()
|
||||
const platformText = isMobile === 'main' ? 'MAIN' : isMobile ? 'MOBILE' : 'DESKTOP'
|
||||
const chalkedPlatform = isMobile === 'main' ? chalk.bgCyan('MAIN') : isMobile ? chalk.bgBlue('MOBILE') : chalk.bgMagenta('DESKTOP')
|
||||
|
||||
let str = ''
|
||||
// Clean string for the Webhook (no chalk)
|
||||
const cleanStr = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}`
|
||||
|
||||
// Send the clean string to the Webhook
|
||||
Webhook(configData, cleanStr)
|
||||
|
||||
// Formatted string with chalk for terminal logging
|
||||
const str = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${chalkedPlatform} [${title}] ${message}`
|
||||
|
||||
const applyChalk = color && typeof chalk[color] === 'function' ? chalk[color] as (msg: string) => string : null
|
||||
|
||||
// Log based on the type
|
||||
switch (type) {
|
||||
case 'warn':
|
||||
str = `[${currentTime}] [PID: ${process.pid}] [WARN] [${title}] ${message}`
|
||||
console.warn(str)
|
||||
applyChalk ? console.warn(applyChalk(str)) : console.warn(str)
|
||||
break
|
||||
|
||||
case 'error':
|
||||
str = `[${currentTime}] [PID: ${process.pid}] [ERROR] [${title}] ${message}`
|
||||
console.error(str)
|
||||
applyChalk ? console.error(applyChalk(str)) : console.error(str)
|
||||
break
|
||||
|
||||
default:
|
||||
str = `[${currentTime}] [PID: ${process.pid}] [LOG] [${title}] ${message}`
|
||||
console.log(str)
|
||||
applyChalk ? console.log(applyChalk(str)) : console.log(str)
|
||||
break
|
||||
}
|
||||
|
||||
if (str) Webhook(str)
|
||||
}
|
||||
@@ -1,42 +1,45 @@
|
||||
import axios from 'axios'
|
||||
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||
|
||||
import { log } from './Logger'
|
||||
|
||||
import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil'
|
||||
|
||||
export async function getUserAgent(mobile: boolean) {
|
||||
const system = getSystemComponents(mobile)
|
||||
const app = await getAppComponents(mobile)
|
||||
const NOT_A_BRAND_VERSION = '99'
|
||||
|
||||
const uaTemplate = mobile ?
|
||||
export async function getUserAgent(isMobile: boolean) {
|
||||
const system = getSystemComponents(isMobile)
|
||||
const app = await getAppComponents(isMobile)
|
||||
|
||||
const uaTemplate = isMobile ?
|
||||
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Mobile Safari/537.36 EdgA/${app.edge_version}` :
|
||||
`Mozilla/5.0 (${system}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${app.chrome_reduced_version} Safari/537.36 Edg/${app.edge_version}`
|
||||
|
||||
const platformVersion = `${mobile ? Math.floor(Math.random() * 5) + 9 : Math.floor(Math.random() * 15) + 1}.0.0`
|
||||
const platformVersion = `${isMobile ? Math.floor(Math.random() * 5) + 9 : Math.floor(Math.random() * 15) + 1}.0.0`
|
||||
|
||||
const uaMetadata = {
|
||||
mobile,
|
||||
platform: mobile ? 'Android' : 'Windows',
|
||||
isMobile,
|
||||
platform: isMobile ? 'Android' : 'Windows',
|
||||
fullVersionList: [
|
||||
{ brand: 'Not/A)Brand', version: '99.0.0.0' },
|
||||
{ brand: 'Not/A)Brand', version: `${NOT_A_BRAND_VERSION}.0.0.0` },
|
||||
{ brand: 'Microsoft Edge', version: app['edge_version'] },
|
||||
{ brand: 'Chromium', version: app['chrome_version'] }
|
||||
],
|
||||
brands: [
|
||||
{ brand: 'Not/A)Brand', version: '99' },
|
||||
{ brand: 'Not/A)Brand', version: NOT_A_BRAND_VERSION },
|
||||
{ brand: 'Microsoft Edge', version: app['edge_major_version'] },
|
||||
{ brand: 'Chromium', version: app['chrome_major_version'] }
|
||||
],
|
||||
platformVersion,
|
||||
architecture: mobile ? '' : 'x86',
|
||||
bitness: mobile ? '' : '64',
|
||||
architecture: isMobile ? '' : 'x86',
|
||||
bitness: isMobile ? '' : '64',
|
||||
model: ''
|
||||
}
|
||||
|
||||
return { userAgent: uaTemplate, userAgentMetadata: uaMetadata }
|
||||
}
|
||||
|
||||
export async function getChromeVersion(): Promise<string> {
|
||||
export async function getChromeVersion(isMobile: boolean): Promise<string> {
|
||||
try {
|
||||
const request = {
|
||||
url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json',
|
||||
@@ -51,11 +54,11 @@ export async function getChromeVersion(): Promise<string> {
|
||||
return data.channels.Stable.version
|
||||
|
||||
} catch (error) {
|
||||
throw log('USERAGENT-CHROME-VERSION', 'An error occurred:' + error, 'error')
|
||||
throw log(isMobile, 'USERAGENT-CHROME-VERSION', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
export async function getEdgeVersions() {
|
||||
export async function getEdgeVersions(isMobile: boolean) {
|
||||
try {
|
||||
const request = {
|
||||
url: 'https://edgeupdates.microsoft.com/api/products',
|
||||
@@ -75,7 +78,7 @@ export async function getEdgeVersions() {
|
||||
|
||||
|
||||
} catch (error) {
|
||||
throw log('USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error')
|
||||
throw log(isMobile, 'USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,20 +93,51 @@ export function getSystemComponents(mobile: boolean): string {
|
||||
return `${uaPlatform}; ${osId}`
|
||||
}
|
||||
|
||||
export async function getAppComponents(mobile: boolean) {
|
||||
const versions = await getEdgeVersions()
|
||||
const edgeVersion = mobile ? versions.android : versions.windows as string
|
||||
export async function getAppComponents(isMobile: boolean) {
|
||||
const versions = await getEdgeVersions(isMobile)
|
||||
const edgeVersion = isMobile ? versions.android : versions.windows as string
|
||||
const edgeMajorVersion = edgeVersion?.split('.')[0]
|
||||
|
||||
const chromeVersion = await getChromeVersion()
|
||||
const chromeVersion = await getChromeVersion(isMobile)
|
||||
const chromeMajorVersion = chromeVersion?.split('.')[0]
|
||||
const chromeReducedVersion = `${chromeMajorVersion}.0.0.0`
|
||||
|
||||
return {
|
||||
not_a_brand_version: `${NOT_A_BRAND_VERSION}.0.0.0`,
|
||||
not_a_brand_major_version: NOT_A_BRAND_VERSION,
|
||||
edge_version: edgeVersion as string,
|
||||
edge_major_version: edgeMajorVersion as string,
|
||||
chrome_version: chromeVersion as string,
|
||||
chrome_major_version: chromeMajorVersion as string,
|
||||
chrome_reduced_version: chromeReducedVersion as string
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateFingerprintUserAgent(fingerprint: BrowserFingerprintWithHeaders, isMobile: boolean): Promise<BrowserFingerprintWithHeaders> {
|
||||
try {
|
||||
const userAgentData = await getUserAgent(isMobile)
|
||||
const componentData = await getAppComponents(isMobile)
|
||||
|
||||
//@ts-expect-error Errors due it not exactly matching
|
||||
fingerprint.fingerprint.navigator.userAgentData = userAgentData.userAgentMetadata
|
||||
fingerprint.fingerprint.navigator.userAgent = userAgentData.userAgent
|
||||
fingerprint.fingerprint.navigator.appVersion = userAgentData.userAgent.replace(`${fingerprint.fingerprint.navigator.appCodeName}/`, '')
|
||||
|
||||
fingerprint.headers['user-agent'] = userAgentData.userAgent
|
||||
fingerprint.headers['sec-ch-ua'] = `"Microsoft Edge";v="${componentData.edge_major_version}", "Not=A?Brand";v="${componentData.not_a_brand_major_version}", "Chromium";v="${componentData.chrome_major_version}"`
|
||||
fingerprint.headers['sec-ch-ua-full-version-list'] = `"Microsoft Edge";v="${componentData.edge_version}", "Not=A?Brand";v="${componentData.not_a_brand_version}", "Chromium";v="${componentData.chrome_version}"`
|
||||
|
||||
/*
|
||||
Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Mobile Safari/537.36 EdgA/129.0.0.0
|
||||
sec-ch-ua-full-version-list: "Microsoft Edge";v="129.0.2792.84", "Not=A?Brand";v="8.0.0.0", "Chromium";v="129.0.6668.90"
|
||||
sec-ch-ua: "Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
|
||||
|
||||
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
|
||||
"Google Chrome";v="129.0.6668.90", "Not=A?Brand";v="8.0.0.0", "Chromium";v="129.0.6668.90"
|
||||
*/
|
||||
|
||||
return fingerprint
|
||||
} catch (error) {
|
||||
throw log(isMobile, 'USER-AGENT-UPDATE', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import ms from 'ms'
|
||||
|
||||
export default class Util {
|
||||
|
||||
async wait(ms: number): Promise<void> {
|
||||
@@ -39,4 +41,12 @@ export default class Util {
|
||||
return chunks
|
||||
}
|
||||
|
||||
stringToMs(input: string | number): number {
|
||||
const milisec = ms(input.toString())
|
||||
if (!milisec) {
|
||||
throw new Error('The string provided cannot be parsed to a valid time! Use a format like "1 min", "1m" or "1 minutes"')
|
||||
}
|
||||
return milisec
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import axios from 'axios'
|
||||
|
||||
import { loadConfig } from './Load'
|
||||
|
||||
import { Config } from '../interface/Config'
|
||||
|
||||
export async function Webhook(content: string) {
|
||||
const webhook = loadConfig().webhook
|
||||
export async function Webhook(configData: Config, content: string) {
|
||||
const webhook = configData.webhook
|
||||
|
||||
if (!webhook.enabled || webhook.url.length < 10) return
|
||||
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"src/accounts.json",
|
||||
"src/config.json"
|
||||
"src/config.json",
|
||||
"src/functions/queries.json"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user