mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 14:03:58 +00:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5fd06d229 | ||
|
|
0ddc964878 | ||
|
|
325bf65b30 | ||
|
|
6f19bd4b0e | ||
|
|
caf6a42a38 | ||
|
|
352d47229b | ||
|
|
9a12ee1ec8 | ||
|
|
b630c3ddda | ||
|
|
287e3897da | ||
|
|
fcf6aba446 | ||
|
|
1102f2ca94 | ||
|
|
82a896e83f | ||
|
|
b0bd1f52c4 | ||
|
|
7e4121e01b | ||
|
|
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 |
@@ -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
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ package-lock.json
|
|||||||
accounts.json
|
accounts.json
|
||||||
notes
|
notes
|
||||||
accounts.dev.json
|
accounts.dev.json
|
||||||
accounts.main.json
|
accounts.main.json
|
||||||
|
.DS_Store
|
||||||
90
Dockerfile
90
Dockerfile
@@ -1,43 +1,47 @@
|
|||||||
# Use an official Node.js runtime as a base image
|
# Use an official Node.js runtime as a base image
|
||||||
FROM node:18
|
FROM node:18
|
||||||
|
|
||||||
# Set the working directory in the container
|
# Set the working directory in the container
|
||||||
WORKDIR /usr/src/microsoft-rewards-script
|
WORKDIR /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
# Install jq
|
# Install necessary dependencies for Playwright and cron
|
||||||
RUN apt-get update && apt-get install -y jq
|
RUN apt-get update && apt-get install -y \
|
||||||
|
jq \
|
||||||
|
cron \
|
||||||
# Copy all files to the working directory
|
gettext-base \
|
||||||
COPY . .
|
xvfb \
|
||||||
|
libgbm-dev \
|
||||||
# Check if "headless" is set to "true" in the config.json file
|
libnss3 \
|
||||||
# DELETE BELOW IF YOU WANT TO RUN THE DOCKER SCRIPT HEADFULL!
|
libasound2 \
|
||||||
RUN HEADLESS=$(cat src/config.json | jq -r .headless) \
|
libxss1 \
|
||||||
&& if [ "$HEADLESS" != "true" ]; then \
|
libatk-bridge2.0-0 \
|
||||||
echo "Error: 'headless' in src/config.json is not true."; \
|
libgtk-3-0 \
|
||||||
exit 1; \
|
tzdata \
|
||||||
fi
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
# Install dependencies including Playwright
|
|
||||||
RUN apt-get install -y \
|
# Copy all files to the working directory
|
||||||
xvfb \
|
COPY . .
|
||||||
libgbm-dev \
|
|
||||||
libnss3 \
|
# Install dependencies, set permissions, and build the script
|
||||||
libasound2 \
|
RUN npm install && \
|
||||||
libxss1 \
|
chmod -R 755 /usr/src/microsoft-rewards-script/node_modules && \
|
||||||
libatk-bridge2.0-0 \
|
npm run pre-build && \
|
||||||
libgtk-3-0 \
|
npm run build
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
# Copy cron file to cron directory
|
||||||
# Install application dependencies
|
COPY src/crontab.template /etc/cron.d/microsoft-rewards-cron.template
|
||||||
RUN npm install
|
|
||||||
|
# Create the log file to be able to run tail
|
||||||
# Build the script
|
RUN touch /var/log/cron.log
|
||||||
RUN npm run build
|
|
||||||
|
# Define the command to run your application with cron optionally
|
||||||
# Install playwright chromium
|
CMD ["sh", "-c", "echo \"$TZ\" > /etc/timezone && \
|
||||||
RUN npx playwright install chromium
|
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
|
||||||
|
dpkg-reconfigure -f noninteractive tzdata && \
|
||||||
# Define the command to run your application
|
envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && \
|
||||||
CMD ["npm", "start"]
|
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"]
|
||||||
63
README.md
63
README.md
@@ -16,10 +16,40 @@ Under development, however mainly for personal use!
|
|||||||
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
|
- 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) ##
|
## Docker (Experimental) ##
|
||||||
1. Download the source code
|
### **Before Starting**
|
||||||
2. Make changes to your `accounts.json` and/or `config.json`
|
|
||||||
3. Run `docker build -t microsoft-rewards-script-docker .`
|
- If you had previously built and run the script locally, **remove** the `/node_modules` and `/dist` folders from your `Microsoft-Rewards-Script` directory.
|
||||||
- Docker container has to be recreated for any changes regarding the `config.json` and/or `accounts.json`!
|
- 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 ##
|
## Config ##
|
||||||
| Setting | Description | Default |
|
| Setting | Description | Default |
|
||||||
@@ -27,19 +57,29 @@ Under development, however mainly for personal use!
|
|||||||
| baseURL | MS Rewards page | `https://rewards.bing.com` |
|
| baseURL | MS Rewards page | `https://rewards.bing.com` |
|
||||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) |
|
| 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) |
|
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) |
|
||||||
|
| parallel | If you want mobile and desktop tasks to run parallel or sequential| `true` |
|
||||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) |
|
| 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) |
|
| 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) |
|
| saveFingerprint.mobile | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||||
|
| saveFingerprint.desktop | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||||
| workers.doDailySet | Complete daily set items | `true` |
|
| workers.doDailySet | Complete daily set items | `true` |
|
||||||
| workers.doMorePromotions | Complete promotional items | `true` |
|
| workers.doMorePromotions | Complete promotional items | `true` |
|
||||||
| workers.doPunchCards | Complete punchcards | `true` |
|
| workers.doPunchCards | Complete punchcards | `true` |
|
||||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
|
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
|
||||||
| workers.doMobileSearch | Complete daily mobile 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` |
|
||||||
|
| searchOnBingLocalQueries | Complete the activity "search on Bing" using the `queries.json` or fetched from this repo | `false` (Will fetch from this repo) |
|
||||||
|
| globalTimeout | The length before the action gets timeout | `30s` |
|
||||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
|
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
|
||||||
| scrollRandomResults | Scroll randomly in search results | `true` |
|
| searchSettings.scrollRandomResults | Scroll randomly in search results | `true` |
|
||||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
||||||
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) |
|
| searchSettings.searchDelay | Minimum and maximum time in milliseconds between search queries | `min: 3min` `max: 5min` |
|
||||||
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` |
|
| searchSettings.retryMobileSearchAmount | Keep retrying mobile searches for specified amount | `2` |
|
||||||
|
| logExcludeFunc | Functions to exclude out of the logs and webhooks | `SEARCH-CLOSE-TABS` |
|
||||||
|
| webhookLogExcludeFunc | Functions to exclude out of the webhooks log | `SEARCH-CLOSE-TABS` |
|
||||||
|
| proxy.proxyGoogleTrends | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
|
||||||
|
| proxy.proxyBingTerms | Enable or disable proxying the request via set proxy | `true` (will be proxied) |
|
||||||
| webhook.enabled | Enable or disable your set webhook | `false` |
|
| webhook.enabled | Enable or disable your set webhook | `false` |
|
||||||
| webhook.url | Your Discord webhook URL | `null` |
|
| webhook.url | Your Discord webhook URL | `null` |
|
||||||
|
|
||||||
@@ -47,6 +87,7 @@ Under development, however mainly for personal use!
|
|||||||
- [x] Multi-Account Support
|
- [x] Multi-Account Support
|
||||||
- [x] Session Storing
|
- [x] Session Storing
|
||||||
- [x] 2FA Support
|
- [x] 2FA Support
|
||||||
|
- [x] Passwordless Support
|
||||||
- [x] Headless Support
|
- [x] Headless Support
|
||||||
- [x] Discord Webhook Support
|
- [x] Discord Webhook Support
|
||||||
- [x] Desktop Searches
|
- [x] Desktop Searches
|
||||||
@@ -65,10 +106,12 @@ Under development, however mainly for personal use!
|
|||||||
- [x] Completing Punchcards
|
- [x] Completing Punchcards
|
||||||
- [x] Solving This Or That Quiz (Random)
|
- [x] Solving This Or That Quiz (Random)
|
||||||
- [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
|
||||||
27
package.json
27
package.json
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "microsoft-rewards-script",
|
"name": "microsoft-rewards-script",
|
||||||
"version": "1.4.5",
|
"version": "1.5.3",
|
||||||
"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",
|
||||||
@@ -26,17 +27,25 @@
|
|||||||
"author": "Netsky",
|
"author": "Netsky",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
"@types/ms": "^0.7.34",
|
||||||
"eslint": "^8.56.0",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-modules-newline": "^0.0.6",
|
"eslint-plugin-modules-newline": "^0.0.6",
|
||||||
"typescript": "^5.3.3"
|
"rimraf": "^6.0.1",
|
||||||
|
"typescript": "^5.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.7",
|
"axios": "^1.8.4",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"chalk": "^4.1.2",
|
||||||
"fingerprint-generator": "^2.1.49",
|
"cheerio": "^1.0.0",
|
||||||
"fingerprint-injector": "^2.1.49",
|
"fingerprint-generator": "^2.1.66",
|
||||||
"playwright": "^1.42.0",
|
"fingerprint-injector": "^2.1.66",
|
||||||
|
"http-proxy-agent": "^7.0.2",
|
||||||
|
"https-proxy-agent": "^7.0.6",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"playwright": "1.47.2",
|
||||||
|
"rebrowser-playwright": "1.47.2",
|
||||||
|
"socks-proxy-agent": "^8.0.5",
|
||||||
"ts-node": "^10.9.2"
|
"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,20 @@
|
|||||||
import playwright from 'playwright'
|
import playwright, { BrowserContext } from 'rebrowser-playwright'
|
||||||
import { BrowserContext } from 'playwright'
|
|
||||||
|
|
||||||
import { newInjectedContext } 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 { 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 {
|
||||||
@@ -39,30 +41,35 @@ class Browser {
|
|||||||
|
|
||||||
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)
|
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)
|
||||||
|
|
||||||
const fingerpint = sessionData.fingerprint ? sessionData.fingerprint : this.generateFingerprint()
|
const fingerprint = sessionData.fingerprint ? sessionData.fingerprint : await this.generateFingerprint()
|
||||||
|
|
||||||
const context = await newInjectedContext(browser, { fingerprint: fingerpint })
|
const context = await newInjectedContext(browser as any, { fingerprint: fingerprint })
|
||||||
|
|
||||||
|
// Set timeout to preferred amount
|
||||||
|
context.setDefaultTimeout(this.bot.utils.stringToMs(this.bot.config?.globalTimeout ?? 30000))
|
||||||
|
|
||||||
await context.addCookies(sessionData.cookies)
|
await context.addCookies(sessionData.cookies)
|
||||||
|
|
||||||
if (this.bot.config.saveFingerprint) {
|
if (this.bot.config.saveFingerprint) {
|
||||||
await saveFingerprintData(this.bot.config.sessionPath, email, this.bot.isMobile, fingerpint)
|
await saveFingerprintData(this.bot.config.sessionPath, email, this.bot.isMobile, fingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bot.log('BROWSER', `Created browser with User-Agent: "${fingerpint.fingerprint.navigator.userAgent}"`)
|
this.bot.log(this.bot.isMobile, 'BROWSER', `Created browser with User-Agent: "${fingerprint.fingerprint.navigator.userAgent}"`)
|
||||||
|
|
||||||
return context
|
return context as BrowserContext
|
||||||
}
|
}
|
||||||
|
|
||||||
generateFingerprint() {
|
async generateFingerprint() {
|
||||||
const fingerPrintData = new FingerprintGenerator().getFingerprint({
|
const fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||||
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||||
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||||
browsers: ['edge']
|
browsers: [{ name: 'edge' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
return fingerPrintData
|
const updatedFingerPrintData = await updateFingerprintUserAgent(fingerPrintData, this.bot.isMobile)
|
||||||
|
|
||||||
|
return updatedFingerPrintData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Browser
|
export default Browser
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
import { Page } from 'playwright'
|
import { BrowserContext, Page } from 'rebrowser-playwright'
|
||||||
import { CheerioAPI, load } from 'cheerio'
|
import { CheerioAPI, load } from 'cheerio'
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
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 {
|
||||||
@@ -34,19 +38,19 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
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', { state: 'visible', timeout: 2000 }).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('GO-HOME', 'Visited homepage successfully')
|
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||||
break
|
break
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -62,7 +66,7 @@ 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 {
|
} else {
|
||||||
this.bot.log('GO-HOME', 'Visited homepage successfully')
|
this.bot.log(this.bot.isMobile, 'GO-HOME', 'Visited homepage successfully')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +74,7 @@ export default class BrowserFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw this.bot.log('GO-HOME', 'An error occurred:' + error, 'error')
|
throw this.bot.log(this.bot.isMobile, 'GO-HOME', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,42 +86,49 @@ export default class BrowserFunc {
|
|||||||
const dashboardURL = new URL(this.bot.config.baseURL)
|
const dashboardURL = new URL(this.bot.config.baseURL)
|
||||||
const currentURL = new URL(this.bot.homePage.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(this.bot.homePage)
|
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('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) {
|
// Reload the page to get new data
|
||||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
await this.bot.homePage.reload({ waitUntil: 'domcontentloaded' })
|
||||||
|
|
||||||
|
const scriptContent = await this.bot.homePage.evaluate(() => {
|
||||||
|
const scripts = Array.from(document.querySelectorAll('script'))
|
||||||
|
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||||
|
|
||||||
|
return targetScript?.innerText ? targetScript.innerText : null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!scriptContent) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the dashboard object from the script content
|
||||||
|
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
||||||
|
// Extract the dashboard object using regex
|
||||||
|
const regex = /var dashboard = (\{.*?\});/s
|
||||||
|
const match = regex.exec(scriptContent)
|
||||||
|
|
||||||
|
if (match && match[1]) {
|
||||||
|
return JSON.parse(match[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
}, scriptContent)
|
||||||
|
|
||||||
|
if (!dashboardData) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
return dashboardData
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'GET-DASHBOARD-DATA', `Error fetching dashboard data: ${error}`, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
return dashboardData
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,42 +142,109 @@ export default class BrowserFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get total earnable points
|
* Get total earnable points with web browser
|
||||||
* @returns {number} Total earnable points
|
* @returns {number} Total earnable points
|
||||||
*/
|
*/
|
||||||
async getEarnablePoints(): Promise<number> {
|
async getBrowserEarnablePoints(): Promise<EarnablePoints> {
|
||||||
try {
|
try {
|
||||||
const data = await this.getDashboardData()
|
let desktopSearchPoints = 0
|
||||||
|
let mobileSearchPoints = 0
|
||||||
|
let dailySetPoints = 0
|
||||||
|
let morePromotionsPoints = 0
|
||||||
|
|
||||||
// These only include the points from tasks that the script can complete!
|
const data = await this.getDashboardData()
|
||||||
let totalEarnablePoints = 0
|
|
||||||
|
|
||||||
// Desktop Search Points
|
// Desktop Search Points
|
||||||
if (data.userStatus.counters.pcSearch?.length) {
|
if (data.userStatus.counters.pcSearch?.length) {
|
||||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
data.userStatus.counters.pcSearch.forEach(x => desktopSearchPoints += (x.pointProgressMax - x.pointProgress))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile Search Points
|
// Mobile Search Points
|
||||||
if (data.userStatus.counters.mobileSearch?.length) {
|
if (data.userStatus.counters.mobileSearch?.length) {
|
||||||
data.userStatus.counters.mobileSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
data.userStatus.counters.mobileSearch.forEach(x => mobileSearchPoints += (x.pointProgressMax - x.pointProgress))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Daily Set
|
// Daily Set
|
||||||
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => dailySetPoints += (x.pointProgressMax - x.pointProgress))
|
||||||
|
|
||||||
// More Promotions
|
// More Promotions
|
||||||
if (data.morePromotions?.length) {
|
if (data.morePromotions?.length) {
|
||||||
data.morePromotions.forEach(x => {
|
data.morePromotions.forEach(x => {
|
||||||
// Only count points from supported activities
|
// Only count points from supported activities
|
||||||
if (['quiz', 'urlreward'].includes(x.activityType)) {
|
if (['quiz', 'urlreward'].includes(x.promotionType) && x.exclusiveLockedFeatureStatus !== 'locked') {
|
||||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
morePromotionsPoints += (x.pointProgressMax - x.pointProgress)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return totalEarnablePoints
|
const totalEarnablePoints = desktopSearchPoints + mobileSearchPoints + dailySetPoints + morePromotionsPoints
|
||||||
|
|
||||||
|
return {
|
||||||
|
dailySetPoints,
|
||||||
|
morePromotionsPoints,
|
||||||
|
desktopSearchPoints,
|
||||||
|
mobileSearchPoints,
|
||||||
|
totalEarnablePoints
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw this.bot.log('GET-EARNABLE-POINTS', 'An error occurred:' + error, '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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +258,7 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
return data.userStatus.availablePoints
|
return data.userStatus.availablePoints
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw this.bot.log('GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
throw this.bot.log(this.bot.isMobile, 'GET-CURRENT-POINTS', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,26 +284,26 @@ 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 waitForQuizRefresh(page: Page): Promise<boolean> {
|
async waitForQuizRefresh(page: Page): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10_000 })
|
await page.waitForSelector('span.rqMCredits', { state: 'visible', timeout: 10000 })
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,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)
|
||||||
|
|
||||||
@@ -259,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 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
import { load } from 'cheerio'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
|
||||||
@@ -10,69 +11,41 @@ export default class BrowserUtil {
|
|||||||
this.bot = bot
|
this.bot = bot
|
||||||
}
|
}
|
||||||
|
|
||||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
async tryDismissAllMessages(page: Page): Promise<void> {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
{ selector: 'button[type="submit"]', label: 'Submit Button' },
|
||||||
{ selector: '#acceptButton', label: 'AcceptButton' },
|
{ 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', isXPath: true },
|
||||||
|
{ selector: '#bnp_btn_accept', label: 'Bing Cookie Banner' },
|
||||||
|
{ selector: '#reward_pivot_earn', label: 'Reward Coupon Accept' }
|
||||||
]
|
]
|
||||||
|
|
||||||
let result = false
|
|
||||||
|
|
||||||
for (const button of buttons) {
|
for (const button of buttons) {
|
||||||
try {
|
try {
|
||||||
const element = await page.waitForSelector(button.selector, { state: 'visible', timeout: 1000 })
|
const element = button.isXPath ? page.locator(`xpath=${button.selector}`) : page.locator(button.selector)
|
||||||
if (element) {
|
await element.first().click({ timeout: 500 })
|
||||||
await element.click()
|
await page.waitForTimeout(500)
|
||||||
result = true
|
|
||||||
}
|
this.bot.log(this.bot.isMobile, 'DISMISS-ALL-MESSAGES', `Dismissed: ${button.label}`)
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
continue
|
// Silent fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async tryDismissCookieBanner(page: Page): Promise<void> {
|
|
||||||
try {
|
|
||||||
await page.waitForSelector('#cookieConsentContainer', { timeout: 1000 })
|
|
||||||
const cookieBanner = await page.$('#cookieConsentContainer')
|
|
||||||
|
|
||||||
if (cookieBanner) {
|
|
||||||
const button = await cookieBanner.$('button')
|
|
||||||
if (button) {
|
|
||||||
await button.click()
|
|
||||||
await this.bot.utils.wait(2000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
// Continue if element is not found or other error occurs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async tryDismissBingCookieBanner(page: Page): Promise<void> {
|
|
||||||
try {
|
|
||||||
await page.waitForSelector('#bnp_btn_accept', { timeout: 1000 })
|
|
||||||
const cookieBanner = await page.$('#bnp_btn_accept')
|
|
||||||
|
|
||||||
if (cookieBanner) {
|
|
||||||
await cookieBanner.click()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Continue if element is not found or other error occurs
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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.context()
|
const browser = page.context()
|
||||||
const pages = browser.pages()
|
const pages = browser.pages()
|
||||||
@@ -82,9 +55,9 @@ export default class BrowserUtil {
|
|||||||
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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,19 +70,19 @@ export default class BrowserUtil {
|
|||||||
let homeTabURL: URL
|
let homeTabURL: URL
|
||||||
|
|
||||||
if (!homeTab) {
|
if (!homeTab) {
|
||||||
throw this.bot.log('GET-TABS', 'Home tab could not be found!', 'error')
|
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Home tab could not be found!', 'error')
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
homeTabURL = new URL(homeTab.url())
|
homeTabURL = new URL(homeTab.url())
|
||||||
|
|
||||||
if (homeTabURL.hostname !== 'rewards.bing.com') {
|
if (homeTabURL.hostname !== 'rewards.bing.com') {
|
||||||
throw this.bot.log('GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Reward page hostname is invalid: ' + homeTabURL.host, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const workerTab = pages[2]
|
const workerTab = pages[2]
|
||||||
if (!workerTab) {
|
if (!workerTab) {
|
||||||
throw this.bot.log('GET-TABS', 'Worker tab could not be found!', 'error')
|
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'Worker tab could not be found!', 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -118,7 +91,24 @@ export default class BrowserUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw this.bot.log('GET-TABS', 'An error occurred:' + error, 'error')
|
throw this.bot.log(this.bot.isMobile, 'GET-TABS', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reloadBadPage(page: Page): Promise<void> {
|
||||||
|
try {
|
||||||
|
const html = await page.content().catch(() => '')
|
||||||
|
const $ = load(html)
|
||||||
|
|
||||||
|
const isNetworkError = $('body.neterror').length
|
||||||
|
|
||||||
|
if (isNetworkError) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'Bad page detected, reloading!')
|
||||||
|
await page.reload()
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'RELOAD-BAD-PAGE', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,28 +2,46 @@
|
|||||||
"baseURL": "https://rewards.bing.com",
|
"baseURL": "https://rewards.bing.com",
|
||||||
"sessionPath": "sessions",
|
"sessionPath": "sessions",
|
||||||
"headless": false,
|
"headless": false,
|
||||||
|
"parallel": true,
|
||||||
"runOnZeroPoints": false,
|
"runOnZeroPoints": false,
|
||||||
"clusters": 1,
|
"clusters": 1,
|
||||||
"saveFingerprint": false,
|
"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"
|
||||||
},
|
},
|
||||||
"retryMobileSearch": true
|
"retryMobileSearchAmount": 2
|
||||||
|
},
|
||||||
|
"logExcludeFunc": [
|
||||||
|
"SEARCH-CLOSE-TABS"
|
||||||
|
],
|
||||||
|
"webhookLogExcludeFunc": [
|
||||||
|
"SEARCH-CLOSE-TABS"
|
||||||
|
],
|
||||||
|
"proxy": {
|
||||||
|
"proxyGoogleTrends": true,
|
||||||
|
"proxyBingTerms": true
|
||||||
},
|
},
|
||||||
"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 'playwright'
|
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 {
|
||||||
@@ -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,17 +1,26 @@
|
|||||||
import { Page } from 'playwright'
|
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 { 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
|
||||||
@@ -23,20 +32,23 @@ export class Login {
|
|||||||
// Navigate to the Bing login page
|
// Navigate to the Bing login page
|
||||||
await page.goto('https://rewards.bing.com/signin')
|
await page.goto('https://rewards.bing.com/signin')
|
||||||
|
|
||||||
const isLoggedIn = await page.waitForSelector('html[data-role-name="RewardsPortal"]', { 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: 10000 }).then(() => true).catch(() => false)
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
// Check if account is locked
|
|
||||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { state: 'visible', timeout: 1000 }).then(() => true).catch(() => false)
|
|
||||||
if (isLocked) {
|
|
||||||
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
|
||||||
throw new Error('Account has been locked!')
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.execLogin(page, email, password)
|
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
|
||||||
@@ -46,66 +58,208 @@ export class Login {
|
|||||||
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
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) {
|
||||||
try {
|
try {
|
||||||
// Enter email
|
await this.enterEmail(page, email)
|
||||||
await page.fill('#i0116', email)
|
await this.bot.utils.wait(2000)
|
||||||
await page.click('#idSIButton9')
|
await this.bot.browser.utils.reloadBadPage(page)
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
await this.enterPassword(page, password)
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
this.bot.log('LOGIN', 'Email entered successfully')
|
// Check if account is locked
|
||||||
|
await this.checkAccountLocked(page)
|
||||||
|
|
||||||
try {
|
await this.bot.browser.utils.reloadBadPage(page)
|
||||||
// Enter password
|
await this.checkLoggedIn(page)
|
||||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
} catch (error) {
|
||||||
await this.bot.utils.wait(2000)
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'An error occurred: ' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await page.fill('#i0118', password)
|
private async enterEmail(page: Page, email: string) {
|
||||||
await page.click('#idSIButton9')
|
const emailInputSelector = 'input[type="email"]'
|
||||||
|
|
||||||
// When erroring at this stage it means a 2FA code is required
|
try {
|
||||||
} catch (error) {
|
// Wait for email field
|
||||||
this.bot.log('LOGIN', '2FA code required')
|
const emailField = await page.waitForSelector(emailInputSelector, { state: 'visible', timeout: 2000 }).catch(() => null)
|
||||||
|
if (!emailField) {
|
||||||
// Wait for user input
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email field not found', 'warn')
|
||||||
const code = await new Promise<string>((resolve) => {
|
return
|
||||||
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('LOGIN', 'Password entered successfully')
|
await this.bot.utils.wait(1000)
|
||||||
|
|
||||||
|
// Check if email is prefilled
|
||||||
|
const emailPrefilled = await page.waitForSelector('#userDisplayName', { timeout: 5000 }).catch(() => null)
|
||||||
|
if (emailPrefilled) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email already prefilled by Microsoft')
|
||||||
|
} else {
|
||||||
|
// Else clear and fill email
|
||||||
|
await page.fill(emailInputSelector, '')
|
||||||
|
await this.bot.utils.wait(500)
|
||||||
|
await page.fill(emailInputSelector, email)
|
||||||
|
await this.bot.utils.wait(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
|
||||||
|
if (nextButton) {
|
||||||
|
await nextButton.click()
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Email entered successfully')
|
||||||
|
} else {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after email entry', 'warn')
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
|
this.bot.log(this.bot.isMobile, 'LOGIN', `Email entry failed: ${error}`, 'error')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentURL = new URL(page.url())
|
private async enterPassword(page: Page, password: string) {
|
||||||
|
const passwordInputSelector = 'input[type="password"]'
|
||||||
|
|
||||||
while (currentURL.pathname !== '/' || currentURL.hostname !== 'rewards.bing.com') {
|
try {
|
||||||
|
// Wait for password field
|
||||||
|
const passwordField = await page.waitForSelector(passwordInputSelector, { state: 'visible', timeout: 5000 }).catch(() => null)
|
||||||
|
if (!passwordField) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password field not found, possibly 2FA required', 'warn')
|
||||||
|
await this.handle2FA(page)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.bot.utils.wait(1000)
|
||||||
|
|
||||||
|
// Clear and fill password
|
||||||
|
await page.fill(passwordInputSelector, '')
|
||||||
|
await this.bot.utils.wait(500)
|
||||||
|
await page.fill(passwordInputSelector, password)
|
||||||
|
await this.bot.utils.wait(1000)
|
||||||
|
|
||||||
|
const nextButton = await page.waitForSelector('button[type="submit"]', { timeout: 2000 }).catch(() => null)
|
||||||
|
if (nextButton) {
|
||||||
|
await nextButton.click()
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Password entered successfully')
|
||||||
|
} else {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', 'Next button not found after password entry', 'warn')
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'LOGIN', `Password entry failed: ${error}`, 'error')
|
||||||
|
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(60000)
|
||||||
|
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: 60000 })
|
||||||
|
|
||||||
|
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="RewardsPortal"]', { timeout: 10_000 })
|
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10000 })
|
||||||
|
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
|
||||||
@@ -114,12 +268,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.browser.utils.tryDismissBingCookieBanner(page)
|
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||||
|
|
||||||
const loggedIn = await this.checkBingLoginStatus(page)
|
const loggedIn = await this.checkBingLoginStatus(page)
|
||||||
// If mobile browser, skip this step
|
// If mobile browser, skip this step
|
||||||
if (loggedIn || this.bot.isMobile) {
|
if (loggedIn || this.bot.isMobile) {
|
||||||
this.bot.log('LOGIN-BING', 'Bing login verification passed!')
|
this.bot.log(this.bot.isMobile, 'LOGIN-BING', 'Bing login verification passed!')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +282,7 @@ 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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,4 +295,61 @@ export class Login {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
import { DashboardData, MorePromotion, PromotionalItem, PunchCard } from '../interface/DashboardData'
|
||||||
|
|
||||||
@@ -18,12 +18,12 @@ export class Workers {
|
|||||||
const activitiesUncompleted = todayData?.filter(x => !x.complete && x.pointProgressMax > 0) ?? []
|
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)
|
||||||
|
|
||||||
@@ -32,33 +32,40 @@ export class Workers {
|
|||||||
// Always return to the homepage if not already
|
// Always return to the homepage if not already
|
||||||
await this.bot.browser.func.goHome(page)
|
await this.bot.browser.func.goHome(page)
|
||||||
|
|
||||||
this.bot.log('DAILY-SET', 'All "Daily Set" items have been completed')
|
this.bot.log(this.bot.isMobile, 'DAILY-SET', 'All "Daily Set" items have been completed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Punch Card
|
// 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
|
// Get latest page for each card
|
||||||
page = await this.bot.browser.utils.getLatestTab(page)
|
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}"`)
|
||||||
|
|
||||||
// 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
|
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||||
await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => { })
|
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { })
|
||||||
|
|
||||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||||
|
|
||||||
@@ -72,10 +79,10 @@ export class Workers {
|
|||||||
await this.bot.browser.func.goHome(page)
|
await this.bot.browser.func.goHome(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bot.log('PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', `All items for punchcard: "${punchCard.parentPromotion.title}" have been completed`)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bot.log('PUNCH-CARD', 'All "Punch Card" items have been completed')
|
this.bot.log(this.bot.isMobile, 'PUNCH-CARD', 'All "Punch Card" items have been completed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// More Promotions
|
// More Promotions
|
||||||
@@ -87,15 +94,15 @@ 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" items')
|
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'Started solving "More Promotions" items')
|
||||||
|
|
||||||
page = await this.bot.browser.utils.getLatestTab(page)
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
@@ -106,7 +113,7 @@ export class Workers {
|
|||||||
// Always return to the homepage if not already
|
// Always return to the homepage if not already
|
||||||
await this.bot.browser.func.goHome(page)
|
await this.bot.browser.func.goHome(page)
|
||||||
|
|
||||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
this.bot.log(this.bot.isMobile, 'MORE-PROMOTIONS', 'All "More Promotion" items have been completed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solve all the different types of activities
|
// Solve all the different types of activities
|
||||||
@@ -132,28 +139,22 @@ export class Workers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let selector = `[data-bi-id="${activity.offerId}"]`
|
let selector = `[data-bi-id^="${activity.offerId}"] .pointLink:not(.contentContainer .pointLink)`
|
||||||
|
|
||||||
if (punchCard) {
|
if (punchCard) {
|
||||||
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
||||||
|
|
||||||
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
} else if (activity.name.toLowerCase().includes('membercenter') || activity.name.toLowerCase().includes('exploreonbing')) {
|
||||||
selector = `[data-bi-id="${activity.name}"]`
|
selector = `[data-bi-id^="${activity.name}"] .pointLink:not(.contentContainer .pointLink)`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click element, it will be opened in a new tab
|
|
||||||
await activityPage.click(selector)
|
|
||||||
|
|
||||||
// Select the new activity page
|
|
||||||
activityPage = await this.bot.browser.utils.getLatestTab(activityPage)
|
|
||||||
|
|
||||||
// Wait for the new tab to fully load, ignore error.
|
// 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,
|
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.
|
if it didn't then it gave enough time for the page to load.
|
||||||
*/
|
*/
|
||||||
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
|
await activityPage.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => { })
|
||||||
await this.bot.utils.wait(5000)
|
await this.bot.utils.wait(2000)
|
||||||
|
|
||||||
switch (activity.promotionType) {
|
switch (activity.promotionType) {
|
||||||
// Quiz (Poll, Quiz or ABC)
|
// Quiz (Poll, Quiz or ABC)
|
||||||
@@ -163,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
|
||||||
}
|
}
|
||||||
@@ -187,14 +196,24 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +221,7 @@ export class Workers {
|
|||||||
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 'playwright'
|
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', { state: 'visible', timeout: 10_000 })
|
await page.waitForSelector('.wk_OptionClickClass', { state: 'visible', timeout: 10000 })
|
||||||
|
|
||||||
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}`, { state: 'visible', timeout: 10_000 })
|
await page.waitForSelector(`#${answer}`, { state: 'visible', timeout: 10000 })
|
||||||
|
|
||||||
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', { state: 'visible', timeout: 10_000 })
|
await page.waitForSelector('div.wk_button', { state: 'visible', timeout: 10000 })
|
||||||
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 'playwright'
|
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, { state: 'visible', timeout: 10_000 }).catch(() => { }) // We're gonna click regardless or not
|
await page.waitForSelector(buttonId, { state: 'visible', timeout: 10000 }).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 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ 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
|
||||||
@@ -14,7 +14,7 @@ export class Quiz extends Workers {
|
|||||||
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}`, { state: 'visible', timeout: 10_000 })
|
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
|
||||||
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') {
|
||||||
@@ -47,7 +47,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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +59,7 @@ export class Quiz extends Workers {
|
|||||||
|
|
||||||
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
for (let i = 0; i < quizData.numberOfOptions; i++) {
|
||||||
|
|
||||||
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10_000 })
|
const answerSelector = await page.waitForSelector(`#rqAnswerOption${i}`, { state: 'visible', timeout: 10000 })
|
||||||
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,20 +1,27 @@
|
|||||||
import { Page } from 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
import axios from 'axios'
|
|
||||||
import { platform } from 'os'
|
import { platform } from 'os'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
import { Counters, DashboardData } from '../../interface/DashboardData'
|
import { Counters, DashboardData } from '../../interface/DashboardData'
|
||||||
import { GoogleTrends } from '../../interface/GoogleDailyTrends'
|
|
||||||
import { GoogleSearch } from '../../interface/Search'
|
import { GoogleSearch } from '../../interface/Search'
|
||||||
|
import { AxiosRequestConfig } from 'axios'
|
||||||
|
|
||||||
|
type GoogleTrendsResponse = [
|
||||||
|
string,
|
||||||
|
[
|
||||||
|
string,
|
||||||
|
...null[],
|
||||||
|
[string, ...string[]]
|
||||||
|
][]
|
||||||
|
];
|
||||||
|
|
||||||
export class Search extends Workers {
|
export class Search extends Workers {
|
||||||
|
private bingHome = 'https://bing.com'
|
||||||
private searchPageURL = 'https://bing.com'
|
private searchPageURL = ''
|
||||||
|
|
||||||
public async doSearch(page: Page, data: DashboardData) {
|
public async doSearch(page: Page, data: DashboardData) {
|
||||||
this.bot.log('SEARCH-BING', 'Starting bing searches')
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Starting Bing searches')
|
||||||
|
|
||||||
page = await this.bot.browser.utils.getLatestTab(page)
|
page = await this.bot.browser.utils.getLatestTab(page)
|
||||||
|
|
||||||
@@ -22,21 +29,25 @@ export class Search extends Workers {
|
|||||||
let missingPoints = this.calculatePoints(searchCounters)
|
let missingPoints = this.calculatePoints(searchCounters)
|
||||||
|
|
||||||
if (missingPoints === 0) {
|
if (missingPoints === 0) {
|
||||||
this.bot.log('SEARCH-BING', `Bing searches for ${this.bot.isMobile ? 'MOBILE' : 'DESKTOP'} have already been completed`)
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Bing searches have already been completed')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate search queries
|
// Generate search queries
|
||||||
let googleSearchQueries = await this.getGoogleTrends(data.userProfile.attributes.country, missingPoints)
|
let googleSearchQueries = await this.getGoogleTrends(this.bot.config.searchSettings.useGeoLocaleQueries ? data.userProfile.attributes.country : 'US')
|
||||||
googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
|
googleSearchQueries = this.bot.utils.shuffleArray(googleSearchQueries)
|
||||||
|
|
||||||
// Deduplicate the search terms
|
// Deduplicate the search terms
|
||||||
googleSearchQueries = [...new Set(googleSearchQueries)]
|
googleSearchQueries = [...new Set(googleSearchQueries)]
|
||||||
|
|
||||||
// Go to bing
|
// Go to bing
|
||||||
await page.goto(this.searchPageURL)
|
await page.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||||
|
|
||||||
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
|
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 doesn't continue after 5 more searches with alternative queries, abort search
|
||||||
|
|
||||||
const queries: string[] = []
|
const queries: string[] = []
|
||||||
// Mobile search doesn't seem to like related queries?
|
// Mobile search doesn't seem to like related queries?
|
||||||
@@ -46,7 +57,7 @@ export class Search extends Workers {
|
|||||||
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: ${this.bot.isMobile}`)
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', `${missingPoints} Points Remaining | Query: ${query}`)
|
||||||
|
|
||||||
searchCounters = await this.bingSearch(page, query)
|
searchCounters = await this.bingSearch(page, query)
|
||||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||||
@@ -65,14 +76,14 @@ export class Search extends Workers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only for mobile searches
|
// Only for mobile searches
|
||||||
if (maxLoop > 3 && this.bot.isMobile) {
|
if (maxLoop > 5 && this.bot.isMobile) {
|
||||||
this.bot.log('SEARCH-BING-MOBILE', 'Search didn\'t gain point for 3 iterations, likely bad User-Agent', 'warn')
|
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
|
||||||
}
|
}
|
||||||
@@ -85,7 +96,7 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
// 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) {
|
||||||
@@ -96,9 +107,9 @@ 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: ${this.bot.isMobile}`)
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term}`)
|
||||||
|
|
||||||
searchCounters = await this.bingSearch(page, query.topic)
|
searchCounters = await this.bingSearch(page, term)
|
||||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||||
|
|
||||||
// If the new point amount is the same as before
|
// If the new point amount is the same as before
|
||||||
@@ -117,7 +128,7 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -125,7 +136,7 @@ export class Search extends Workers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bot.log('SEARCH-BING', 'Completed searches')
|
this.bot.log(this.bot.isMobile, 'SEARCH-BING', 'Completed searches')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async bingSearch(searchPage: Page, query: string) {
|
private async bingSearch(searchPage: Page, query: string) {
|
||||||
@@ -134,14 +145,18 @@ export class Search extends Workers {
|
|||||||
// 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
|
// Go to top of the page
|
||||||
await searchPage.evaluate(() => {
|
await searchPage.evaluate(() => {
|
||||||
window.scrollTo(0, 0)
|
window.scrollTo(0, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await this.bot.utils.wait(500)
|
||||||
|
|
||||||
const searchBar = '#sb_form_q'
|
const searchBar = '#sb_form_q'
|
||||||
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
|
||||||
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(platformControlKey)
|
await searchPage.keyboard.down(platformControlKey)
|
||||||
@@ -151,84 +166,109 @@ export class Search extends Workers {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay between searches
|
// Delay between searches
|
||||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
|
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.min), this.bot.utils.stringToMs(this.bot.config.searchSettings.searchDelay.max))))
|
||||||
|
|
||||||
return await this.bot.browser.func.getSearchPoints()
|
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
|
// Reset the tabs
|
||||||
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
|
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||||
await this.closeTabs(lastTab, this.searchPageURL)
|
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()
|
return await this.bot.browser.func.getSearchPoints()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getGoogleTrends(geoLocale: string, queryCount: number): Promise<GoogleSearch[]> {
|
private async getGoogleTrends(geoLocale: string = 'US'): Promise<GoogleSearch[]> {
|
||||||
const queryTerms: GoogleSearch[] = []
|
const queryTerms: GoogleSearch[] = []
|
||||||
let i = 0
|
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
||||||
|
|
||||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toUpperCase() : 'US'
|
try {
|
||||||
|
const request: AxiosRequestConfig = {
|
||||||
this.bot.log('SEARCH-GOOGLE-TRENDS', `Generating search queries, can take a while! | GeoLocale: ${geoLocale}`)
|
url: 'https://trends.google.com/_/TrendsUi/data/batchexecute',
|
||||||
|
method: 'POST',
|
||||||
while (queryCount > queryTerms.length) {
|
headers: {
|
||||||
i += 1
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
|
||||||
const date = new Date()
|
},
|
||||||
date.setDate(date.getDate() - i)
|
data: `f.req=[[[i0OFE,"[null, null, \\"${geoLocale.toUpperCase()}\\", 0, null, 48]"]]]`
|
||||||
const formattedDate = this.formatDate(date)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const request = {
|
|
||||||
url: `https://trends.google.com/trends/api/dailytrends?geo=${geoLocale}&hl=en&ed=${formattedDate}&ns=15`,
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await axios(request)
|
|
||||||
|
|
||||||
const data: GoogleTrends = JSON.parse((await response.data).slice(5))
|
|
||||||
|
|
||||||
for (const topic of data.default.trendingSearchesDays[0]?.trendingSearches ?? []) {
|
|
||||||
queryTerms.push({
|
|
||||||
topic: topic.title.query.toLowerCase(),
|
|
||||||
related: topic.relatedQueries.map(x => x.query.toLowerCase())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.bot.log('SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyGoogleTrends)
|
||||||
|
const rawText = response.data
|
||||||
|
|
||||||
|
const trendsData = this.extractJsonFromResponse(rawText)
|
||||||
|
if (!trendsData) {
|
||||||
|
throw this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Failed to parse Google Trends response', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappedTrendsData = trendsData.map(query => [query[0], query[9]!.slice(1)])
|
||||||
|
if (mappedTrendsData.length < 90) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'Insufficient search queries, falling back to US', 'warn')
|
||||||
|
return this.getGoogleTrends()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [topic, relatedQueries] of mappedTrendsData) {
|
||||||
|
queryTerms.push({
|
||||||
|
topic: topic as string,
|
||||||
|
related: relatedQueries as string[]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-GOOGLE-TRENDS', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
return queryTerms
|
return queryTerms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private extractJsonFromResponse(text: string): GoogleTrendsResponse[1] | null {
|
||||||
|
const lines = text.split('\n')
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim()
|
||||||
|
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
||||||
|
try {
|
||||||
|
return JSON.parse(JSON.parse(trimmed)[0][2])[1]
|
||||||
|
} catch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private async getRelatedTerms(term: string): Promise<string[]> {
|
private async getRelatedTerms(term: string): Promise<string[]> {
|
||||||
try {
|
try {
|
||||||
const request = {
|
const request = {
|
||||||
@@ -239,55 +279,51 @@ export class Search extends Workers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await axios(request)
|
const response = await this.bot.axios.request(request, this.bot.config.proxy.proxyBingTerms)
|
||||||
|
|
||||||
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 []
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatDate(date: Date): string {
|
|
||||||
const year = date.getFullYear()
|
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
||||||
const day = String(date.getDate()).padStart(2, '0')
|
|
||||||
|
|
||||||
return `${year}${month}${day}`
|
|
||||||
}
|
|
||||||
|
|
||||||
private async randomScroll(page: Page) {
|
private async randomScroll(page: Page) {
|
||||||
try {
|
try {
|
||||||
const scrollAmount = this.bot.utils.randomNumber(5, 5000)
|
const viewportHeight = await page.evaluate(() => window.innerHeight)
|
||||||
|
const totalHeight = await page.evaluate(() => document.body.scrollHeight)
|
||||||
|
const randomScrollPosition = Math.floor(Math.random() * (totalHeight - viewportHeight))
|
||||||
|
|
||||||
await page.evaluate((scrollAmount) => {
|
await page.evaluate((scrollPos) => {
|
||||||
window.scrollBy(0, scrollAmount)
|
window.scrollTo(0, scrollPos)
|
||||||
}, scrollAmount)
|
}, 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)
|
||||||
|
|
||||||
// Will get current tab if no new one is created
|
// Stay for 10 seconds for page to load and "visit"
|
||||||
|
await this.bot.utils.wait(10000)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
// Let website load, if it doesn't load within 5 sec. exit regardless
|
let lastTabURL = new URL(lastTab.url()) // Get new tab info, this is the website we're visiting
|
||||||
await this.bot.utils.wait(5000)
|
|
||||||
|
|
||||||
let lastTabURL = new URL(lastTab.url()) // Get new tab info
|
|
||||||
|
|
||||||
// Check if the URL is different from the original one, don't loop more than 5 times.
|
// Check if the URL is different from the original one, don't loop more than 5 times.
|
||||||
let i = 0
|
let i = 0
|
||||||
while (lastTabURL.href !== searchListingURL.href && i < 5) {
|
while (lastTabURL.href !== this.searchPageURL && i < 5) {
|
||||||
|
|
||||||
await this.closeTabs(lastTab, searchListingURL.href)
|
await this.closeTabs(lastTab)
|
||||||
|
|
||||||
// End of loop, refresh lastPage
|
// End of loop, refresh lastPage
|
||||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
||||||
@@ -296,29 +332,44 @@ export class Search extends Workers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.bot.log('SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
this.bot.log(this.bot.isMobile, 'SEARCH-RANDOM-CLICK', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async closeTabs(lastTab: Page, url: string) {
|
private async closeTabs(lastTab: Page) {
|
||||||
const browser = lastTab.context()
|
const browser = lastTab.context()
|
||||||
const tabs = browser.pages()
|
const tabs = browser.pages()
|
||||||
|
|
||||||
// If more than 3 tabs are open, close the last tab
|
try {
|
||||||
if (tabs.length > 2) {
|
if (tabs.length > 2) {
|
||||||
await lastTab.close()
|
// If more than 2 tabs are open, close the last tab
|
||||||
|
|
||||||
// If only 1 tab is open, open a new one to search in
|
await lastTab.close()
|
||||||
} else if (tabs.length === 1) {
|
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', `More than 2 were open, closed the last tab: "${new URL(lastTab.url()).host}"`)
|
||||||
const newPage = await browser.newPage()
|
|
||||||
await newPage.goto(url)
|
|
||||||
|
|
||||||
// Else go back one page
|
} else if (tabs.length === 1) {
|
||||||
} else {
|
// 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)
|
||||||
|
|
||||||
|
await newPage.goto(this.bingHome)
|
||||||
|
await this.bot.utils.wait(3000)
|
||||||
|
this.searchPageURL = newPage.url()
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'There was only 1 tab open, crated a new one')
|
||||||
|
} else {
|
||||||
|
// Else reset the last tab back to the search listing or Bing.com
|
||||||
|
|
||||||
|
lastTab = await this.bot.browser.utils.getLatestTab(lastTab)
|
||||||
|
await lastTab.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-CLOSE-TABS', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private calculatePoints(counters: Counters) {
|
private calculatePoints(counters: Counters) {
|
||||||
const mobileData = counters.mobileSearch?.[0] // Mobile searches
|
const mobileData = counters.mobileSearch?.[0] // Mobile searches
|
||||||
@@ -332,4 +383,18 @@ export class Search extends Workers {
|
|||||||
|
|
||||||
return missingPoints
|
return missingPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async closeContinuePopup(page: Page) {
|
||||||
|
try {
|
||||||
|
await page.waitForSelector('#sacs_close', { timeout: 1000 })
|
||||||
|
const continueButton = await page.$('#sacs_close')
|
||||||
|
|
||||||
|
if (continueButton) {
|
||||||
|
await continueButton.click()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Continue if element is not found or other error occurs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
75
src/functions/activities/SearchOnBing.ts
Normal file
75
src/functions/activities/SearchOnBing.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
|
import { MorePromotion, PromotionalItem } from '../../interface/DashboardData'
|
||||||
|
|
||||||
|
|
||||||
|
export class SearchOnBing extends Workers {
|
||||||
|
|
||||||
|
async doSearchOnBing(page: Page, activity: MorePromotion | PromotionalItem) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Trying to complete SearchOnBing')
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.bot.utils.wait(5000)
|
||||||
|
|
||||||
|
await this.bot.browser.utils.tryDismissAllMessages(page)
|
||||||
|
|
||||||
|
const query = await this.getSearchQuery(activity.title)
|
||||||
|
|
||||||
|
const searchBar = '#sb_form_q'
|
||||||
|
await page.waitForSelector(searchBar, { state: 'visible', timeout: 10000 })
|
||||||
|
await page.click(searchBar)
|
||||||
|
await this.bot.utils.wait(500)
|
||||||
|
await page.keyboard.type(query)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await this.bot.utils.wait(3000)
|
||||||
|
|
||||||
|
await page.close()
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'Completed the SearchOnBing successfully')
|
||||||
|
} catch (error) {
|
||||||
|
await page.close()
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING', 'An error occurred:' + error, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSearchQuery(title: string): Promise<string> {
|
||||||
|
interface Queries {
|
||||||
|
title: string;
|
||||||
|
queries: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let queries: Queries[] = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.bot.config.searchOnBingLocalQueries) {
|
||||||
|
const data = fs.readFileSync(path.join(__dirname, '../queries.json'), 'utf8')
|
||||||
|
queries = JSON.parse(data)
|
||||||
|
} else {
|
||||||
|
// Fetch from the repo directly so the user doesn't need to redownload the script for the new activities
|
||||||
|
const response = await this.bot.axios.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://raw.githubusercontent.com/TheNetsky/Microsoft-Rewards-Script/refs/heads/main/src/functions/queries.json'
|
||||||
|
})
|
||||||
|
queries = response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
const answers = queries.find(x => this.normalizeString(x.title) === this.normalizeString(title))
|
||||||
|
const answer = answers ? this.bot.utils.shuffleArray(answers?.queries)[0] as string : title
|
||||||
|
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', `Fetched answer: ${answer} | question: ${title}`)
|
||||||
|
return answer
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log(this.bot.isMobile, 'SEARCH-ON-BING-QUERY', 'An error occurred:' + error, 'error')
|
||||||
|
return title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeString(string: string): string {
|
||||||
|
return string.normalize('NFD').trim().toLowerCase().replace(/[^\x20-\x7E]/g, '').replace(/[?!]/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Page } from 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,7 +6,7 @@ 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')
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -15,7 +15,7 @@ export class ThisOrThat extends Workers {
|
|||||||
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)
|
||||||
@@ -32,15 +32,15 @@ export class ThisOrThat 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bot.log('THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
this.bot.log(this.bot.isMobile, 'THIS-OR-THAT', 'Completed the ThisOrThat successfully')
|
||||||
} catch (error) {
|
} 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 'playwright'
|
import { Page } from 'rebrowser-playwright'
|
||||||
|
|
||||||
import { Workers } from '../Workers'
|
import { Workers } from '../Workers'
|
||||||
|
|
||||||
@@ -6,17 +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 {
|
||||||
this.bot.utils.wait(2000)
|
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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
289
src/functions/queries.json
Normal file
289
src/functions/queries.json
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "Maisons près de chez vous",
|
||||||
|
"queries": [
|
||||||
|
"Maisons près de chez moi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Vous ressentez des symptômes ?",
|
||||||
|
"queries": [
|
||||||
|
"Éruption cutanée sur l'avant-bras",
|
||||||
|
"Nez bouché",
|
||||||
|
"Toux chatouilleuse"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Faites vos achats plus vite",
|
||||||
|
"queries": [
|
||||||
|
"Acheter une PS5",
|
||||||
|
"Acheter une Xbox",
|
||||||
|
"Offres sur les chaises"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Traduisez tout !",
|
||||||
|
"queries": [
|
||||||
|
"Traduction bienvenue à la maison en coréen",
|
||||||
|
"Traduction bienvenue à la maison en japonais",
|
||||||
|
"Traduction au revoir en japonais"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Rechercher paroles de chanson",
|
||||||
|
"queries": [
|
||||||
|
"Paroles de Debarge rhythm of the night"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Et si nous regardions ce film une nouvelle fois?",
|
||||||
|
"queries": [
|
||||||
|
"Alien film",
|
||||||
|
"Film Aliens",
|
||||||
|
"Film Alien 3",
|
||||||
|
"Film Predator"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Planifiez une petite escapade",
|
||||||
|
"queries": [
|
||||||
|
"Vols Amsterdam-Tokyo",
|
||||||
|
"Vols New York-Tokyo"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Consulter postes à pourvoir",
|
||||||
|
"queries": [
|
||||||
|
"emplois chez Microsoft",
|
||||||
|
"Offres d'emploi Microsoft",
|
||||||
|
"Emplois près de chez moi",
|
||||||
|
"emplois chez Boeing"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Vous pouvez suivre votre colis",
|
||||||
|
"queries": [
|
||||||
|
"Suivi Chronopost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Trouver un endroit à découvrir",
|
||||||
|
"queries": [
|
||||||
|
"Itinéraire vers Berlin",
|
||||||
|
"Itinéraire vers Tokyo",
|
||||||
|
"Itinéraire vers New York"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Trop fatigué pour cuisiner ce soir ?",
|
||||||
|
"queries": [
|
||||||
|
"KFC près de chez moi",
|
||||||
|
"Burger King près de chez moi",
|
||||||
|
"McDonalds près de chez moi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Convertissez rapidement votre argent",
|
||||||
|
"queries": [
|
||||||
|
"convertir 250 EUR en yen",
|
||||||
|
"convertir 500 EUR en yen"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Apprenez à cuisiner une nouvelle recette",
|
||||||
|
"queries": [
|
||||||
|
"Comment faire cuire la ratatouille",
|
||||||
|
"Comment faire cuire les lasagnes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Trouvez des emplacements pour rester!",
|
||||||
|
"queries": [
|
||||||
|
"Hôtels Berlin Allemagne",
|
||||||
|
"Hôtels Amsterdam Pays-Bas"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Comment se porte l'économie ?",
|
||||||
|
"queries": [
|
||||||
|
"CAC 40"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Qui a gagné ?",
|
||||||
|
"queries": [
|
||||||
|
"score du Paris Saint-Germain"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Temps de jeu",
|
||||||
|
"queries": [
|
||||||
|
"Jeu vidéo Overwatch",
|
||||||
|
"Jeu vidéo Call of Duty"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Enrichissez votre vocabulaire",
|
||||||
|
"queries": [
|
||||||
|
"definition definition"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Quelle heure est-il ?",
|
||||||
|
"queries": [
|
||||||
|
"Heure du Japon",
|
||||||
|
"Heure de New York"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Vérifier la météo",
|
||||||
|
"queries": [
|
||||||
|
"Météo de Paris",
|
||||||
|
"Météo de la France"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Tenez-vous informé des sujets d'actualité",
|
||||||
|
"queries": [
|
||||||
|
"Augmentation Impots",
|
||||||
|
"Mort célébrité"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
204
src/index.ts
204
src/index.ts
@@ -1,5 +1,5 @@
|
|||||||
import cluster from 'cluster'
|
import cluster from 'cluster'
|
||||||
import { BrowserContext, Page } from 'playwright'
|
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'
|
||||||
@@ -14,6 +14,8 @@ 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 {
|
||||||
@@ -25,17 +27,25 @@ export class MicrosoftRewardsBot {
|
|||||||
func: BrowserFunc,
|
func: BrowserFunc,
|
||||||
utils: BrowserUtil
|
utils: BrowserUtil
|
||||||
}
|
}
|
||||||
public isMobile: boolean = false
|
public isMobile: boolean
|
||||||
public homePage!: Page
|
public homePage!: Page
|
||||||
|
|
||||||
private collectedPoints: number = 0
|
private pointsCanCollect: number = 0
|
||||||
|
private pointsInitial: 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 = []
|
||||||
@@ -47,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() {
|
||||||
@@ -54,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) {
|
||||||
@@ -64,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)
|
||||||
|
|
||||||
@@ -82,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)
|
||||||
@@ -102,53 +113,68 @@ 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) {
|
||||||
this.isMobile = false
|
|
||||||
|
|
||||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
this.homePage = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
|
|
||||||
log('MAIN', 'Starting DESKTOP browser')
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
|
|
||||||
// Login into MS Rewards, then go to rewards homepage
|
// Login into MS Rewards, then go to rewards homepage
|
||||||
await this.login.login(this.homePage, account.email, account.password)
|
await this.login.login(this.homePage, account.email, account.password)
|
||||||
|
|
||||||
await this.browser.func.goHome(this.homePage)
|
await this.browser.func.goHome(this.homePage)
|
||||||
|
|
||||||
const data = await this.browser.func.getDashboardData()
|
const data = await this.browser.func.getDashboardData()
|
||||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
|
||||||
|
|
||||||
const earnablePoints = await this.browser.func.getEarnablePoints()
|
this.pointsInitial = data.userStatus.availablePoints
|
||||||
this.collectedPoints = earnablePoints
|
|
||||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
|
log(this.isMobile, 'MAIN-POINTS', `Current point count: ${this.pointsInitial}`)
|
||||||
|
|
||||||
|
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||||
|
|
||||||
|
// 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 this.closeBrowser(browser, account.email)
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a new tab to where the tasks are going to be completed
|
// Open a new tab to where the tasks are going to be completed
|
||||||
@@ -181,83 +207,113 @@ export class MicrosoftRewardsBot {
|
|||||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||||
|
|
||||||
// Close desktop browser
|
// Close desktop browser
|
||||||
return await this.closeBrowser(browser, account.email)
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile
|
// Mobile
|
||||||
async Mobile(account: Account) {
|
async Mobile(account: Account) {
|
||||||
this.isMobile = true
|
|
||||||
|
|
||||||
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
|
||||||
this.homePage = await browser.newPage()
|
this.homePage = await browser.newPage()
|
||||||
|
|
||||||
log('MAIN', 'Starting MOBILE browser')
|
log(this.isMobile, 'MAIN', 'Starting browser')
|
||||||
|
|
||||||
// Login into MS Rewards, then go to rewards homepage
|
// Login into MS Rewards, then go to rewards homepage
|
||||||
await this.login.login(this.homePage, account.email, account.password)
|
await this.login.login(this.homePage, account.email, account.password)
|
||||||
|
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||||
|
|
||||||
await this.browser.func.goHome(this.homePage)
|
await this.browser.func.goHome(this.homePage)
|
||||||
|
|
||||||
const data = await this.browser.func.getDashboardData()
|
const data = await this.browser.func.getDashboardData()
|
||||||
|
|
||||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||||
if (!data.userStatus.counters.mobileSearch) {
|
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||||
log('MAIN', 'No mobile searches found, stopping!')
|
|
||||||
|
this.pointsCanCollect = browserEnarablePoints.mobileSearchPoints + appEarnablePoints.totalEarnablePoints
|
||||||
|
|
||||||
|
log(this.isMobile, 'MAIN-POINTS', `You can earn ${this.pointsCanCollect} points today (Browser: ${browserEnarablePoints.mobileSearchPoints} points, App: ${appEarnablePoints.totalEarnablePoints} points)`)
|
||||||
|
|
||||||
|
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||||
|
if (!this.config.runOnZeroPoints && this.pointsCanCollect === 0) {
|
||||||
|
log(this.isMobile, 'MAIN', 'No points to earn and "runOnZeroPoints" is set to "false", stopping!', 'log', 'yellow')
|
||||||
|
|
||||||
// Close mobile browser
|
// Close mobile browser
|
||||||
return await this.closeBrowser(browser, account.email)
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a new tab to where the tasks are going to be completed
|
// Do daily check in
|
||||||
const workerPage = await browser.newPage()
|
if (this.config.workers.doDailyCheckIn) {
|
||||||
|
await this.activities.doDailyCheckIn(this.accessToken, data)
|
||||||
|
}
|
||||||
|
|
||||||
// Go to homepage on worker page
|
// Do read to earn
|
||||||
await this.browser.func.goHome(workerPage)
|
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(workerPage, data)
|
// 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()
|
||||||
|
|
||||||
// Fetch current search points
|
// Go to homepage on worker page
|
||||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
await this.browser.func.goHome(workerPage)
|
||||||
|
|
||||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
await this.activities.doSearch(workerPage, data)
|
||||||
// Retry until all points are gathered when (retryMobileSearch is enabled)
|
|
||||||
if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
|
|
||||||
log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...')
|
|
||||||
|
|
||||||
// Close mobile browser
|
// Fetch current search points
|
||||||
await this.closeBrowser(browser, account.email)
|
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||||
|
|
||||||
// Retry
|
if (mobileSearchPoints && (mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0) {
|
||||||
await this.Mobile(account)
|
// 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()
|
|
||||||
|
|
||||||
// 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
|
||||||
return await this.closeBrowser(browser, account.email)
|
await this.browser.func.closeBrowser(browser, account.email)
|
||||||
}
|
return
|
||||||
|
|
||||||
private async closeBrowser(browser: BrowserContext, email: string) {
|
|
||||||
// Save cookies
|
|
||||||
await saveSessionData(this.config.sessionPath, browser, email, this.isMobile)
|
|
||||||
|
|
||||||
// Close browser
|
|
||||||
await browser.close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const rewardsBot = new MicrosoftRewardsBot(false)
|
||||||
|
|
||||||
const bot = new MicrosoftRewardsBot()
|
try {
|
||||||
|
await rewardsBot.initialize()
|
||||||
|
await rewardsBot.run()
|
||||||
|
} catch (error) {
|
||||||
|
log(false, 'MAIN-ERROR', `Error running desktop bot: ${error}`, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize accounts first and then start the bot
|
// Start the bots
|
||||||
bot.initialize().then(() => {
|
main().catch(error => {
|
||||||
bot.run()
|
log('main', 'MAIN-ERROR', `Error running bots: ${error}`, 'error')
|
||||||
|
process.exit(1)
|
||||||
})
|
})
|
||||||
@@ -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,36 +2,54 @@ 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;
|
saveFingerprint: ConfigSaveFingerprint;
|
||||||
searchSettings: SearchSettings;
|
workers: ConfigWorkers;
|
||||||
webhook: Webhook;
|
searchOnBingLocalQueries: boolean;
|
||||||
saveFingerprint: boolean;
|
globalTimeout: number | string;
|
||||||
|
searchSettings: ConfigSearchSettings;
|
||||||
|
logExcludeFunc: string[];
|
||||||
|
webhookLogExcludeFunc: string[];
|
||||||
|
proxy: ConfigProxy;
|
||||||
|
webhook: ConfigWebhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchSettings {
|
export interface ConfigSaveFingerprint {
|
||||||
|
mobile: boolean;
|
||||||
|
desktop: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigSearchSettings {
|
||||||
useGeoLocaleQueries: boolean;
|
useGeoLocaleQueries: boolean;
|
||||||
scrollRandomResults: boolean;
|
scrollRandomResults: boolean;
|
||||||
clickRandomResults: boolean;
|
clickRandomResults: boolean;
|
||||||
searchDelay: SearchDelay;
|
searchDelay: ConfigSearchDelay;
|
||||||
retryMobileSearch: boolean;
|
retryMobileSearchAmount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchDelay {
|
export interface ConfigSearchDelay {
|
||||||
min: number;
|
min: number | string;
|
||||||
max: number;
|
max: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Webhook {
|
export interface ConfigWebhook {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Workers {
|
export interface ConfigProxy {
|
||||||
|
proxyGoogleTrends: boolean;
|
||||||
|
proxyBingTerms: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigWorkers {
|
||||||
doDailySet: boolean;
|
doDailySet: boolean;
|
||||||
doMorePromotions: boolean;
|
doMorePromotions: boolean;
|
||||||
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
|
||||||
|
}
|
||||||
32
src/run_daily.sh
Normal file
32
src/run_daily.sh
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/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..."
|
||||||
|
|
||||||
|
# Execute the Node.js script directly
|
||||||
|
npm run start
|
||||||
49
src/util/Axios.ts
Normal file
49
src/util/Axios.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
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, bypassProxy = false): Promise<AxiosResponse> {
|
||||||
|
if (bypassProxy) {
|
||||||
|
const bypassInstance = axios.create()
|
||||||
|
return bypassInstance.request(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.instance.request(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AxiosClient
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import { BrowserContext, Cookie } from 'playwright'
|
import { BrowserContext, Cookie } from 'rebrowser-playwright'
|
||||||
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
import { 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 { Account } from '../interface/Account'
|
||||||
import { Config } from '../interface/Config'
|
import { Config, ConfigSaveFingerprint } from '../interface/Config'
|
||||||
|
|
||||||
|
let configCache: Config
|
||||||
|
|
||||||
export function loadAccounts(): Account[] {
|
export function loadAccounts(): Account[] {
|
||||||
try {
|
try {
|
||||||
@@ -28,16 +29,23 @@ 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) {
|
} catch (error) {
|
||||||
throw new Error(error as string)
|
throw new Error(error as string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, getFingerprint: boolean) {
|
export async function loadSessionData(sessionPath: string, email: string, isMobile: boolean, saveFingerprint: ConfigSaveFingerprint) {
|
||||||
try {
|
try {
|
||||||
// Fetch cookie file
|
// Fetch cookie file
|
||||||
const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`)
|
const cookieFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_cookies' : 'desktop_cookies'}.json`)
|
||||||
@@ -52,7 +60,7 @@ export async function loadSessionData(sessionPath: string, email: string, isMobi
|
|||||||
const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`)
|
const fingerprintFile = path.join(__dirname, '../browser/', sessionPath, email, `${isMobile ? 'mobile_fingerpint' : 'desktop_fingerpint'}.json`)
|
||||||
|
|
||||||
let fingerprint!: BrowserFingerprintWithHeaders
|
let fingerprint!: BrowserFingerprintWithHeaders
|
||||||
if (getFingerprint && fs.existsSync(fingerprintFile)) {
|
if (((saveFingerprint.desktop && !isMobile) || (saveFingerprint.mobile && isMobile)) && fs.existsSync(fingerprintFile)) {
|
||||||
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
const fingerprintData = await fs.promises.readFile(fingerprintFile, 'utf-8')
|
||||||
fingerprint = JSON.parse(fingerprintData)
|
fingerprint = JSON.parse(fingerprintData)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,45 @@
|
|||||||
|
import chalk from 'chalk'
|
||||||
|
|
||||||
import { Webhook } from './Webhook'
|
import { Webhook } from './Webhook'
|
||||||
|
import { loadConfig } from './Load'
|
||||||
|
|
||||||
|
|
||||||
|
export function log(isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof chalk): void {
|
||||||
|
const configData = loadConfig()
|
||||||
|
|
||||||
|
if (configData.logExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
export function log(title: string, message: string, type?: 'log' | 'warn' | 'error') {
|
|
||||||
const currentTime = new Date().toLocaleString()
|
const currentTime = new Date().toLocaleString()
|
||||||
|
const platformText = isMobile === 'main' ? 'MAIN' : isMobile ? 'MOBILE' : 'DESKTOP'
|
||||||
|
const chalkedPlatform = isMobile === 'main' ? chalk.bgCyan('MAIN') : isMobile ? chalk.bgBlue('MOBILE') : chalk.bgMagenta('DESKTOP')
|
||||||
|
|
||||||
let str = ''
|
// Clean string for the Webhook (no chalk)
|
||||||
|
const cleanStr = `[${currentTime}] [PID: ${process.pid}] [${type.toUpperCase()}] ${platformText} [${title}] ${message}`
|
||||||
|
|
||||||
|
// Send the clean string to the Webhook
|
||||||
|
if (!configData.webhookLogExcludeFunc.some(x => x.toLowerCase() === title.toLowerCase())) {
|
||||||
|
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,7 +78,7 @@ 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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,20 +93,51 @@ 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,
|
||||||
chrome_major_version: chromeMajorVersion as string,
|
chrome_major_version: chromeMajorVersion as string,
|
||||||
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> {
|
||||||
@@ -16,11 +18,9 @@ export default class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shuffleArray<T>(array: T[]): T[] {
|
shuffleArray<T>(array: T[]): T[] {
|
||||||
const shuffledArray = array.slice()
|
return array.map(value => ({ value, sort: Math.random() }))
|
||||||
|
.sort((a, b) => a.sort - b.sort)
|
||||||
shuffledArray.sort(() => Math.random() - 0.5)
|
.map(({ value }) => value)
|
||||||
|
|
||||||
return shuffledArray
|
|
||||||
}
|
}
|
||||||
|
|
||||||
randomNumber(min: number, max: number): number {
|
randomNumber(min: number, max: number): number {
|
||||||
@@ -39,4 +39,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