mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 14:03:58 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
849406c44f | ||
|
|
d1f4364e18 | ||
|
|
cf79467a4e | ||
|
|
ae554a0639 | ||
|
|
1a6f9f4ac3 | ||
|
|
45083ed41f | ||
|
|
8b489d50f7 | ||
|
|
7151f2351a | ||
|
|
5afd8cbe1d | ||
|
|
f015649d16 | ||
|
|
034359019c | ||
|
|
09ddbee45a | ||
|
|
7939196e88 | ||
|
|
5bc66c5fc9 | ||
|
|
c70d6f9cb1 | ||
|
|
a47b86e74d | ||
|
|
ce2a72ee36 | ||
|
|
755237caa1 | ||
|
|
2b4cd505c0 | ||
|
|
a39a861dab | ||
|
|
8d19129906 | ||
|
|
c6ab80fe54 | ||
|
|
9b1eed526f | ||
|
|
9a144b2e60 | ||
|
|
28b1881642 | ||
|
|
ef6ad569ff | ||
|
|
da9ba91c5c | ||
|
|
deb2d58b1b | ||
|
|
66a82c2584 | ||
|
|
8a022d5983 | ||
|
|
64048e35d7 | ||
|
|
cf7f7ac790 | ||
|
|
f7aa5039f9 | ||
|
|
e082fb03f0 | ||
|
|
0303b8c605 | ||
|
|
2fea17c415 | ||
|
|
c5beccb54b | ||
|
|
b566ccaece | ||
|
|
15b2b827eb | ||
|
|
02518ee4ba | ||
|
|
69819b5631 | ||
|
|
b389b87792 | ||
|
|
9ea7f5c452 | ||
|
|
f3fb641ecd | ||
|
|
bca1e7c896 | ||
|
|
fd7c8e36d4 | ||
|
|
dcb0c25d46 | ||
|
|
a8cb5482d4 | ||
|
|
28286ff9fe | ||
|
|
1d6167aeca | ||
|
|
3b15fe19a7 |
@@ -28,6 +28,10 @@ module.exports = {
|
|||||||
'error',
|
'error',
|
||||||
'never'
|
'never'
|
||||||
],
|
],
|
||||||
|
'@typescript-eslint/no-explicit-any':
|
||||||
|
['warn', {
|
||||||
|
fixToUnknown: true // This line is optional and only relevant if you are using TypeScript
|
||||||
|
}],
|
||||||
'comma-dangle': 'off',
|
'comma-dangle': 'off',
|
||||||
'@typescript-eslint/comma-dangle': 'error',
|
'@typescript-eslint/comma-dangle': 'error',
|
||||||
'prefer-arrow-callback': 'error'
|
'prefer-arrow-callback': 'error'
|
||||||
|
|||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.sh text eol=lf
|
||||||
|
*.template text eol=lf
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ accounts.json
|
|||||||
notes
|
notes
|
||||||
accounts.dev.json
|
accounts.dev.json
|
||||||
accounts.main.json
|
accounts.main.json
|
||||||
|
.DS_Store
|
||||||
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
// Use IntelliSense to learn about possible attributes.
|
|
||||||
// Hover to view descriptions of existing attributes.
|
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Launch Program",
|
|
||||||
"skipFiles": [
|
|
||||||
"<node_internals>/**"
|
|
||||||
],
|
|
||||||
"program": "${file}",
|
|
||||||
"outFiles": [
|
|
||||||
"${workspaceFolder}/**/*.js"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@@ -0,0 +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 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"]
|
||||||
80
README.md
80
README.md
@@ -1,5 +1,5 @@
|
|||||||
# Microsoft-Rewards-Script
|
# Microsoft-Rewards-Script
|
||||||
Automated Microsoft Rewards script, however this time using TypeScript, Cheerio and Puppeteer.
|
Automated Microsoft Rewards script, however this time using TypeScript, Cheerio and Playwright.
|
||||||
|
|
||||||
Under development, however mainly for personal use!
|
Under development, however mainly for personal use!
|
||||||
|
|
||||||
@@ -11,10 +11,79 @@ Under development, however mainly for personal use!
|
|||||||
5. Run `npm run build` to build the script
|
5. Run `npm run build` to build the script
|
||||||
6. Run `npm run start` to start the built script
|
6. Run `npm run start` to start the built script
|
||||||
|
|
||||||
|
## Notes ##
|
||||||
|
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run kill-chrome-win` script. (Windows)
|
||||||
|
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
|
||||||
|
|
||||||
|
## Docker (Experimental) ##
|
||||||
|
### **Before Starting**
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
### **Setup the Source Files**
|
||||||
|
|
||||||
|
1. **Download the Source Code**
|
||||||
|
|
||||||
|
2. **Update `accounts.json`**
|
||||||
|
|
||||||
|
3. **Edit `config.json`,** ensuring the following values are set (other settings are up to your preference):
|
||||||
|
|
||||||
|
```json
|
||||||
|
"headless": true,
|
||||||
|
"clusters": 1,
|
||||||
|
```
|
||||||
|
|
||||||
|
### **Customize the `compose.yaml` File**
|
||||||
|
|
||||||
|
A basic docker `compose.yaml` is provided. Follow these steps to configure and run the container:
|
||||||
|
|
||||||
|
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 |
|
||||||
|
| :------------- |:-------------| :-----|
|
||||||
|
| 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 ##
|
## Features ##
|
||||||
- [x] Multi-Account Support
|
- [x] Multi-Account Support
|
||||||
- [x] Session Storing
|
- [x] Session Storing
|
||||||
- [x] 2FA Support
|
- [x] 2FA Support
|
||||||
|
- [x] Passwordless Support
|
||||||
- [x] Headless Support
|
- [x] Headless Support
|
||||||
- [x] Discord Webhook Support
|
- [x] Discord Webhook Support
|
||||||
- [x] Desktop Searches
|
- [x] Desktop Searches
|
||||||
@@ -31,13 +100,14 @@ Under development, however mainly for personal use!
|
|||||||
- [x] Completing Click Rewards
|
- [x] Completing Click Rewards
|
||||||
- [x] Completing Polls
|
- [x] Completing Polls
|
||||||
- [x] Completing Punchcards
|
- [x] Completing Punchcards
|
||||||
- [ ] Solving This Or That Quiz
|
- [x] Solving This Or That Quiz (Random)
|
||||||
- [x] Clicking Promotional Items
|
|
||||||
- [x] Solving ABC Quiz
|
- [x] Solving ABC Quiz
|
||||||
- [ ] Completing Shopping Game
|
- [x] Completing Daily Check In
|
||||||
- [ ] Completing Gaming Tab
|
- [x] Completing Read To Earn
|
||||||
- [x] Clustering Support
|
- [x] Clustering Support
|
||||||
- [x] Proxy Support
|
- [x] Proxy Support
|
||||||
|
- [x] Docker Support (experimental)
|
||||||
|
- [x] Automatic scheduling (via Docker)
|
||||||
|
|
||||||
## Disclaimer ##
|
## Disclaimer ##
|
||||||
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
||||||
|
|||||||
15
compose.yaml
Normal file
15
compose.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
services:
|
||||||
|
microsoft-rewards-script:
|
||||||
|
build: .
|
||||||
|
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
|
||||||
|
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
|
||||||
38
package.json
38
package.json
@@ -1,16 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "microsoft-rewards-script",
|
"name": "microsoft-rewards-script",
|
||||||
"version": "1.2.4",
|
"version": "1.5.0",
|
||||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.0.0"
|
"node": ">=18.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"pre-build": "npm i && rimraf dist && npx playwright install chromium",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node ./dist/index.js",
|
"start": "node ./dist/index.js",
|
||||||
"ts-start": "ts-node ./src/index.ts",
|
"ts-start": "ts-node ./src/index.ts",
|
||||||
"dev": "ts-node ./src/index.ts -dev"
|
"dev": "ts-node ./src/index.ts -dev",
|
||||||
|
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"",
|
||||||
|
"create-docker": "docker build -t microsoft-rewards-script-docker ."
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"Bing Rewards",
|
"Bing Rewards",
|
||||||
@@ -18,24 +21,31 @@
|
|||||||
"Bot",
|
"Bot",
|
||||||
"Script",
|
"Script",
|
||||||
"TypeScript",
|
"TypeScript",
|
||||||
"Puppeteer",
|
"Playwright",
|
||||||
"Cheerio"
|
"Cheerio"
|
||||||
],
|
],
|
||||||
"author": "Netsky",
|
"author": "Netsky",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
"@types/ms": "^0.7.34",
|
||||||
"typescript": "^5.2.2"
|
"@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": {
|
"dependencies": {
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.7.9",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"chalk": "^4.1.2",
|
||||||
"eslint": "^8.49.0",
|
"cheerio": "^1.0.0",
|
||||||
"eslint-plugin-modules-newline": "^0.0.6",
|
"fingerprint-generator": "^2.1.62",
|
||||||
"fingerprint-generator": "^2.1.42",
|
"fingerprint-injector": "^2.1.62",
|
||||||
"fingerprint-injector": "^2.1.42",
|
"http-proxy-agent": "^7.0.2",
|
||||||
"puppeteer": "^21.4.1",
|
"https-proxy-agent": "^7.0.6",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"ms": "^2.1.3",
|
||||||
"ts-node": "^10.9.1"
|
"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",
|
"email": "email_1",
|
||||||
"password": "password_1",
|
"password": "password_1",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
|
"proxyAxios": true,
|
||||||
"url": "",
|
"url": "",
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"username": "",
|
"username": "",
|
||||||
@@ -13,6 +14,7 @@
|
|||||||
"email": "email_2",
|
"email": "email_2",
|
||||||
"password": "password_2",
|
"password": "password_2",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
|
"proxyAxios": true,
|
||||||
"url": "",
|
"url": "",
|
||||||
"port": 0,
|
"port": 0,
|
||||||
"username": "",
|
"username": "",
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import puppeteer from 'puppeteer-extra'
|
import playwright, { BrowserContext } from 'rebrowser-playwright'
|
||||||
import { FingerprintInjector } from 'fingerprint-injector'
|
|
||||||
|
import { newInjectedContext } from 'fingerprint-injector'
|
||||||
import { FingerprintGenerator } from 'fingerprint-generator'
|
import { FingerprintGenerator } from 'fingerprint-generator'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
import { loadSessionData, saveFingerprintData } from '../util/Load'
|
||||||
|
import { updateFingerprintUserAgent } from '../util/UserAgent'
|
||||||
|
|
||||||
import { AccountProxy } from '../interface/Account'
|
import { AccountProxy } from '../interface/Account'
|
||||||
|
|
||||||
/* Test Stuff
|
/* Test Stuff
|
||||||
https://abrahamjuliot.github.io/creepjs/
|
https://abrahamjuliot.github.io/creepjs/
|
||||||
https://botcheck.luminati.io/
|
https://botcheck.luminati.io/
|
||||||
http://f.vision/
|
https://fv.pro/
|
||||||
|
https://pixelscan.net/
|
||||||
|
https://www.browserscan.net/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
class Browser {
|
class Browser {
|
||||||
private bot: MicrosoftRewardsBot
|
private bot: MicrosoftRewardsBot
|
||||||
|
|
||||||
@@ -20,38 +24,52 @@ class Browser {
|
|||||||
this.bot = bot
|
this.bot = bot
|
||||||
}
|
}
|
||||||
|
|
||||||
async createBrowser(email: string, proxy: AccountProxy, isMobile: boolean) {
|
async createBrowser(proxy: AccountProxy, email: string): Promise<BrowserContext> {
|
||||||
// const userAgent = await getUserAgent(isMobile)
|
const browser = await playwright.chromium.launch({
|
||||||
|
//channel: 'msedge', // Uses Edge instead of chrome
|
||||||
const browser = await puppeteer.launch({
|
|
||||||
headless: this.bot.config.headless,
|
headless: this.bot.config.headless,
|
||||||
userDataDir: await this.bot.browser.func.loadSesion(email),
|
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
|
||||||
args: [
|
args: [
|
||||||
'--no-sandbox',
|
'--no-sandbox',
|
||||||
'--mute-audio',
|
'--mute-audio',
|
||||||
'--disable-setuid-sandbox',
|
'--disable-setuid-sandbox',
|
||||||
proxy.url ? `--proxy-server=${proxy.url}:${proxy.port}` : ''
|
'--ignore-certificate-errors',
|
||||||
|
'--ignore-certificate-errors-spki-list',
|
||||||
|
'--ignore-ssl-errors'
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)
|
||||||
devices: isMobile ? ['mobile'] : ['desktop'],
|
|
||||||
operatingSystems: isMobile ? ['android'] : ['windows'],
|
|
||||||
browsers: ['edge'],
|
|
||||||
browserListQuery: 'last 2 Edge versions'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Modify the newPage function to attach the fingerprint
|
const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint()
|
||||||
const originalNewPage = browser.newPage
|
|
||||||
browser.newPage = async function () {
|
const context = await newInjectedContext(browser as any, { fingerprint: fingerprint })
|
||||||
const page = await originalNewPage.apply(browser)
|
|
||||||
await new FingerprintInjector().attachFingerprintToPuppeteer(page, { fingerprint, headers })
|
// Set timeout to preferred amount
|
||||||
return page
|
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, fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
return browser
|
this.bot.log(this.bot.isMobile, 'BROWSER', `Created browser with User-Agent: "${fingerprint.fingerprint.navigator.userAgent}"`)
|
||||||
|
|
||||||
|
return context as BrowserContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async generateFingerprint() {
|
||||||
|
const fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||||
|
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||||
|
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||||
|
browsers: [{ name: 'edge' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const updatedFingerPrintData = await updateFingerprintUserAgent(fingerPrintData, this.bot.isMobile)
|
||||||
|
|
||||||
|
return updatedFingerPrintData
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Browser
|
export default Browser
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { BrowserContext, Page } from 'rebrowser-playwright'
|
||||||
import { CheerioAPI, load } from 'cheerio'
|
import { CheerioAPI, load } from 'cheerio'
|
||||||
import fs from 'fs'
|
import { AxiosRequestConfig } from 'axios'
|
||||||
import path from 'path'
|
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
import { saveSessionData } from '../util/Load'
|
||||||
|
|
||||||
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
||||||
import { QuizData } from './../interface/QuizData'
|
import { QuizData } from './../interface/QuizData'
|
||||||
|
import { AppUserData } from '../interface/AppUserData'
|
||||||
|
import { EarnablePoints } from '../interface/Points'
|
||||||
|
|
||||||
|
|
||||||
export default class BrowserFunc {
|
export default class BrowserFunc {
|
||||||
@@ -16,35 +18,46 @@ export default class BrowserFunc {
|
|||||||
this.bot = bot
|
this.bot = bot
|
||||||
}
|
}
|
||||||
|
|
||||||
async goHome(page: Page): Promise<boolean> {
|
|
||||||
|
/**
|
||||||
|
* Navigate the provided page to rewards homepage
|
||||||
|
* @param {Page} page Playwright page
|
||||||
|
*/
|
||||||
|
async goHome(page: Page) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||||
|
|
||||||
|
if (page.url() === dashboardURL.href) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await page.goto(this.bot.config.baseURL)
|
await page.goto(this.bot.config.baseURL)
|
||||||
|
|
||||||
const maxIterations = 5 // Maximum iterations set to 5
|
const maxIterations = 5 // Maximum iterations set to 5
|
||||||
|
|
||||||
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
for (let iteration = 1; iteration <= maxIterations; iteration++) {
|
||||||
await this.bot.utils.wait(3000)
|
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
|
// Check if account is suspended
|
||||||
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
const isSuspended = await page.waitForSelector('#suspendedAccountHeader', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
|
||||||
if (isSuspended) {
|
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!')
|
throw new Error('Account has been suspended!')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// If activities are found, exit the loop
|
// If activities are found, exit the loop
|
||||||
await page.waitForSelector('#more-activities', { timeout: 1000 })
|
await page.waitForSelector('#more-activities', { timeout: 1000 })
|
||||||
|
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||||
break
|
break
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Continue if element is not found
|
// Continue if element is not found
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Below runs if the homepage was unable to be visited
|
||||||
const currentURL = new URL(page.url())
|
const currentURL = new URL(page.url())
|
||||||
|
|
||||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||||
@@ -52,60 +65,208 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
await page.goto(this.bot.config.baseURL)
|
await page.goto(this.bot.config.baseURL)
|
||||||
|
} else {
|
||||||
|
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.utils.wait(5000)
|
await this.bot.utils.wait(5000)
|
||||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('An error occurred:', error)
|
throw this.bot.log(this.bot.isMobile, 'GO-HOME', 'An error occurred:' + error, 'error')
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDashboardData(page: Page): Promise<DashboardData> {
|
/**
|
||||||
|
* Fetch user dashboard data
|
||||||
|
* @returns {DashboardData} Object of user bing rewards dashboard data
|
||||||
|
*/
|
||||||
|
async getDashboardData(): Promise<DashboardData> {
|
||||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||||
const currentURL = new URL(page.url())
|
const currentURL = new URL(this.bot.homePage.url())
|
||||||
|
|
||||||
// Should never happen since tasks are opened in a new tab!
|
try {
|
||||||
if (currentURL.hostname !== dashboardURL.hostname) {
|
// Should never happen since tasks are opened in a new tab!
|
||||||
this.bot.log('DASHBOARD-DATA', 'Provided page did not equal dashboard page, redirecting to dashboard page')
|
if (currentURL.hostname !== dashboardURL.hostname) {
|
||||||
await this.goHome(page)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload the page to get new data
|
|
||||||
await page.reload({ waitUntil: 'networkidle2' })
|
|
||||||
|
|
||||||
const scriptContent = await page.evaluate(() => {
|
|
||||||
const scripts = Array.from(document.querySelectorAll('script'))
|
|
||||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
|
||||||
|
|
||||||
if (targetScript) {
|
|
||||||
return targetScript.innerText
|
|
||||||
} else {
|
|
||||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Extract the dashboard object from the script content
|
|
||||||
const dashboardData = await page.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])
|
|
||||||
} else {
|
|
||||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
|
||||||
}
|
|
||||||
}, scriptContent)
|
|
||||||
|
|
||||||
return dashboardData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get search point counters
|
||||||
|
* @returns {Counters} Object of search counter data
|
||||||
|
*/
|
||||||
|
async getSearchPoints(): Promise<Counters> {
|
||||||
|
const dashboardData = await this.getDashboardData() // Always fetch newest data
|
||||||
|
|
||||||
|
return dashboardData.userStatus.counters
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total earnable points with web browser
|
||||||
|
* @returns {number} Total earnable points
|
||||||
|
*/
|
||||||
|
async getBrowserEarnablePoints(): Promise<EarnablePoints> {
|
||||||
|
try {
|
||||||
|
let desktopSearchPoints = 0
|
||||||
|
let mobileSearchPoints = 0
|
||||||
|
let dailySetPoints = 0
|
||||||
|
let morePromotionsPoints = 0
|
||||||
|
|
||||||
|
const data = await this.getDashboardData()
|
||||||
|
|
||||||
|
// Desktop Search Points
|
||||||
|
if (data.userStatus.counters.pcSearch?.length) {
|
||||||
|
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 => mobileSearchPoints += (x.pointProgressMax - x.pointProgress))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daily Set
|
||||||
|
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') {
|
||||||
|
morePromotionsPoints += (x.pointProgressMax - x.pointProgress)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalEarnablePoints = desktopSearchPoints + mobileSearchPoints + dailySetPoints + morePromotionsPoints
|
||||||
|
|
||||||
|
return {
|
||||||
|
dailySetPoints,
|
||||||
|
morePromotionsPoints,
|
||||||
|
desktopSearchPoints,
|
||||||
|
mobileSearchPoints,
|
||||||
|
totalEarnablePoints
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-BROWSER-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total earnable points with mobile app
|
||||||
|
* @returns {number} Total earnable points
|
||||||
|
*/
|
||||||
|
async getAppEarnablePoints(accessToken: string) {
|
||||||
|
try {
|
||||||
|
const points = {
|
||||||
|
readToEarn: 0,
|
||||||
|
checkIn: 0,
|
||||||
|
totalEarnablePoints: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const eligibleOffers = [
|
||||||
|
'ENUS_readarticle3_30points',
|
||||||
|
'Gamification_Sapphire_DailyCheckIn'
|
||||||
|
]
|
||||||
|
|
||||||
|
const data = await this.getDashboardData()
|
||||||
|
let geoLocale = data.userProfile.attributes.country
|
||||||
|
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us'
|
||||||
|
|
||||||
|
const userDataRequest: AxiosRequestConfig = {
|
||||||
|
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me?channel=SAAndroid&options=613',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'X-Rewards-Country': geoLocale,
|
||||||
|
'X-Rewards-Language': 'en'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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') {
|
||||||
|
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()) {
|
||||||
|
points.checkIn = parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
points.totalEarnablePoints = points.readToEarn + points.checkIn
|
||||||
|
|
||||||
|
return points
|
||||||
|
} catch (error) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current point amount
|
||||||
|
* @returns {number} Current total point amount
|
||||||
|
*/
|
||||||
|
async getCurrentPoints(): Promise<number> {
|
||||||
|
try {
|
||||||
|
const data = await this.getDashboardData()
|
||||||
|
|
||||||
|
return data.userStatus.availablePoints
|
||||||
|
} catch (error) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse quiz data from provided page
|
||||||
|
* @param {Page} page Playwright page
|
||||||
|
* @returns {QuizData} Quiz data object
|
||||||
|
*/
|
||||||
async getQuizData(page: Page): Promise<QuizData> {
|
async getQuizData(page: Page): Promise<QuizData> {
|
||||||
try {
|
try {
|
||||||
const html = await page.content()
|
const html = await page.content()
|
||||||
@@ -123,102 +284,33 @@ export default class BrowserFunc {
|
|||||||
const quizData = JSON.parse(match[1])
|
const quizData = JSON.parse(match[1])
|
||||||
return quizData
|
return quizData
|
||||||
} else {
|
} 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 {
|
} 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) {
|
} 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')
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSearchPoints(page: Page): Promise<Counters> {
|
|
||||||
const dashboardData = await this.getDashboardData(page) // Always fetch newest data
|
|
||||||
|
|
||||||
return dashboardData.userStatus.counters
|
|
||||||
}
|
|
||||||
|
|
||||||
async getEarnablePoints(data: DashboardData, page: null | Page = null): Promise<number> {
|
|
||||||
try {
|
|
||||||
// Fetch new data if page is provided
|
|
||||||
if (page) {
|
|
||||||
data = await this.getDashboardData(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
// These only include the points from tasks that the script can complete!
|
|
||||||
let totalEarnablePoints = 0
|
|
||||||
|
|
||||||
// Desktop Search Points
|
|
||||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
|
||||||
|
|
||||||
// Mobile Search Points
|
|
||||||
if (data.userStatus.counters.mobileSearch?.length) {
|
|
||||||
data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Daily Set
|
|
||||||
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
|
||||||
|
|
||||||
// More Promotions
|
|
||||||
data.morePromotions.forEach(x => {
|
|
||||||
// Only count points from supported activities
|
|
||||||
if (['quiz', 'urlreward'].includes(x.activityType)) {
|
|
||||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return totalEarnablePoints
|
|
||||||
} catch (error) {
|
|
||||||
throw this.bot.log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCurrentPoints(data: DashboardData, page: null | Page = null): Promise<number> {
|
|
||||||
try {
|
|
||||||
// Fetch new data if page is provided
|
|
||||||
if (page) {
|
|
||||||
data = await this.getDashboardData(page)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data.userStatus.availablePoints
|
|
||||||
} catch (error) {
|
|
||||||
throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadSesion(email: string): Promise<string> {
|
|
||||||
const sessionDir = path.join(__dirname, this.bot.config.sessionPath, email)
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create session dir
|
|
||||||
if (!fs.existsSync(sessionDir)) {
|
|
||||||
await fs.promises.mkdir(sessionDir, { recursive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessionDir
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error as string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('#rqHeaderCredits', { visible: true, timeout: 10_000 })
|
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 })
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} 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
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkQuizCompleted(page: Page): Promise<boolean> {
|
async checkQuizCompleted(page: Page): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('#quizCompleteContainer', { visible: true, timeout: 2000 })
|
await page.waitForSelector('#quizCompleteContainer', { state: 'visible', timeout: 2000 })
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -227,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 html = await page.content()
|
||||||
const $ = load(html)
|
const $ = load(html)
|
||||||
|
|
||||||
@@ -245,10 +337,24 @@ export default class BrowserFunc {
|
|||||||
selector = `a[href*="${element.attribs.href}"]`
|
selector = `a[href*="${element.attribs.href}"]`
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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
|
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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
import { load } from 'cheerio'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
|
||||||
@@ -12,78 +13,105 @@ export default class BrowserUtil {
|
|||||||
|
|
||||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||||
|
{ selector: '.ext-secondary.ext-button', label: '"Skip for now" Button' },
|
||||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||||
{ selector: '#iNext', label: 'iNext' },
|
{ selector: '#iNext', label: 'iNext' },
|
||||||
{ selector: '#iLooksGood', label: 'iLooksGood' },
|
{ selector: '#iLooksGood', label: 'iLooksGood' },
|
||||||
{ selector: '#idSIButton9', label: 'idSIButton9' },
|
{ 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
|
const dismissTasks = buttons.map(async (button) => {
|
||||||
|
|
||||||
for (const button of buttons) {
|
|
||||||
try {
|
try {
|
||||||
const element = await page.waitForSelector(button.selector, { visible: true, timeout: 1000 })
|
const element = page.locator(button.selector)
|
||||||
if (element) {
|
if (await element.first().isVisible({ timeout: 1000 })) {
|
||||||
await element.click()
|
await element.first().click({ timeout: 1000 })
|
||||||
result = true
|
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
continue
|
// Ignore errors and continue
|
||||||
}
|
}
|
||||||
}
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
return result
|
const results = await Promise.allSettled(dismissTasks)
|
||||||
}
|
return results.some(result => result.status === 'fulfilled' && result.value === true)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getLatestTab(page: Page): Promise<Page> {
|
async getLatestTab(page: Page): Promise<Page> {
|
||||||
try {
|
try {
|
||||||
await this.bot.utils.wait(500)
|
await this.bot.utils.wait(1000)
|
||||||
|
|
||||||
const browser = page.browser()
|
const browser = page.context()
|
||||||
const pages = await browser.pages()
|
const pages = browser.pages()
|
||||||
const newTab = pages[pages.length - 1]
|
const newTab = pages[pages.length - 1]
|
||||||
|
|
||||||
if (newTab) {
|
if (newTab) {
|
||||||
return newTab
|
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) {
|
} 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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTabs(page: Page) {
|
||||||
|
try {
|
||||||
|
const browser = page.context()
|
||||||
|
const pages = browser.pages()
|
||||||
|
|
||||||
|
const homeTab = pages[1]
|
||||||
|
let homeTabURL: URL
|
||||||
|
|
||||||
|
if (!homeTab) {
|
||||||
|
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(this.bot.isMobile, 'GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const workerTab = pages[2]
|
||||||
|
if (!workerTab) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Worker tab could not be found!', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
homeTab: homeTab,
|
||||||
|
workerTab: workerTab
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (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,25 +1,38 @@
|
|||||||
{
|
{
|
||||||
"baseURL": "https://rewards.bing.com",
|
"baseURL": "https://rewards.bing.com",
|
||||||
"sessionPath": "sessions",
|
"sessionPath": "sessions",
|
||||||
"headless": false,
|
"headless": true,
|
||||||
|
"parallel": true,
|
||||||
"runOnZeroPoints": false,
|
"runOnZeroPoints": false,
|
||||||
"clusters": 1,
|
"clusters": 1,
|
||||||
|
"saveFingerprint": {
|
||||||
|
"mobile": false,
|
||||||
|
"desktop": false
|
||||||
|
},
|
||||||
"workers": {
|
"workers": {
|
||||||
"doDailySet": true,
|
"doDailySet": true,
|
||||||
"doMorePromotions": true,
|
"doMorePromotions": true,
|
||||||
"doPunchCards": true,
|
"doPunchCards": true,
|
||||||
"doDesktopSearch": true,
|
"doDesktopSearch": true,
|
||||||
"doMobileSearch": true
|
"doMobileSearch": true,
|
||||||
|
"doDailyCheckIn": true,
|
||||||
|
"doReadToEarn": true
|
||||||
},
|
},
|
||||||
|
"searchOnBingLocalQueries": false,
|
||||||
|
"globalTimeout": "30s",
|
||||||
"searchSettings": {
|
"searchSettings": {
|
||||||
"useGeoLocaleQueries": false,
|
"useGeoLocaleQueries": false,
|
||||||
"scrollRandomResults": true,
|
"scrollRandomResults": true,
|
||||||
"clickRandomResults": true,
|
"clickRandomResults": true,
|
||||||
"searchDelay": {
|
"searchDelay": {
|
||||||
"min": 10000,
|
"min": "3min",
|
||||||
"max": 20000
|
"max": "5min"
|
||||||
}
|
},
|
||||||
|
"retryMobileSearchAmount": 2
|
||||||
},
|
},
|
||||||
|
"logExcludeFunc": [
|
||||||
|
"SEARCH-CLOSE-TABS"
|
||||||
|
],
|
||||||
"webhook": {
|
"webhook": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"url": ""
|
"url": ""
|
||||||
|
|||||||
1
src/crontab.template
Normal file
1
src/crontab.template
Normal file
@@ -0,0 +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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
|
||||||
@@ -8,8 +8,11 @@ import { Poll } from './activities/Poll'
|
|||||||
import { Quiz } from './activities/Quiz'
|
import { Quiz } from './activities/Quiz'
|
||||||
import { ThisOrThat } from './activities/ThisOrThat'
|
import { ThisOrThat } from './activities/ThisOrThat'
|
||||||
import { UrlReward } from './activities/UrlReward'
|
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 {
|
export default class Activities {
|
||||||
@@ -19,9 +22,9 @@ export default class Activities {
|
|||||||
this.bot = bot
|
this.bot = bot
|
||||||
}
|
}
|
||||||
|
|
||||||
doSearch = async (page: Page, data: DashboardData, mobile: boolean): Promise<void> => {
|
doSearch = async (page: Page, data: DashboardData): Promise<void> => {
|
||||||
const search = new Search(this.bot)
|
const search = new Search(this.bot)
|
||||||
await search.doSearch(page, data, mobile)
|
await search.doSearch(page, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
doABC = async (page: Page): Promise<void> => {
|
doABC = async (page: Page): Promise<void> => {
|
||||||
@@ -49,4 +52,19 @@ export default class Activities {
|
|||||||
await urlReward.doUrlReward(page)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
doDailyCheckIn = async (accessToken: string, data: DashboardData): Promise<void> => {
|
||||||
|
const dailyCheckIn = new DailyCheckIn(this.bot)
|
||||||
|
await dailyCheckIn.doDailyCheckIn(accessToken, data)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
import readline from 'readline'
|
import readline from 'readline'
|
||||||
|
import * as crypto from 'crypto'
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
import { saveSessionData } from '../util/Load'
|
||||||
|
|
||||||
|
import { OAuth } from '../interface/OAuth'
|
||||||
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
output: process.stdout
|
output: process.stdout
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
export class Login {
|
export class Login {
|
||||||
private bot: MicrosoftRewardsBot
|
private bot: MicrosoftRewardsBot
|
||||||
|
private clientId: string = '0000000040170455'
|
||||||
|
private authBaseUrl: string = 'https://login.live.com/oauth20_authorize.srf'
|
||||||
|
private redirectUrl: string = 'https://login.live.com/oauth20_desktop.srf'
|
||||||
|
private tokenUrl: string = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token'
|
||||||
|
private scope: string = 'service::prod.rewardsplatform.microsoft.com::MBI_SSL'
|
||||||
|
|
||||||
constructor(bot: MicrosoftRewardsBot) {
|
constructor(bot: MicrosoftRewardsBot) {
|
||||||
this.bot = bot
|
this.bot = bot
|
||||||
@@ -20,81 +30,186 @@ export class Login {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Navigate to the Bing login page
|
// Navigate to the Bing login page
|
||||||
await page.goto('https://login.live.com/')
|
await page.goto('https://rewards.bing.com/signin')
|
||||||
|
|
||||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 }).then(() => true).catch(() => false)
|
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) {
|
if (!isLoggedIn) {
|
||||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { visible: true, timeout: 10_000 }).then(() => true).catch(() => false)
|
|
||||||
if (isLocked) {
|
|
||||||
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
|
||||||
throw new Error('Account has been locked!')
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.waitForSelector('#loginHeader', { visible: true, timeout: 10_000 })
|
|
||||||
|
|
||||||
await this.execLogin(page, email, password)
|
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 {
|
} 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
|
// Check if logged in to bing
|
||||||
await this.checkBingLogin(page)
|
await this.checkBingLogin(page)
|
||||||
|
|
||||||
|
// Save session
|
||||||
|
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
||||||
|
|
||||||
// We're done logging in
|
// 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) {
|
} catch (error) {
|
||||||
// Throw and don't continue
|
// 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) {
|
private async execLogin(page: Page, email: string, password: string) {
|
||||||
await page.type('#i0116', email)
|
|
||||||
await page.click('#idSIButton9')
|
|
||||||
|
|
||||||
this.bot.log('LOGIN', 'Email entered successfully')
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('#i0118', { visible: true, timeout: 2000 })
|
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)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
await page.type('#i0118', password)
|
// Check if account is locked
|
||||||
await page.click('#idSIButton9')
|
await this.checkAccountLocked(page)
|
||||||
|
|
||||||
|
await this.bot.browser.utils.reloadBadPage(page)
|
||||||
|
await this.checkLoggedIn(page)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.bot.log('LOGIN', '2FA code required')
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred: ' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const code = await new Promise<string>((resolve) => {
|
private async enterEmail(page: Page, email: string) {
|
||||||
rl.question('Enter 2FA code:\n', (input) => {
|
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 2_000 }).catch(() => null)
|
||||||
rl.close()
|
if (emailPrefilled) {
|
||||||
resolve(input)
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
|
||||||
})
|
return
|
||||||
})
|
|
||||||
|
|
||||||
await page.type('input[name="otc"]', code)
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
this.bot.log('LOGIN', 'Password entered successfully')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentURL = new URL(page.url())
|
await page.fill('#i0116', email)
|
||||||
|
await page.click('#idSIButton9')
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
|
||||||
|
}
|
||||||
|
|
||||||
while (currentURL.pathname !== '/' || currentURL.hostname !== 'account.microsoft.com') {
|
private async enterPassword(page: Page, password: string) {
|
||||||
|
try {
|
||||||
|
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
await page.fill('#i0118', password)
|
||||||
|
await page.click('#idSIButton9')
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
|
||||||
|
} catch {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entry failed or 2FA required')
|
||||||
|
await this.handle2FA(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handle2FA(page: Page) {
|
||||||
|
try {
|
||||||
|
const numberToPress = await this.get2FACode(page)
|
||||||
|
if (numberToPress) {
|
||||||
|
// Authentictor App verification
|
||||||
|
await this.authAppVerification(page, numberToPress)
|
||||||
|
} else {
|
||||||
|
// SMS verification
|
||||||
|
await this.authSMSVerification(page)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', `2FA handling failed: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async get2FACode(page: Page): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
|
||||||
|
return await element.textContent()
|
||||||
|
} catch {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async authAppVerification(page: Page, numberToPress: string | null) {
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
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: 60_000 })
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Login successfully approved!')
|
||||||
|
break
|
||||||
|
} catch {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async authSMSVerification(page: Page) {
|
||||||
|
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) => {
|
||||||
|
rl.close()
|
||||||
|
resolve(input)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.fill('input[name="otc"]', code)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', '2FA code entered successfully')
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkLoggedIn(page: Page) {
|
||||||
|
const targetHostname = 'rewards.bing.com'
|
||||||
|
const targetPathname = '/'
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-constant-condition
|
||||||
|
while (true) {
|
||||||
await this.bot.browser.utils.tryDismissAllMessages(page)
|
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||||
currentURL.href = page.url()
|
const currentURL = new URL(page.url())
|
||||||
|
if (currentURL.hostname === targetHostname && currentURL.pathname === targetPathname) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for login to complete
|
// Wait for login to complete
|
||||||
await page.waitForSelector('html[data-role-name="MeePortal"]', { timeout: 10_000 })
|
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Successfully logged into the rewards portal')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkBingLogin(page: Page): Promise<void> {
|
private async checkBingLogin(page: Page): Promise<void> {
|
||||||
try {
|
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')
|
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
|
const maxIterations = 5
|
||||||
@@ -103,12 +218,12 @@ export class Login {
|
|||||||
const currentUrl = new URL(page.url())
|
const currentUrl = new URL(page.url())
|
||||||
|
|
||||||
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
|
if (currentUrl.hostname === 'www.bing.com' && currentUrl.pathname === '/') {
|
||||||
await this.bot.utils.wait(3000)
|
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||||
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
|
|
||||||
|
|
||||||
const loggedIn = await this.checkBingLoginStatus(page)
|
const loggedIn = await this.checkBingLoginStatus(page)
|
||||||
if (loggedIn) {
|
// If mobile browser, skip this step
|
||||||
this.bot.log('LOGIN-BING', 'Bing login verification passed!')
|
if (loggedIn || this.bot.isMobile) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing login verification passed!')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,17 +232,74 @@ export class Login {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} 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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkBingLoginStatus(page: Page): Promise<boolean> {
|
private async checkBingLoginStatus(page: Page): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('#id_n', { timeout: 10_000 })
|
await page.waitForSelector('#id_n', { timeout: 5000 })
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMobileAccessToken(page: Page, email: string) {
|
||||||
|
const authorizeUrl = new URL(this.authBaseUrl)
|
||||||
|
|
||||||
|
authorizeUrl.searchParams.append('response_type', 'code')
|
||||||
|
authorizeUrl.searchParams.append('client_id', this.clientId)
|
||||||
|
authorizeUrl.searchParams.append('redirect_uri', this.redirectUrl)
|
||||||
|
authorizeUrl.searchParams.append('scope', this.scope)
|
||||||
|
authorizeUrl.searchParams.append('state', crypto.randomBytes(16).toString('hex'))
|
||||||
|
authorizeUrl.searchParams.append('access_type', 'offline_access')
|
||||||
|
authorizeUrl.searchParams.append('login_hint', email)
|
||||||
|
|
||||||
|
await page.goto(authorizeUrl.href)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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: AxiosRequestConfig = {
|
||||||
|
url: this.tokenUrl,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
data: body.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenResponse = await this.bot.axios.request(tokenRequest)
|
||||||
|
const tokenData: OAuth = await tokenResponse.data
|
||||||
|
|
||||||
|
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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||||
|
|
||||||
@@ -18,49 +18,71 @@ export class Workers {
|
|||||||
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
||||||
|
|
||||||
if (!activitiesUncompleted.length) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve Activities
|
// 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)
|
await this.solveActivities(page, activitiesUncompleted)
|
||||||
|
|
||||||
this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
|
// Always return to the homepage if not already
|
||||||
|
await this.bot.browser.func.goHome(page)
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have been completed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Punch Card
|
// Punch Card
|
||||||
async doPunchCard(page: Page, data: DashboardData) {
|
async doPunchCard(page: Page, data: DashboardData) {
|
||||||
|
|
||||||
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
|
const punchCardsUncompleted = data.punchCards?.filter(x => x.parentPromotion && !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
|
||||||
|
|
||||||
if (!punchCardsUncompleted.length) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const punchCard of punchCardsUncompleted) {
|
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
|
const activitiesUncompleted = punchCard.childPromotions.filter(x => !x.complete) // Only return uncompleted activities
|
||||||
|
|
||||||
// Solve 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}"`)
|
||||||
|
|
||||||
const browser = page.browser()
|
|
||||||
page = await browser.newPage()
|
|
||||||
|
|
||||||
// Got to punch card index page in a new tab
|
// Got to punch card index page in a new tab
|
||||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||||
|
|
||||||
|
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||||
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { })
|
||||||
|
|
||||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||||
|
|
||||||
// Close the punch card index page
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
await page.close()
|
|
||||||
|
|
||||||
this.bot.log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
const pages = page.context().pages()
|
||||||
|
|
||||||
|
if (pages.length > 3) {
|
||||||
|
await page.close()
|
||||||
|
} else {
|
||||||
|
await this.bot.browser.func.goHome(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// More Promotions
|
||||||
@@ -72,55 +94,67 @@ export class Workers {
|
|||||||
morePromotions.push(data.promotionalItem as unknown as MorePromotion)
|
morePromotions.push(data.promotionalItem as unknown as MorePromotion)
|
||||||
}
|
}
|
||||||
|
|
||||||
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
const activitiesUncompleted = morePromotions?.filter(x => !x.complete && x.pointProgressMax > 0 && x.exclusiveLockedFeatureStatus !== 'locked') ?? []
|
||||||
|
|
||||||
if (!activitiesUncompleted.length) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve Activities
|
// Solve Activities
|
||||||
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
|
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'Started solving "More Promotions" items')
|
||||||
|
|
||||||
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
await this.solveActivities(page, activitiesUncompleted)
|
await this.solveActivities(page, activitiesUncompleted)
|
||||||
|
|
||||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
|
// Always return to the homepage if not already
|
||||||
|
await this.bot.browser.func.goHome(page)
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve all the different types of activities
|
// Solve all the different types of activities
|
||||||
private async solveActivities(page: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
private async solveActivities(activityPage: Page, activities: PromotionalItem[] | MorePromotion[], punchCard?: PunchCard) {
|
||||||
|
const activityInitial = activityPage.url() // Homepage for Daily/More and Index for promotions
|
||||||
|
|
||||||
for (const activity of activities) {
|
for (const activity of activities) {
|
||||||
try {
|
try {
|
||||||
|
// Reselect the worker page
|
||||||
|
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||||
|
|
||||||
let selector = `[data-bi-id="${activity.offerId}"]`
|
const pages = activityPage.context().pages()
|
||||||
|
if (pages.length > 3) {
|
||||||
|
await activityPage.close()
|
||||||
|
|
||||||
if (punchCard) {
|
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
||||||
selector = await this.bot.browser.func.getPunchCardActivity(page, activity)
|
|
||||||
|
|
||||||
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
|
||||||
|
|
||||||
// Promotion
|
|
||||||
if (activity.priority === 1) {
|
|
||||||
selector = '#promo-item'
|
|
||||||
} else {
|
|
||||||
selector = `[data-bi-id="${activity.name}"]`
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for element to load
|
await this.bot.utils.wait(1000)
|
||||||
await page.waitForSelector(selector, { timeout: 10_000 })
|
|
||||||
|
|
||||||
// Click element, it will be opened in a new tab
|
if (activityPage.url() !== activityInitial) {
|
||||||
await page.click(selector)
|
await activityPage.goto(activityInitial)
|
||||||
|
}
|
||||||
|
|
||||||
// Cooldown
|
|
||||||
await this.bot.utils.wait(4000)
|
|
||||||
|
|
||||||
// Select the new activity page
|
let selector = `[data-bi-id^="${activity.offerId}"] .pointLink:not(.contentContainer .pointLink)`
|
||||||
const activityPage = await this.bot.browser.utils.getLatestTab(page)
|
|
||||||
|
|
||||||
// Wait for body to load
|
if (punchCard) {
|
||||||
await activityPage.waitForSelector('body', { timeout: 10_000 })
|
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
||||||
|
|
||||||
|
} else if (activity.name.toLowerCase().includes('membercenter') || activity.name.toLowerCase().includes('exploreonbing')) {
|
||||||
|
selector = `[data-bi-id^="${activity.name}"] .pointLink:not(.contentContainer .pointLink)`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(2000)
|
||||||
|
|
||||||
switch (activity.promotionType) {
|
switch (activity.promotionType) {
|
||||||
// Quiz (Poll, Quiz or ABC)
|
// Quiz (Poll, Quiz or ABC)
|
||||||
@@ -130,23 +164,31 @@ export class Workers {
|
|||||||
case 10:
|
case 10:
|
||||||
// Normal poll
|
// Normal poll
|
||||||
if (activity.destinationUrl.toLowerCase().includes('pollscenarioid')) {
|
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)
|
await this.bot.activities.doPoll(activityPage)
|
||||||
} else { // ABC
|
} 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)
|
await this.bot.activities.doABC(activityPage)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
// This Or That Quiz (Usually 50 points)
|
// This Or That Quiz (Usually 50 points)
|
||||||
case 50:
|
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)
|
await this.bot.activities.doThisOrThat(activityPage)
|
||||||
break
|
break
|
||||||
|
|
||||||
// Quizzes are usually 30-40 points
|
// Quizzes are usually 30-40 points
|
||||||
default:
|
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)
|
await this.bot.activities.doQuiz(activityPage)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -154,22 +196,34 @@ export class Workers {
|
|||||||
|
|
||||||
// UrlReward (Visit)
|
// UrlReward (Visit)
|
||||||
case 'urlreward':
|
case 'urlreward':
|
||||||
this.bot.log('ACTIVITY', `Found activity type: "UrlReward" title: "${activity.title}"`)
|
// Search on Bing are subtypes of "urlreward"
|
||||||
await this.bot.activities.doUrlReward(activityPage)
|
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
|
break
|
||||||
|
|
||||||
// Misc, Usually UrlReward Type
|
// Unsupported types
|
||||||
default:
|
default:
|
||||||
this.bot.log('ACTIVITY', `Found activity type: "Misc" title: "${activity.title}"`)
|
this.bot.log(this.bot.isMobile, 'ACTIVITY', `Skipped activity "${activity.title}" | Reason: Unsupported type: "${activity.promotionType}"!`, 'warn')
|
||||||
await this.bot.activities.doUrlReward(activityPage)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cooldown
|
// Cooldown
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
} catch (error) {
|
} 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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,31 +6,31 @@ import { Workers } from '../Workers'
|
|||||||
export class ABC extends Workers {
|
export class ABC extends Workers {
|
||||||
|
|
||||||
async doABC(page: Page) {
|
async doABC(page: Page) {
|
||||||
this.bot.log('ABC', 'Trying to complete poll')
|
this.bot.log(this.bot.isMobile, 'ABC', 'Trying to complete poll')
|
||||||
|
|
||||||
try {
|
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
|
// Don't loop more than 15 in case unable to solve, would lock otherwise
|
||||||
const maxIterations = 15
|
const maxIterations = 15
|
||||||
let i
|
let i
|
||||||
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
|
for (i = 0; i < maxIterations && !$('span.rw_icon').length; i++) {
|
||||||
await page.waitForSelector('.wk_OptionClickClass', { visible: true, timeout: 10_000 })
|
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10_000 })
|
||||||
|
|
||||||
const answers = $('.wk_OptionClickClass')
|
const answers = $('.wk_OptionClickClass')
|
||||||
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
|
const answer = answers[this.bot.utils.randomNumber(0, 2)]?.attribs['id']
|
||||||
|
|
||||||
await page.waitForSelector(`#${answer}`, { visible: true, timeout: 10_000 })
|
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10_000 })
|
||||||
|
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
await page.click(`#${answer}`) // Click answer
|
await page.click(`#${answer}`) // Click answer
|
||||||
|
|
||||||
await this.bot.utils.wait(4000)
|
await this.bot.utils.wait(4000)
|
||||||
await page.waitForSelector('div.wk_button', { visible: true, timeout: 10_000 })
|
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10_000 })
|
||||||
await page.click('div.wk_button') // Click next question button
|
await page.click('div.wk_button') // Click next question button
|
||||||
|
|
||||||
page = await this.bot.browser.utils.getLatestTab(page)
|
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)
|
await this.bot.utils.wait(1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +38,14 @@ export class ABC extends Workers {
|
|||||||
await page.close()
|
await page.close()
|
||||||
|
|
||||||
if (i === maxIterations) {
|
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 {
|
} else {
|
||||||
this.bot.log('ABC', 'Completed the ABC successfully')
|
this.bot.log(this.bot.isMobile, 'ABC', 'Completed the ABC successfully')
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await page.close()
|
await page.close()
|
||||||
this.bot.log('ABC', 'An error occurred:' + error, 'error')
|
this.bot.log(this.bot.isMobile, 'ABC', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
src/functions/activities/DailyCheckIn.ts
Normal file
48
src/functions/activities/DailyCheckIn.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
|
import { DashboardData } from '../../interface/DashboardData'
|
||||||
|
|
||||||
|
|
||||||
|
export class DailyCheckIn extends Workers {
|
||||||
|
public async doDailyCheckIn(accessToken: string, data: DashboardData) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'Starting Daily Check In')
|
||||||
|
|
||||||
|
try {
|
||||||
|
let geoLocale = data.userProfile.attributes.country
|
||||||
|
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us'
|
||||||
|
|
||||||
|
const jsonData = {
|
||||||
|
amount: 1,
|
||||||
|
country: geoLocale,
|
||||||
|
id: randomBytes(64).toString('hex'),
|
||||||
|
type: 101,
|
||||||
|
attributes: {
|
||||||
|
offerid: 'Gamification_Sapphire_DailyCheckIn'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimRequest: AxiosRequestConfig = {
|
||||||
|
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Rewards-Country': geoLocale,
|
||||||
|
'X-Rewards-Language': 'en'
|
||||||
|
},
|
||||||
|
data: JSON.stringify(jsonData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimResponse = await this.bot.axios.request(claimRequest)
|
||||||
|
const claimedPoint = parseInt((await claimResponse.data).response?.activity?.p) ?? 0
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today')
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'DAILY-CHECK-IN', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@ import { Workers } from '../Workers'
|
|||||||
export class Poll extends Workers {
|
export class Poll extends Workers {
|
||||||
|
|
||||||
async doPoll(page: Page) {
|
async doPoll(page: Page) {
|
||||||
this.bot.log('POLL', 'Trying to complete poll')
|
this.bot.log(this.bot.isMobile, 'POLL', 'Trying to complete poll')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
const buttonId = `#btoption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||||
|
|
||||||
await page.waitForSelector(buttonId, { visible: true, timeout: 10_000 })
|
await page.waitForSelector(buttonId, { state: 'visible', timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
await page.click(buttonId)
|
await page.click(buttonId)
|
||||||
@@ -19,10 +19,10 @@ export class Poll extends Workers {
|
|||||||
await this.bot.utils.wait(4000)
|
await this.bot.utils.wait(4000)
|
||||||
await page.close()
|
await page.close()
|
||||||
|
|
||||||
this.bot.log('POLL', 'Completed the poll successfully')
|
this.bot.log(this.bot.isMobile, 'POLL', 'Completed the poll successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await page.close()
|
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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,15 +6,15 @@ import { Workers } from '../Workers'
|
|||||||
export class Quiz extends Workers {
|
export class Quiz extends Workers {
|
||||||
|
|
||||||
async doQuiz(page: Page) {
|
async doQuiz(page: Page) {
|
||||||
this.bot.log('QUIZ', 'Trying to complete quiz')
|
this.bot.log(this.bot.isMobile, 'QUIZ', 'Trying to complete quiz')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if the quiz has been started or not
|
// Check if the quiz has been started or not
|
||||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
|
||||||
if (quizNotStarted) {
|
if (quizNotStarted) {
|
||||||
await page.click('#rqStartQuiz')
|
await page.click('#rqStartQuiz')
|
||||||
} else {
|
} 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)
|
await this.bot.utils.wait(2000)
|
||||||
@@ -29,7 +29,7 @@ export class Quiz extends Workers {
|
|||||||
const answers: string[] = []
|
const answers: string[] = []
|
||||||
|
|
||||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
|
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
|
||||||
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
|
const answerAttribute = await answerSelector?.evaluate(el => el.getAttribute('iscorrectoption'))
|
||||||
|
|
||||||
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
|
if (answerAttribute && answerAttribute.toLowerCase() === 'true') {
|
||||||
@@ -39,7 +39,7 @@ export class Quiz extends Workers {
|
|||||||
|
|
||||||
// Click the answers
|
// Click the answers
|
||||||
for (const answer of answers) {
|
for (const answer of answers) {
|
||||||
await page.waitForSelector(answer, { visible: true, timeout: 2000 })
|
await page.waitForSelector(answer, { state: 'visible', timeout: 2000 })
|
||||||
|
|
||||||
// Click the answer on page
|
// Click the answer on page
|
||||||
await page.click(answer)
|
await page.click(answer)
|
||||||
@@ -47,19 +47,19 @@ export class Quiz extends Workers {
|
|||||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||||
if (!refreshSuccess) {
|
if (!refreshSuccess) {
|
||||||
await page.close()
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other type quiz
|
// Other type quiz, lightspeed
|
||||||
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
|
} else if ([2, 3, 4].includes(quizData.numberOfOptions)) {
|
||||||
quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data
|
quizData = await this.bot.browser.func.getQuizData(page) // Refresh Quiz Data
|
||||||
const correctOption = quizData.correctAnswer
|
const correctOption = quizData.correctAnswer
|
||||||
|
|
||||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||||
|
|
||||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { visible: true, timeout: 10_000 })
|
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
|
||||||
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
|
const dataOption = await answerSelector?.evaluate(el => el.getAttribute('data-option'))
|
||||||
|
|
||||||
if (dataOption === correctOption) {
|
if (dataOption === correctOption) {
|
||||||
@@ -69,7 +69,7 @@ export class Quiz extends Workers {
|
|||||||
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||||
if (!refreshSuccess) {
|
if (!refreshSuccess) {
|
||||||
await page.close()
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,10 +81,11 @@ export class Quiz extends Workers {
|
|||||||
// Done with
|
// Done with
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
await page.close()
|
await page.close()
|
||||||
this.bot.log('QUIZ', 'Completed the quiz successfully')
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'QUIZ', 'Completed the quiz successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await page.close()
|
await page.close()
|
||||||
this.bot.log('QUIZ', 'An error occurred:' + error, 'error')
|
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
73
src/functions/activities/ReadToEarn.ts
Normal file
73
src/functions/activities/ReadToEarn.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { randomBytes } from 'crypto'
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
|
import { DashboardData } from '../../interface/DashboardData'
|
||||||
|
|
||||||
|
|
||||||
|
export class ReadToEarn extends Workers {
|
||||||
|
public async doReadToEarn(accessToken: string, data: DashboardData) {
|
||||||
|
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: AxiosRequestConfig = {
|
||||||
|
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me',
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'X-Rewards-Country': geoLocale,
|
||||||
|
'X-Rewards-Language': 'en'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const userDataResponse = await this.bot.axios.request(userDataRequest)
|
||||||
|
const userData = (await userDataResponse.data).response
|
||||||
|
let userBalance = userData.balance
|
||||||
|
|
||||||
|
const jsonData = {
|
||||||
|
amount: 1,
|
||||||
|
country: geoLocale,
|
||||||
|
id: '1',
|
||||||
|
type: 101,
|
||||||
|
attributes: {
|
||||||
|
offerid: 'ENUS_readarticle3_30points'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const articleCount = 10
|
||||||
|
for (let i = 0; i < articleCount; ++i) {
|
||||||
|
jsonData.id = randomBytes(64).toString('hex')
|
||||||
|
const claimRequest = {
|
||||||
|
url: 'https://prod.rewardsplatform.microsoft.com/dapi/me/activities',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Rewards-Country': geoLocale,
|
||||||
|
'X-Rewards-Language': 'en'
|
||||||
|
},
|
||||||
|
data: JSON.stringify(jsonData)
|
||||||
|
}
|
||||||
|
|
||||||
|
const claimResponse = await this.bot.axios.request(claimRequest)
|
||||||
|
const newBalance = (await claimResponse.data).response.balance
|
||||||
|
|
||||||
|
if (newBalance == userBalance) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Read all available articles')
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
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.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'Completed Read to Earn')
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'READ-TO-EARN', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,27 @@
|
|||||||
import { Page } from 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
import axios from 'axios'
|
import { platform } from 'os'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
import { DashboardData, DashboardImpression } from '../../interface/DashboardData'
|
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||||
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
|
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
|
||||||
import { GoogleSearch } from '../../interface/Search'
|
import { GoogleSearch } from '../../interface/Search'
|
||||||
|
|
||||||
|
|
||||||
export class Search extends Workers {
|
export class Search extends Workers {
|
||||||
|
private bingHome = 'https://bing.com'
|
||||||
|
private searchPageURL = ''
|
||||||
|
|
||||||
private searchPageURL = 'https://bing.com'
|
public async doSearch(page: Page, data: DashboardData) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Starting Bing searches')
|
||||||
|
|
||||||
public async doSearch(page: Page, data: DashboardData, mobile: boolean) {
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
this.bot.log('SEARCH-BING', 'Starting bing searches')
|
|
||||||
|
|
||||||
const mobileData = data.userStatus.counters?.mobileSearch ? data.userStatus.counters.mobileSearch[0] : null // Mobile searches
|
let searchCounters: Counters = await this.bot.browser.func.getSearchPoints()
|
||||||
const edgeData = data.userStatus.counters.pcSearch[1] as DashboardImpression // Edge searches
|
let missingPoints = this.calculatePoints(searchCounters)
|
||||||
const genericData = data.userStatus.counters.pcSearch[0] as DashboardImpression // Normal searches
|
|
||||||
|
|
||||||
let missingPoints = (mobile && mobileData) ?
|
if (missingPoints === 0) {
|
||||||
(mobileData.pointProgressMax - mobileData.pointProgress) :
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Bing searches have already been completed')
|
||||||
(edgeData.pointProgressMax - edgeData.pointProgress) + (genericData.pointProgressMax - genericData.pointProgress)
|
|
||||||
|
|
||||||
if (missingPoints == 0) {
|
|
||||||
this.bot.log('SEARCH-BING', `Bing searches for ${mobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,33 +32,27 @@ export class Search extends Workers {
|
|||||||
// Deduplicate the search terms
|
// Deduplicate the search terms
|
||||||
googleSearchQueries = [...new Set(googleSearchQueries)]
|
googleSearchQueries = [...new Set(googleSearchQueries)]
|
||||||
|
|
||||||
// Open a new tab
|
|
||||||
const browser = page.browser()
|
|
||||||
const searchPage = await browser.newPage()
|
|
||||||
|
|
||||||
// Go to bing
|
// Go to bing
|
||||||
await searchPage.goto(this.searchPageURL)
|
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
|
let maxLoop = 0 // If the loop hits 10 this when not gaining any points, we're assuming it's stuck. If it ddoesn't continue after 5 more searches with alternative queries, abort search
|
||||||
|
|
||||||
const queries: string[] = []
|
const queries: string[] = []
|
||||||
googleSearchQueries.forEach(x => queries.push(x.topic, ...x.related))
|
// Mobile search doesn't seem to like related queries?
|
||||||
|
googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : queries.push(x.topic, ...x.related) })
|
||||||
|
|
||||||
// Loop over Google search queries
|
// Loop over Google search queries
|
||||||
for (let i = 0; i < queries.length; i++) {
|
for (let i = 0; i < queries.length; i++) {
|
||||||
const query = queries[i] as string
|
const query = queries[i] as string
|
||||||
|
|
||||||
this.bot.log('SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query} | Mobile: ${mobile}`)
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query}`)
|
||||||
|
|
||||||
const newData = await this.bingSearch(page, searchPage, query)
|
searchCounters = await this.bingSearch(page, query)
|
||||||
|
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||||
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
|
|
||||||
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
|
|
||||||
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
|
|
||||||
|
|
||||||
const newMissingPoints = (mobile && newMobileData) ?
|
|
||||||
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
|
|
||||||
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
|
|
||||||
|
|
||||||
// If the new point amount is the same as before
|
// If the new point amount is the same as before
|
||||||
if (newMissingPoints == missingPoints) {
|
if (newMissingPoints == missingPoints) {
|
||||||
@@ -72,21 +63,32 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
missingPoints = newMissingPoints
|
missingPoints = newMissingPoints
|
||||||
|
|
||||||
if (missingPoints == 0) {
|
if (missingPoints === 0) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only for mobile searches
|
||||||
|
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
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we didn't gain points for 10 iterations, assume it's stuck
|
// If we didn't gain points for 10 iterations, assume it's stuck
|
||||||
if (maxLoop > 10) {
|
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
|
maxLoop = 0 // Reset to 0 so we can retry with related searches below
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only for mobile searches
|
||||||
|
if (missingPoints > 0 && this.bot.isMobile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// If we still got remaining search queries, generate extra ones
|
// If we still got remaining search queries, generate extra ones
|
||||||
if (missingPoints > 0) {
|
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
|
let i = 0
|
||||||
while (missingPoints > 0) {
|
while (missingPoints > 0) {
|
||||||
@@ -97,16 +99,10 @@ export class Search extends Workers {
|
|||||||
if (relatedTerms.length > 3) {
|
if (relatedTerms.length > 3) {
|
||||||
// Search for the first 2 related terms
|
// Search for the first 2 related terms
|
||||||
for (const term of relatedTerms.slice(1, 3)) {
|
for (const term of relatedTerms.slice(1, 3)) {
|
||||||
this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${mobile}`)
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term}`)
|
||||||
const newData = await this.bingSearch(page, searchPage, query.topic)
|
|
||||||
|
|
||||||
const newMobileData = newData.mobileSearch ? newData.mobileSearch[0] : null // Mobile searches
|
searchCounters = await this.bingSearch(page, term)
|
||||||
const newEdgeData = newData.pcSearch[1] as DashboardImpression // Edge searches
|
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||||
const newGenericData = newData.pcSearch[0] as DashboardImpression // Normal searches
|
|
||||||
|
|
||||||
const newMissingPoints = (mobile && newMobileData) ?
|
|
||||||
(newMobileData.pointProgressMax - newMobileData.pointProgress) :
|
|
||||||
(newEdgeData.pointProgressMax - newEdgeData.pointProgress) + (newGenericData.pointProgressMax - newGenericData.pointProgress)
|
|
||||||
|
|
||||||
// If the new point amount is the same as before
|
// If the new point amount is the same as before
|
||||||
if (newMissingPoints == missingPoints) {
|
if (newMissingPoints == missingPoints) {
|
||||||
@@ -118,13 +114,13 @@ export class Search extends Workers {
|
|||||||
missingPoints = newMissingPoints
|
missingPoints = newMissingPoints
|
||||||
|
|
||||||
// If we satisfied the searches
|
// If we satisfied the searches
|
||||||
if (missingPoints == 0) {
|
if (missingPoints === 0) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
|
// Try 5 more times, then we tried a total of 15 times, fair to say it's stuck
|
||||||
if (maxLoop > 5) {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,53 +128,79 @@ 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(page: Page, searchPage: Page, query: string) {
|
private async bingSearch(searchPage: Page, query: string) {
|
||||||
|
const platformControlKey = platform() === 'darwin' ? 'Meta' : 'Control'
|
||||||
|
|
||||||
// Try a max of 5 times
|
// Try a max of 5 times
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
try {
|
try {
|
||||||
|
// This page had already been set to the Bing.com page or the previous search listing, we just need to select it
|
||||||
|
searchPage = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||||
|
|
||||||
|
// Go to top of the page
|
||||||
|
await searchPage.evaluate(() => {
|
||||||
|
window.scrollTo(0, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.bot.utils.wait(500)
|
||||||
|
|
||||||
const searchBar = '#sb_form_q'
|
const searchBar = '#sb_form_q'
|
||||||
await searchPage.waitForSelector(searchBar, { visible: true, timeout: 10_000 })
|
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||||
await searchPage.click(searchBar) // Focus on the textarea
|
await searchPage.click(searchBar) // Focus on the textarea
|
||||||
await this.bot.utils.wait(500)
|
await this.bot.utils.wait(500)
|
||||||
await searchPage.keyboard.down('Control')
|
await searchPage.keyboard.down(platformControlKey)
|
||||||
await searchPage.keyboard.press('A')
|
await searchPage.keyboard.press('A')
|
||||||
await searchPage.keyboard.press('Backspace')
|
await searchPage.keyboard.press('Backspace')
|
||||||
await searchPage.keyboard.up('Control')
|
await searchPage.keyboard.up(platformControlKey)
|
||||||
await searchPage.keyboard.type(query)
|
await searchPage.keyboard.type(query)
|
||||||
await searchPage.keyboard.press('Enter')
|
await searchPage.keyboard.press('Enter')
|
||||||
|
|
||||||
|
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) {
|
if (this.bot.config.searchSettings.scrollRandomResults) {
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
await this.randomScroll(searchPage)
|
await this.randomScroll(resultPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.bot.config.searchSettings.clickRandomResults) {
|
if (this.bot.config.searchSettings.clickRandomResults) {
|
||||||
await this.bot.utils.wait(2000)
|
await this.bot.utils.wait(2000)
|
||||||
await this.clickRandomLink(searchPage)
|
await this.clickRandomLink(resultPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
|
// Delay between searches
|
||||||
|
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(page)
|
return await this.bot.browser.func.getSearchPoints()
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (i === 5) {
|
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
|
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)
|
||||||
|
await this.closeTabs(lastTab)
|
||||||
|
|
||||||
await this.bot.utils.wait(4000)
|
await this.bot.utils.wait(4000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(page)
|
return await this.bot.browser.func.getSearchPoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
||||||
@@ -187,7 +209,7 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
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) {
|
while (queryCount > queryTerms.length) {
|
||||||
i += 1
|
i += 1
|
||||||
@@ -204,19 +226,20 @@ 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))
|
const data: GoogleTrends = JSON.parse((await response.data).slice(5))
|
||||||
|
|
||||||
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
|
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
|
||||||
queryTerms.push({
|
queryTerms.push({
|
||||||
topic: topic.title.query.toLowerCase(),
|
topic: topic.title.query.toLowerCase(),
|
||||||
related: topic.relatedQueries.map(x => x.query.toLocaleLowerCase())
|
related: topic.relatedQueries.map(x => x.query.toLowerCase())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,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[]
|
return response.data[1] as string[]
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.bot.log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING-RELATED', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,82 +276,109 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
private async randomScroll(page: Page) {
|
private async randomScroll(page: Page) {
|
||||||
try {
|
try {
|
||||||
// Press the arrow down key to scroll
|
const viewportHeight = await page.evaluate(() => window.innerHeight)
|
||||||
for (let i = 0; i < this.bot.utils.randomNumber(5, 100); i++) {
|
const totalHeight = await page.evaluate(() => document.body.scrollHeight)
|
||||||
await page.keyboard.press('ArrowDown')
|
const randomScrollPosition = Math.floor(Math.random() * (totalHeight - viewportHeight))
|
||||||
}
|
|
||||||
|
await page.evaluate((scrollPos) => {
|
||||||
|
window.scrollTo(0, scrollPos)
|
||||||
|
}, randomScrollPosition)
|
||||||
|
|
||||||
} catch (error) {
|
} 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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async clickRandomLink(page: Page) {
|
private async clickRandomLink(page: Page) {
|
||||||
try {
|
try {
|
||||||
const searchListingURL = new URL(page.url()) // Get searchPage info before clicking
|
await page.click('#b_results .b_algo h2', { timeout: 2000 }).catch(() => { }) // Since we don't really care if it did it or not
|
||||||
|
|
||||||
await page.click('#b_results .b_algo h2').catch(() => { }) // Since we don't really care if it did it or not
|
// Only used if the browser is not the edge browser (continue on Edge popup)
|
||||||
|
await this.closeContinuePopup(page)
|
||||||
|
|
||||||
// Wait for website to load
|
// Stay for 10 seconds for page to load and "visit"
|
||||||
await this.bot.utils.wait(3000)
|
await this.bot.utils.wait(10_000)
|
||||||
|
|
||||||
// Will get current tab if no new one is created
|
// Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click
|
||||||
let lastTab = await this.bot.browser.utils.getLatestTab(page)
|
let lastTab = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
// Wait for the body of the new page to be loaded
|
let lastTabURL = new URL(lastTab.url()) // Get new tab info, this is the website we're visiting
|
||||||
await lastTab.waitForSelector('body', { timeout: 10_000 }).catch(() => { })
|
|
||||||
|
|
||||||
// Check if the tab is closed or not
|
// Check if the URL is different from the original one, don't loop more than 5 times.
|
||||||
if (!lastTab.isClosed()) {
|
let i = 0
|
||||||
let lastTabURL = new URL(lastTab.url()) // Get new tab info
|
while (lastTabURL.href !== this.searchPageURL && i < 5) {
|
||||||
|
|
||||||
// Check if the URL is different from the original one, don't loop more than 5 times.
|
await this.closeTabs(lastTab)
|
||||||
let i = 0
|
|
||||||
while (lastTabURL.href !== searchListingURL.href && i < 5) {
|
|
||||||
// If hostname is still bing, (Bing images/news etc)
|
|
||||||
if (lastTabURL.hostname == searchListingURL.hostname) {
|
|
||||||
await lastTab.goBack()
|
|
||||||
|
|
||||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
|
// End of loop, refresh lastPage
|
||||||
|
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
||||||
|
lastTabURL = new URL(lastTab.url()) // Get new tab info
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
// If "goBack" didn't return to search listing (due to redirects)
|
} catch (error) {
|
||||||
if (lastTabURL.hostname !== searchListingURL.hostname) {
|
this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||||
await lastTab.goto(this.searchPageURL)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else { // No longer on bing, likely opened a new tab, close this tab
|
private async closeTabs(lastTab: Page) {
|
||||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
|
const browser = lastTab.context()
|
||||||
lastTabURL = new URL(lastTab.url())
|
const tabs = browser.pages()
|
||||||
|
|
||||||
const tabs = await (page.browser()).pages() // Get all tabs open
|
try {
|
||||||
|
if (tabs.length > 2) {
|
||||||
|
// If more than 2 tabs are open, close the last tab
|
||||||
|
|
||||||
// If the browser has more than 3 tabs open, it has opened a new one, we need to close this one.
|
await lastTab.close()
|
||||||
if (tabs.length > 3) {
|
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', `More than 2 were open, closed the last tab: "${new URL(lastTab.url()).host}"`)
|
||||||
// Make sure the page is still open!
|
|
||||||
if (!lastTab.isClosed()) {
|
|
||||||
await lastTab.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (lastTabURL.href !== searchListingURL.href) {
|
} else if (tabs.length === 1) {
|
||||||
|
// If only 1 tab is open, open a new one to search in
|
||||||
|
|
||||||
await lastTab.goBack()
|
const newPage = await browser.newPage()
|
||||||
|
await this.bot.utils.wait(1000)
|
||||||
|
|
||||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Get last opened tab
|
await newPage.goto(this.bingHome)
|
||||||
lastTabURL = new URL(lastTab.url())
|
await this.bot.utils.wait(3000)
|
||||||
|
this.searchPageURL = newPage.url()
|
||||||
|
|
||||||
// If "goBack" didn't return to search listing (due to redirects)
|
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'There was only 1 tab open, crated a new one')
|
||||||
if (lastTabURL.hostname !== searchListingURL.hostname) {
|
} else {
|
||||||
await lastTab.goto(this.searchPageURL)
|
// Else reset the last tab back to the search listing or Bing.com
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const mobileData = counters.mobileSearch?.[0] // Mobile searches
|
||||||
|
const genericData = counters.pcSearch?.[0] // Normal searches
|
||||||
|
const edgeData = counters.pcSearch?.[1] // Edge searches
|
||||||
|
|
||||||
|
const missingPoints = (this.bot.isMobile && mobileData)
|
||||||
|
? mobileData.pointProgressMax - mobileData.pointProgress
|
||||||
|
: (edgeData ? edgeData.pointProgressMax - edgeData.pointProgress : 0)
|
||||||
|
+ (genericData ? genericData.pointProgressMax - genericData.pointProgress : 0)
|
||||||
|
|
||||||
|
return missingPoints
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (error) {
|
||||||
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, '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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,28 +6,41 @@ import { Workers } from '../Workers'
|
|||||||
export class ThisOrThat extends Workers {
|
export class ThisOrThat extends Workers {
|
||||||
|
|
||||||
async doThisOrThat(page: Page) {
|
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')
|
||||||
|
|
||||||
|
|
||||||
return
|
|
||||||
try {
|
try {
|
||||||
// Check if the quiz has been started or not
|
// Check if the quiz has been started or not
|
||||||
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { visible: true, timeout: 3000 }).then(() => true).catch(() => false)
|
const quizNotStarted = await page.waitForSelector('#rqStartQuiz', { state: 'visible', timeout: 2000 }).then(() => true).catch(() => false)
|
||||||
if (quizNotStarted) {
|
if (quizNotStarted) {
|
||||||
await page.click('#rqStartQuiz')
|
await page.click('#rqStartQuiz')
|
||||||
} else {
|
} 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)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
// Solving
|
// Solving
|
||||||
const quizData = await this.bot.browser.func.getQuizData(page)
|
const quizData = await this.bot.browser.func.getQuizData(page)
|
||||||
quizData // correctAnswer property is always null?
|
const questionsRemaining = quizData.maxQuestions - (quizData.currentQuestionNumber - 1) // Amount of questions remaining
|
||||||
|
|
||||||
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrthat successfully')
|
for (let question = 0; question < questionsRemaining; question++) {
|
||||||
|
// Since there's no solving logic yet, randomly guess to complete
|
||||||
|
const buttonId = `#rqAnswerOption${Math.floor(this.bot.utils.randomNumber(0, 1))}`
|
||||||
|
await page.click(buttonId)
|
||||||
|
|
||||||
|
const refreshSuccess = await this.bot.browser.func.waitForQuizRefresh(page)
|
||||||
|
if (!refreshSuccess) {
|
||||||
|
await page.close()
|
||||||
|
this.bot.log(this.bot.isMobile, 'QUIZ', 'An error occurred, refresh was unsuccessful', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await page.close()
|
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 'puppeteer'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,16 +6,17 @@ import { Workers } from '../Workers'
|
|||||||
export class UrlReward extends Workers {
|
export class UrlReward extends Workers {
|
||||||
|
|
||||||
async doUrlReward(page: Page) {
|
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 {
|
try {
|
||||||
// After waiting, close the page
|
this.bot.utils.wait(2000)
|
||||||
|
|
||||||
await page.close()
|
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) {
|
} catch (error) {
|
||||||
await page.close()
|
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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
138
src/functions/queries.json
Normal file
138
src/functions/queries.json
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Houses near you",
|
||||||
|
"queries": [
|
||||||
|
"Houses near me"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Feeling symptoms?",
|
||||||
|
"queries": [
|
||||||
|
"Rash on forearm",
|
||||||
|
"Stuffy nose",
|
||||||
|
"Tickling cough"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Get your shopping done faster",
|
||||||
|
"queries": [
|
||||||
|
"Buy PS5",
|
||||||
|
"Buy Xbox",
|
||||||
|
"Chair deals"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Translate anything",
|
||||||
|
"queries": [
|
||||||
|
"Translate welcome home to Korean",
|
||||||
|
"Translate welcome home to Japanese",
|
||||||
|
"Translate goodbye to Japanese"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Search the lyrics of a song",
|
||||||
|
"queries": [
|
||||||
|
"Debarge rhythm of the night lyrics"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Let's watch that movie again!",
|
||||||
|
"queries": [
|
||||||
|
"Alien movie",
|
||||||
|
"Aliens movie",
|
||||||
|
"Alien 3 movie",
|
||||||
|
"Predator movie"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Plan a quick getaway",
|
||||||
|
"queries": [
|
||||||
|
"Flights Amsterdam to Tokyo",
|
||||||
|
"Flights New York to Tokyo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Discover open job roles",
|
||||||
|
"queries": [
|
||||||
|
"jobs at Microsoft",
|
||||||
|
"Microsoft Job Openings",
|
||||||
|
"Jobs near me",
|
||||||
|
"jobs at Boeing worked"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "You can track your package",
|
||||||
|
"queries": [
|
||||||
|
"USPS tracking"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Find somewhere new to explore",
|
||||||
|
"queries": [
|
||||||
|
"Directions to Berlin",
|
||||||
|
"Directions to Tokyo",
|
||||||
|
"Directions to New York"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Too tired to cook tonight?",
|
||||||
|
"queries": [
|
||||||
|
"KFC near me",
|
||||||
|
"Burger King near me",
|
||||||
|
"McDonalds near me"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Quickly convert your money",
|
||||||
|
"queries": [
|
||||||
|
"convert 250 USD to yen",
|
||||||
|
"convert 500 USD to yen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Learn to cook a new recipe",
|
||||||
|
"queries": [
|
||||||
|
"How to cook ratatouille",
|
||||||
|
"How to cook lasagna"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Find places to stay!",
|
||||||
|
"queries": [
|
||||||
|
"Hotels Berlin Germany",
|
||||||
|
"Hotels Amsterdam Netherlands"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "How's the economy?",
|
||||||
|
"queries": [
|
||||||
|
"sp 500"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Who won?",
|
||||||
|
"queries": [
|
||||||
|
"braves score"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Gaming time",
|
||||||
|
"queries": [
|
||||||
|
"Overwatch video game",
|
||||||
|
"Call of duty video game"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Expand your vocabulary",
|
||||||
|
"queries": [
|
||||||
|
"definition definition"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "What time is it?",
|
||||||
|
"queries": [
|
||||||
|
"Japan time",
|
||||||
|
"New York time"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
245
src/index.ts
245
src/index.ts
@@ -1,4 +1,5 @@
|
|||||||
import cluster from 'cluster'
|
import cluster from 'cluster'
|
||||||
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import Browser from './browser/Browser'
|
import Browser from './browser/Browser'
|
||||||
import BrowserFunc from './browser/BrowserFunc'
|
import BrowserFunc from './browser/BrowserFunc'
|
||||||
@@ -6,13 +7,15 @@ import BrowserUtil from './browser/BrowserUtil'
|
|||||||
|
|
||||||
import { log } from './util/Logger'
|
import { log } from './util/Logger'
|
||||||
import Util from './util/Utils'
|
import Util from './util/Utils'
|
||||||
import { loadAccounts, loadConfig } from './util/Load'
|
import { loadAccounts, loadConfig, saveSessionData } from './util/Load'
|
||||||
|
|
||||||
import { Login } from './functions/Login'
|
import { Login } from './functions/Login'
|
||||||
import { Workers } from './functions/Workers'
|
import { Workers } from './functions/Workers'
|
||||||
import Activities from './functions/Activities'
|
import Activities from './functions/Activities'
|
||||||
|
|
||||||
import { Account } from './interface/Account'
|
import { Account } from './interface/Account'
|
||||||
|
import Axios from './util/Axios'
|
||||||
|
|
||||||
|
|
||||||
// Main bot class
|
// Main bot class
|
||||||
export class MicrosoftRewardsBot {
|
export class MicrosoftRewardsBot {
|
||||||
@@ -24,15 +27,25 @@ export class MicrosoftRewardsBot {
|
|||||||
func: BrowserFunc,
|
func: BrowserFunc,
|
||||||
utils: BrowserUtil
|
utils: BrowserUtil
|
||||||
}
|
}
|
||||||
|
public isMobile: boolean
|
||||||
|
public homePage!: Page
|
||||||
|
|
||||||
|
private pointsCanCollect: number = 0
|
||||||
|
private pointsInitial: number = 0
|
||||||
|
|
||||||
private collectedPoints: number = 0
|
|
||||||
private activeWorkers: number
|
private activeWorkers: number
|
||||||
|
private mobileRetryAttempts: number
|
||||||
private browserFactory: Browser = new Browser(this)
|
private browserFactory: Browser = new Browser(this)
|
||||||
private accounts: Account[]
|
private accounts: Account[]
|
||||||
private workers: Workers
|
private workers: Workers
|
||||||
private login = new Login(this)
|
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.log = log
|
||||||
|
|
||||||
this.accounts = []
|
this.accounts = []
|
||||||
@@ -44,6 +57,7 @@ export class MicrosoftRewardsBot {
|
|||||||
}
|
}
|
||||||
this.config = loadConfig()
|
this.config = loadConfig()
|
||||||
this.activeWorkers = this.config.clusters
|
this.activeWorkers = this.config.clusters
|
||||||
|
this.mobileRetryAttempts = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
async initialize() {
|
async initialize() {
|
||||||
@@ -51,7 +65,7 @@ export class MicrosoftRewardsBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
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
|
// Only cluster when there's more than 1 cluster demanded
|
||||||
if (this.config.clusters > 1) {
|
if (this.config.clusters > 1) {
|
||||||
@@ -61,12 +75,12 @@ export class MicrosoftRewardsBot {
|
|||||||
this.runWorker()
|
this.runWorker()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.runTasks(this.accounts)
|
await this.runTasks(this.accounts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private runMaster() {
|
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)
|
const accountChunks = this.utils.chunkArray(this.accounts, this.config.clusters)
|
||||||
|
|
||||||
@@ -79,18 +93,18 @@ export class MicrosoftRewardsBot {
|
|||||||
cluster.on('exit', (worker, code) => {
|
cluster.on('exit', (worker, code) => {
|
||||||
this.activeWorkers -= 1
|
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
|
// Check if all workers have exited
|
||||||
if (this.activeWorkers === 0) {
|
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)
|
process.exit(0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private runWorker() {
|
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
|
// Receive the chunk of accounts from the master
|
||||||
process.on('message', async ({ chunk }) => {
|
process.on('message', async ({ chunk }) => {
|
||||||
await this.runTasks(chunk)
|
await this.runTasks(chunk)
|
||||||
@@ -99,142 +113,207 @@ export class MicrosoftRewardsBot {
|
|||||||
|
|
||||||
private async runTasks(accounts: Account[]) {
|
private async runTasks(accounts: Account[]) {
|
||||||
for (const account of accounts) {
|
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
|
this.axios = new Axios(account.proxy)
|
||||||
await this.Desktop(account)
|
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
|
return mobileInstance.Mobile(account)
|
||||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
})()
|
||||||
continue
|
])
|
||||||
|
} else {
|
||||||
|
this.isMobile = false
|
||||||
|
await this.Desktop(account)
|
||||||
|
|
||||||
|
this.isMobile = true
|
||||||
|
await this.Mobile(account)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile Searches
|
log('main', 'MAIN-WORKER', `Completed tasks for account ${account.email}`, 'log', 'green')
|
||||||
await this.Mobile(account)
|
|
||||||
|
|
||||||
log('MAIN-WORKER', `Completed tasks for account ${account.email}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log('MAIN-PRIMARY', 'Completed tasks for ALL accounts')
|
log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green')
|
||||||
log('MAIN-PRIMARY', 'All workers destroyed!')
|
process.exit()
|
||||||
process.exit(0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Desktop
|
// Desktop
|
||||||
async Desktop(account: Account) {
|
async Desktop(account: Account) {
|
||||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, false)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
const page = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
let pages = await browser.pages()
|
|
||||||
|
|
||||||
// If for some reason the browser initializes with more than 2 pages, close these
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
while (pages.length > 2) {
|
|
||||||
await pages[0]?.close()
|
|
||||||
pages = await browser.pages()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log into proxy
|
// Login into MS Rewards, then go to rewards homepage
|
||||||
await page.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
await this.login.login(this.homePage, account.email, account.password)
|
||||||
|
|
||||||
log('MAIN', 'Starting DESKTOP browser')
|
await this.browser.func.goHome(this.homePage)
|
||||||
|
|
||||||
// Login into MS Rewards
|
const data = await this.browser.func.getDashboardData()
|
||||||
await this.login.login(page, account.email, account.password)
|
|
||||||
|
|
||||||
const wentHome = await this.browser.func.goHome(page)
|
this.pointsInitial = data.userStatus.availablePoints
|
||||||
if (!wentHome) {
|
|
||||||
throw log('MAIN', 'Unable to get dashboard page', 'error')
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await this.browser.func.getDashboardData(page)
|
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${this.pointsInitial}`)
|
||||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
|
||||||
|
|
||||||
const earnablePoints = await this.browser.func.getEarnablePoints(data)
|
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||||
this.collectedPoints = earnablePoints
|
|
||||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
|
// 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 runOnZeroPoints is false and 0 points to earn, don't continue
|
||||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||||
log('MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping')
|
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||||
|
|
||||||
// Close desktop browser
|
// Close desktop browser
|
||||||
return await browser.close()
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Open a new tab to where the tasks are going to be completed
|
||||||
|
const workerPage = await browser.newPage()
|
||||||
|
|
||||||
|
// Go to homepage on worker page
|
||||||
|
await this.browser.func.goHome(workerPage)
|
||||||
|
|
||||||
// Complete daily set
|
// Complete daily set
|
||||||
if (this.config.workers.doDailySet) {
|
if (this.config.workers.doDailySet) {
|
||||||
await this.workers.doDailySet(page, data)
|
await this.workers.doDailySet(workerPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete more promotions
|
// Complete more promotions
|
||||||
if (this.config.workers.doMorePromotions) {
|
if (this.config.workers.doMorePromotions) {
|
||||||
await this.workers.doMorePromotions(page, data)
|
await this.workers.doMorePromotions(workerPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Complete punch cards
|
// Complete punch cards
|
||||||
if (this.config.workers.doPunchCards) {
|
if (this.config.workers.doPunchCards) {
|
||||||
await this.workers.doPunchCard(page, data)
|
await this.workers.doPunchCard(workerPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do desktop searches
|
// Do desktop searches
|
||||||
if (this.config.workers.doDesktopSearch) {
|
if (this.config.workers.doDesktopSearch) {
|
||||||
await this.activities.doSearch(page, data, false)
|
await this.activities.doSearch(workerPage, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save cookies
|
||||||
|
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||||
|
|
||||||
// Close desktop browser
|
// Close desktop browser
|
||||||
await browser.close()
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile
|
// Mobile
|
||||||
async Mobile(account: Account) {
|
async Mobile(account: Account) {
|
||||||
const browser = await this.browserFactory.createBrowser(account.email, account.proxy, true)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
const page = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
let pages = await browser.pages()
|
|
||||||
|
|
||||||
// If for some reason the browser initializes with more than 2 pages, close these
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
while (pages.length > 2) {
|
|
||||||
await pages[0]?.close()
|
|
||||||
pages = await browser.pages()
|
|
||||||
}
|
|
||||||
// Log into proxy
|
|
||||||
await page.authenticate({ username: account.proxy.username, password: account.proxy.password })
|
|
||||||
|
|
||||||
log('MAIN', 'Starting MOBILE 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)
|
||||||
|
|
||||||
// Login into MS Rewards
|
await this.browser.func.goHome(this.homePage)
|
||||||
await this.login.login(page, account.email, account.password)
|
|
||||||
|
|
||||||
await this.browser.func.goHome(page)
|
const data = await this.browser.func.getDashboardData()
|
||||||
|
|
||||||
const data = await this.browser.func.getDashboardData(page)
|
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||||
|
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||||
|
|
||||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
this.pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||||
if (!data.userStatus.counters.mobileSearch) {
|
|
||||||
log('MAIN', 'No mobile searches found, stopping')
|
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
|
// Close mobile browser
|
||||||
return await browser.close()
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do read to earn
|
||||||
|
if (this.config.workers.doReadToEarn) {
|
||||||
|
await this.activities.doReadToEarn(this.accessToken, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do mobile searches
|
// Do mobile searches
|
||||||
if (this.config.workers.doMobileSearch) {
|
if (this.config.workers.doMobileSearch) {
|
||||||
await this.activities.doSearch(page, data, true)
|
// 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)
|
||||||
|
|
||||||
|
await this.activities.doSearch(workerPage, data)
|
||||||
|
|
||||||
|
// Fetch current search points
|
||||||
|
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||||
|
|
||||||
|
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.browser.func.closeBrowser(browser, account.email)
|
||||||
|
|
||||||
|
// 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')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch new points
|
const afterPointAmount = await this.browser.func.getCurrentPoints()
|
||||||
const earnablePoints = await this.browser.func.getEarnablePoints(data, page)
|
|
||||||
|
|
||||||
// If the new earnable is 0, means we got all the points, else retract
|
log(this.isMobile, 'MAIN-POINTS', `The script collected ${afterPointAmount - this.pointsInitial} points today`)
|
||||||
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)
|
|
||||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
|
||||||
|
|
||||||
// Close mobile browser
|
// Close mobile browser
|
||||||
await browser.close()
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const rewardsBot = new MicrosoftRewardsBot(false)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await rewardsBot.initialize()
|
||||||
|
await rewardsBot.run()
|
||||||
|
} catch (error) {
|
||||||
|
log(false, 'MAIN-ERROR', `Error running desktop bot: ${error}`, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bot = new MicrosoftRewardsBot()
|
// Start the bots
|
||||||
|
main().catch(error => {
|
||||||
// Initialize accounts first and then start the bot
|
log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error')
|
||||||
bot.initialize().then(() => {
|
process.exit(1)
|
||||||
bot.run()
|
|
||||||
})
|
})
|
||||||
@@ -5,6 +5,7 @@ export interface Account {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccountProxy {
|
export interface AccountProxy {
|
||||||
|
proxyAxios: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
port: number;
|
port: number;
|
||||||
password: string;
|
password: string;
|
||||||
|
|||||||
226
src/interface/AppUserData.ts
Normal file
226
src/interface/AppUserData.ts
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
export interface AppUserData {
|
||||||
|
response: Response;
|
||||||
|
correlationId: string;
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Response {
|
||||||
|
profile: Profile;
|
||||||
|
balance: number;
|
||||||
|
counters: null;
|
||||||
|
promotions: Promotion[];
|
||||||
|
catalog: null;
|
||||||
|
goal_item: GoalItem;
|
||||||
|
activities: null;
|
||||||
|
cashback: null;
|
||||||
|
orders: Order[];
|
||||||
|
rebateProfile: null;
|
||||||
|
rebatePayouts: null;
|
||||||
|
giveProfile: GiveProfile;
|
||||||
|
autoRedeemProfile: null;
|
||||||
|
autoRedeemItem: null;
|
||||||
|
thirdPartyProfile: null;
|
||||||
|
notifications: null;
|
||||||
|
waitlist: null;
|
||||||
|
autoOpenFlyout: null;
|
||||||
|
coupons: null;
|
||||||
|
recommendedAffordableCatalog: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiveProfile {
|
||||||
|
give_user: string;
|
||||||
|
give_organization: { [key: string]: GiveOrganization | null };
|
||||||
|
first_give_optin: string;
|
||||||
|
last_give_optout: string;
|
||||||
|
give_lifetime_balance: string;
|
||||||
|
give_lifetime_donation_balance: string;
|
||||||
|
give_balance: string;
|
||||||
|
form: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiveOrganization {
|
||||||
|
give_organization_donation_points: number;
|
||||||
|
give_organization_donation_point_to_currency_ratio: number;
|
||||||
|
give_organization_donation_currency: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoalItem {
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
price: number;
|
||||||
|
attributes: GoalItemAttributes;
|
||||||
|
config: GoalItemConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoalItemAttributes {
|
||||||
|
category: string;
|
||||||
|
CategoryDescription: string;
|
||||||
|
'desc.group_text': string;
|
||||||
|
'desc.legal_text'?: string;
|
||||||
|
'desc.sc_description': string;
|
||||||
|
'desc.sc_title': string;
|
||||||
|
display_order: string;
|
||||||
|
ExtraLargeImage: string;
|
||||||
|
group: string;
|
||||||
|
group_image: string;
|
||||||
|
group_sc_image: string;
|
||||||
|
group_title: string;
|
||||||
|
hidden?: string;
|
||||||
|
large_image: string;
|
||||||
|
large_sc_image: string;
|
||||||
|
medium_image: string;
|
||||||
|
MobileImage: string;
|
||||||
|
original_price: string;
|
||||||
|
Remarks?: string;
|
||||||
|
ShortText?: string;
|
||||||
|
showcase?: string;
|
||||||
|
small_image: string;
|
||||||
|
title: string;
|
||||||
|
cimsid: string;
|
||||||
|
user_defined_goal?: string;
|
||||||
|
disable_bot_redemptions?: string;
|
||||||
|
'desc.large_text'?: string;
|
||||||
|
english_title?: string;
|
||||||
|
etid?: string;
|
||||||
|
sku?: string;
|
||||||
|
coupon_discount?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GoalItemConfig {
|
||||||
|
amount: string;
|
||||||
|
currencyCode: string;
|
||||||
|
isHidden: string;
|
||||||
|
PointToCurrencyConversionRatio: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Order {
|
||||||
|
id: string;
|
||||||
|
t: Date;
|
||||||
|
sku: string;
|
||||||
|
item_snapshot: ItemSnapshot;
|
||||||
|
p: number;
|
||||||
|
s: S;
|
||||||
|
a: A;
|
||||||
|
child_redemption: null;
|
||||||
|
third_party_partner: null;
|
||||||
|
log: Log[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface A {
|
||||||
|
form?: string;
|
||||||
|
OrderId: string;
|
||||||
|
CorrelationId: string;
|
||||||
|
Channel: string;
|
||||||
|
Language: string;
|
||||||
|
Country: string;
|
||||||
|
EvaluationId: string;
|
||||||
|
provider?: string;
|
||||||
|
referenceOrderID?: string;
|
||||||
|
externalRefID?: string;
|
||||||
|
denomination?: string;
|
||||||
|
rewardName?: string;
|
||||||
|
sendEmail?: string;
|
||||||
|
status?: string;
|
||||||
|
createdAt?: Date;
|
||||||
|
bal_before_deduct?: string;
|
||||||
|
bal_after_deduct?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemSnapshot {
|
||||||
|
name: string;
|
||||||
|
provider: string;
|
||||||
|
price: number;
|
||||||
|
attributes: GoalItemAttributes;
|
||||||
|
config: ItemSnapshotConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ItemSnapshotConfig {
|
||||||
|
amount: string;
|
||||||
|
countryCode: string;
|
||||||
|
currencyCode: string;
|
||||||
|
sku: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Log {
|
||||||
|
time: Date;
|
||||||
|
from: From;
|
||||||
|
to: S;
|
||||||
|
reason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum From {
|
||||||
|
Created = 'Created',
|
||||||
|
RiskApproved = 'RiskApproved',
|
||||||
|
RiskReview = 'RiskReview'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum S {
|
||||||
|
Cancelled = 'Cancelled',
|
||||||
|
RiskApproved = 'RiskApproved',
|
||||||
|
RiskReview = 'RiskReview',
|
||||||
|
Shipped = 'Shipped'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Profile {
|
||||||
|
ruid: string;
|
||||||
|
attributes: ProfileAttributes;
|
||||||
|
offline_attributes: OfflineAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProfileAttributes {
|
||||||
|
publisher: string;
|
||||||
|
publisher_upd: Date;
|
||||||
|
creative: string;
|
||||||
|
creative_upd: Date;
|
||||||
|
program: string;
|
||||||
|
program_upd: Date;
|
||||||
|
country: string;
|
||||||
|
country_upd: Date;
|
||||||
|
referrerhash: string;
|
||||||
|
referrerhash_upd: Date;
|
||||||
|
optout_upd: Date;
|
||||||
|
language: string;
|
||||||
|
language_upd: Date;
|
||||||
|
target: string;
|
||||||
|
target_upd: Date;
|
||||||
|
created: Date;
|
||||||
|
created_upd: Date;
|
||||||
|
epuid: string;
|
||||||
|
epuid_upd: Date;
|
||||||
|
goal: string;
|
||||||
|
goal_upd: Date;
|
||||||
|
waitlistattributes: string;
|
||||||
|
waitlistattributes_upd: Date;
|
||||||
|
serpbotscore_upd: Date;
|
||||||
|
iscashbackeligible: string;
|
||||||
|
cbedc: string;
|
||||||
|
rlscpct_upd: Date;
|
||||||
|
give_user: string;
|
||||||
|
rebcpc_upd: Date;
|
||||||
|
SerpBotScore_upd: Date;
|
||||||
|
AdsBotScore_upd: Date;
|
||||||
|
dbs_upd: Date;
|
||||||
|
rbs: string;
|
||||||
|
rbs_upd: Date;
|
||||||
|
iris_segmentation: string;
|
||||||
|
iris_segmentation_upd: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OfflineAttributes {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Promotion {
|
||||||
|
name: string;
|
||||||
|
priority: number;
|
||||||
|
attributes: { [key: string]: string };
|
||||||
|
tags: Tag[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Tag {
|
||||||
|
AllowTrialUser = 'allow_trial_user',
|
||||||
|
ExcludeGivePcparent = 'exclude_give_pcparent',
|
||||||
|
ExcludeGlobalConfig = 'exclude_global_config',
|
||||||
|
ExcludeHidden = 'exclude_hidden',
|
||||||
|
LOCString = 'locString',
|
||||||
|
NonGlobalConfig = 'non_global_config'
|
||||||
|
}
|
||||||
@@ -2,11 +2,21 @@ export interface Config {
|
|||||||
baseURL: string;
|
baseURL: string;
|
||||||
sessionPath: string;
|
sessionPath: string;
|
||||||
headless: boolean;
|
headless: boolean;
|
||||||
|
parallel: boolean;
|
||||||
runOnZeroPoints: boolean;
|
runOnZeroPoints: boolean;
|
||||||
clusters: number;
|
clusters: number;
|
||||||
workers: Workers;
|
workers: Workers;
|
||||||
|
searchOnBingLocalQueries: boolean;
|
||||||
|
globalTimeout: number | string;
|
||||||
searchSettings: SearchSettings;
|
searchSettings: SearchSettings;
|
||||||
webhook: Webhook;
|
webhook: Webhook;
|
||||||
|
logExcludeFunc: string[];
|
||||||
|
saveFingerprint: ConfigSaveFingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigSaveFingerprint {
|
||||||
|
mobile: boolean;
|
||||||
|
desktop: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchSettings {
|
export interface SearchSettings {
|
||||||
@@ -14,11 +24,12 @@ export interface SearchSettings {
|
|||||||
scrollRandomResults: boolean;
|
scrollRandomResults: boolean;
|
||||||
clickRandomResults: boolean;
|
clickRandomResults: boolean;
|
||||||
searchDelay: SearchDelay;
|
searchDelay: SearchDelay;
|
||||||
|
retryMobileSearchAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchDelay {
|
export interface SearchDelay {
|
||||||
min: number;
|
min: number | string;
|
||||||
max: number;
|
max: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Webhook {
|
export interface Webhook {
|
||||||
@@ -32,4 +43,6 @@ export interface Workers {
|
|||||||
doPunchCards: boolean;
|
doPunchCards: boolean;
|
||||||
doDesktopSearch: boolean;
|
doDesktopSearch: boolean;
|
||||||
doMobileSearch: boolean;
|
doMobileSearch: boolean;
|
||||||
|
doDailyCheckIn: boolean;
|
||||||
|
doReadToEarn: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,11 +351,13 @@ export interface MorePromotion {
|
|||||||
legalText: string;
|
legalText: string;
|
||||||
legalLinkText: string;
|
legalLinkText: string;
|
||||||
deviceType: string;
|
deviceType: string;
|
||||||
|
exclusiveLockedFeatureType: string;
|
||||||
|
exclusiveLockedFeatureStatus: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PunchCard {
|
export interface PunchCard {
|
||||||
name: string;
|
name: string;
|
||||||
parentPromotion: PromotionalItem;
|
parentPromotion?: PromotionalItem;
|
||||||
childPromotions: PromotionalItem[];
|
childPromotions: PromotionalItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
9
src/interface/OAuth.ts
Normal file
9
src/interface/OAuth.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface OAuth {
|
||||||
|
access_token: string;
|
||||||
|
refresh_token: string;
|
||||||
|
scope: string;
|
||||||
|
expires_in: number;
|
||||||
|
ext_expires_in: number;
|
||||||
|
foci: string;
|
||||||
|
token_type: string;
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
35
src/run_daily.sh
Normal file
35
src/run_daily.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Set up environment variables
|
||||||
|
export PATH=$PATH:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
|
||||||
|
|
||||||
|
# Ensure TZ is set
|
||||||
|
export TZ=${TZ}
|
||||||
|
|
||||||
|
# Change directory to the application directory
|
||||||
|
cd /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
|
# Define the minimum and maximum wait times in seconds
|
||||||
|
MINWAIT=$((5*60)) # 5 minutes
|
||||||
|
MAXWAIT=$((50*60)) # 50 minutes
|
||||||
|
|
||||||
|
# Calculate a random sleep time within the specified range
|
||||||
|
SLEEPTIME=$((MINWAIT + RANDOM % (MAXWAIT - MINWAIT)))
|
||||||
|
|
||||||
|
# Convert the sleep time to minutes for logging
|
||||||
|
SLEEP_MINUTES=$((SLEEPTIME / 60))
|
||||||
|
|
||||||
|
# Log the sleep duration
|
||||||
|
echo "Sleeping for $SLEEP_MINUTES minutes ($SLEEPTIME seconds)..."
|
||||||
|
|
||||||
|
# Sleep for the calculated time
|
||||||
|
sleep $SLEEPTIME
|
||||||
|
|
||||||
|
# Log the start of the script
|
||||||
|
echo "Starting script..."
|
||||||
|
|
||||||
|
# Update config with environment variables before running the script
|
||||||
|
node src/updateConfig.js
|
||||||
|
|
||||||
|
# Execute the Node.js script directly
|
||||||
|
npm run start
|
||||||
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,9 +1,13 @@
|
|||||||
|
import { BrowserContext, Cookie } from 'rebrowser-playwright'
|
||||||
|
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
|
|
||||||
import { Account } from '../interface/Account'
|
|
||||||
import { Config } from '../interface/Config'
|
|
||||||
|
|
||||||
|
import { Account } from '../interface/Account'
|
||||||
|
import { Config, ConfigSaveFingerprint } from '../interface/Config'
|
||||||
|
|
||||||
|
let configCache: Config
|
||||||
|
|
||||||
export function loadAccounts(): Account[] {
|
export function loadAccounts(): Account[] {
|
||||||
try {
|
try {
|
||||||
@@ -25,10 +29,87 @@ export function loadAccounts(): Account[] {
|
|||||||
|
|
||||||
export function loadConfig(): Config {
|
export function loadConfig(): Config {
|
||||||
try {
|
try {
|
||||||
|
if (configCache) {
|
||||||
|
return configCache
|
||||||
|
}
|
||||||
|
|
||||||
const configDir = path.join(__dirname, '../', 'config.json')
|
const configDir = path.join(__dirname, '../', 'config.json')
|
||||||
const config = fs.readFileSync(configDir, 'utf-8')
|
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, saveFingerprint: ConfigSaveFingerprint) {
|
||||||
|
try {
|
||||||
|
// Fetch cookie file
|
||||||
|
const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`)
|
||||||
|
|
||||||
|
let cookies: Cookie[] = []
|
||||||
|
if (fs.existsSync(cookieFile)) {
|
||||||
|
const cookiesData = await fs.promises.readFile(cookieFile, 'utf-8')
|
||||||
|
cookies = JSON.parse(cookiesData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch fingerprint file
|
||||||
|
const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`)
|
||||||
|
|
||||||
|
let fingerprint!: BrowserFingerprintWithHeaders
|
||||||
|
if (((saveFingerprint.desktop && !isMobile) || (saveFingerprint.mobile && isMobile)) && fs.existsSync(fingerprintFile)) {
|
||||||
|
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
||||||
|
fingerprint = JSON.parse(fingerprintData)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cookies: cookies,
|
||||||
|
fingerprint: fingerprint
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveSessionData(sessionPath: string, browser: BrowserContext, email: string, isMobile: boolean): Promise<string> {
|
||||||
|
try {
|
||||||
|
const cookies = await browser.cookies()
|
||||||
|
|
||||||
|
// Fetch path
|
||||||
|
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
||||||
|
|
||||||
|
// Create session dir
|
||||||
|
if (!fs.existsSync(sessionDir)) {
|
||||||
|
await fs.promises.mkdir(sessionDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save cookies to a file
|
||||||
|
await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`), JSON.stringify(cookies))
|
||||||
|
|
||||||
|
return sessionDir
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error as string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function saveFingerprintData(sessionPath: string, email: string, isMobile: boolean, fingerpint: BrowserFingerprintWithHeaders): Promise<string> {
|
||||||
|
try {
|
||||||
|
// Fetch path
|
||||||
|
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
||||||
|
|
||||||
|
// Create session dir
|
||||||
|
if (!fs.existsSync(sessionDir)) {
|
||||||
|
await fs.promises.mkdir(sessionDir, { recursive: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save fingerprint to a file
|
||||||
|
await fs.promises.writeFile(path.join(sessionDir, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`), JSON.stringify(fingerpint))
|
||||||
|
|
||||||
|
return sessionDir
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(error as string)
|
throw new Error(error as string)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,43 @@
|
|||||||
|
import chalk from 'chalk'
|
||||||
|
|
||||||
import { Webhook } from './Webhook'
|
import { Webhook } from './Webhook'
|
||||||
|
import { loadConfig } from './Load'
|
||||||
|
|
||||||
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
|
|
||||||
const currentTime = new Date().toISOString()
|
|
||||||
|
|
||||||
let str = ''
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
// 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) {
|
switch (type) {
|
||||||
case 'warn':
|
case 'warn':
|
||||||
str = `[${currentTime}] [PID: ${process.pid}] [WARN] [${title}] ${message}`
|
applyChalk ? console.warn(applyChalk(str)) : console.warn(str)
|
||||||
console.warn(str)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'error':
|
case 'error':
|
||||||
str = `[${currentTime}] [PID: ${process.pid}] [ERROR] [${title}] ${message}`
|
applyChalk ? console.error(applyChalk(str)) : console.error(str)
|
||||||
console.error(str)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
str = `[${currentTime}] [PID: ${process.pid}] [LOG] [${title}] ${message}`
|
applyChalk ? console.log(applyChalk(str)) : console.log(str)
|
||||||
console.log(str)
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if (str) Webhook(str)
|
|
||||||
}
|
}
|
||||||
@@ -1,42 +1,45 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
|
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||||
|
|
||||||
import { log } from './Logger'
|
import { log } from './Logger'
|
||||||
|
|
||||||
import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil'
|
import { ChromeVersion, EdgeVersion } from '../interface/UserAgentUtil'
|
||||||
|
|
||||||
export async function getUserAgent(mobile: boolean) {
|
const NOT_A_BRAND_VERSION = '99'
|
||||||
const system = getSystemComponents(mobile)
|
|
||||||
const app = await getAppComponents(mobile)
|
|
||||||
|
|
||||||
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} 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}`
|
`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 = {
|
const uaMetadata = {
|
||||||
mobile,
|
isMobile,
|
||||||
platform: mobile ? 'Android' : 'Windows',
|
platform: isMobile ? 'Android' : 'Windows',
|
||||||
fullVersionList: [
|
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: 'Microsoft Edge', version: app['edge_version'] },
|
||||||
{ brand: 'Chromium', version: app['chrome_version'] }
|
{ brand: 'Chromium', version: app['chrome_version'] }
|
||||||
],
|
],
|
||||||
brands: [
|
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: 'Microsoft Edge', version: app['edge_major_version'] },
|
||||||
{ brand: 'Chromium', version: app['chrome_major_version'] }
|
{ brand: 'Chromium', version: app['chrome_major_version'] }
|
||||||
],
|
],
|
||||||
platformVersion,
|
platformVersion,
|
||||||
architecture: mobile ? '' : 'x86',
|
architecture: isMobile ? '' : 'x86',
|
||||||
bitness: mobile ? '' : '64',
|
bitness: isMobile ? '' : '64',
|
||||||
model: ''
|
model: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
return { userAgent: uaTemplate, userAgentMetadata: uaMetadata }
|
return { userAgent: uaTemplate, userAgentMetadata: uaMetadata }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getChromeVersion(): Promise<string> {
|
export async function getChromeVersion(isMobile: boolean): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const request = {
|
const request = {
|
||||||
url: 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions.json',
|
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
|
return data.channels.Stable.version
|
||||||
|
|
||||||
} catch (error) {
|
} 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 {
|
try {
|
||||||
const request = {
|
const request = {
|
||||||
url: 'https://edgeupdates.microsoft.com/api/products',
|
url: 'https://edgeupdates.microsoft.com/api/products',
|
||||||
@@ -75,13 +78,13 @@ export async function getEdgeVersions() {
|
|||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw log('USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error')
|
throw log(isMobile, 'USERAGENT-EDGE-VERSION', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSystemComponents(mobile: boolean): string {
|
export function getSystemComponents(mobile: boolean): string {
|
||||||
const osId: string = mobile ? 'Linux' : 'Windows NT 10.0'
|
const osId: string = mobile ? 'Linux' : 'Windows NT 10.0'
|
||||||
const uaPlatform: string = mobile ? 'Android 13' : 'Win64; x64'
|
const uaPlatform: string = mobile ? `Android 1${Math.floor(Math.random() * 5)}` : 'Win64; x64'
|
||||||
|
|
||||||
if (mobile) {
|
if (mobile) {
|
||||||
return `${uaPlatform}; ${osId}; K`
|
return `${uaPlatform}; ${osId}; K`
|
||||||
@@ -90,16 +93,18 @@ export function getSystemComponents(mobile: boolean): string {
|
|||||||
return `${uaPlatform}; ${osId}`
|
return `${uaPlatform}; ${osId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAppComponents(mobile: boolean) {
|
export async function getAppComponents(isMobile: boolean) {
|
||||||
const versions = await getEdgeVersions()
|
const versions = await getEdgeVersions(isMobile)
|
||||||
const edgeVersion = mobile ? versions.android : versions.windows as string
|
const edgeVersion = isMobile ? versions.android : versions.windows as string
|
||||||
const edgeMajorVersion = edgeVersion?.split('.')[0]
|
const edgeMajorVersion = edgeVersion?.split('.')[0]
|
||||||
|
|
||||||
const chromeVersion = await getChromeVersion()
|
const chromeVersion = await getChromeVersion(isMobile)
|
||||||
const chromeMajorVersion = chromeVersion?.split('.')[0]
|
const chromeMajorVersion = chromeVersion?.split('.')[0]
|
||||||
const chromeReducedVersion = `${chromeMajorVersion}.0.0.0`
|
const chromeReducedVersion = `${chromeMajorVersion}.0.0.0`
|
||||||
|
|
||||||
return {
|
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_version: edgeVersion as string,
|
||||||
edge_major_version: edgeMajorVersion as string,
|
edge_major_version: edgeMajorVersion as string,
|
||||||
chrome_version: chromeVersion as string,
|
chrome_version: chromeVersion as string,
|
||||||
@@ -107,3 +112,32 @@ export async function getAppComponents(mobile: boolean) {
|
|||||||
chrome_reduced_version: chromeReducedVersion 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 {
|
export default class Util {
|
||||||
|
|
||||||
async wait(ms: number): Promise<void> {
|
async wait(ms: number): Promise<void> {
|
||||||
@@ -39,4 +41,12 @@ export default class Util {
|
|||||||
return chunks
|
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 axios from 'axios'
|
||||||
|
|
||||||
import { loadConfig } from './Load'
|
|
||||||
|
|
||||||
|
import { Config } from '../interface/Config'
|
||||||
|
|
||||||
export async function Webhook(content: string) {
|
export async function Webhook(configData: Config, content: string) {
|
||||||
const webhook = loadConfig().webhook
|
const webhook = configData.webhook
|
||||||
|
|
||||||
if (!webhook.enabled || webhook.url.length < 10) return
|
if (!webhook.enabled || webhook.url.length < 10) return
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,8 @@
|
|||||||
"include": [
|
"include": [
|
||||||
"src/**/*.ts",
|
"src/**/*.ts",
|
||||||
"src/accounts.json",
|
"src/accounts.json",
|
||||||
"src/config.json"
|
"src/config.json",
|
||||||
|
"src/functions/queries.json"
|
||||||
],
|
],
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
Reference in New Issue
Block a user