mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-12 03:16:19 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f015649d16 | ||
|
|
034359019c | ||
|
|
09ddbee45a | ||
|
|
7939196e88 | ||
|
|
5bc66c5fc9 | ||
|
|
c70d6f9cb1 | ||
|
|
a47b86e74d | ||
|
|
ce2a72ee36 | ||
|
|
755237caa1 | ||
|
|
2b4cd505c0 | ||
|
|
a39a861dab | ||
|
|
8d19129906 | ||
|
|
c6ab80fe54 | ||
|
|
9b1eed526f | ||
|
|
9a144b2e60 | ||
|
|
28b1881642 | ||
|
|
ef6ad569ff | ||
|
|
da9ba91c5c | ||
|
|
deb2d58b1b | ||
|
|
66a82c2584 | ||
|
|
8a022d5983 | ||
|
|
64048e35d7 | ||
|
|
cf7f7ac790 | ||
|
|
f7aa5039f9 | ||
|
|
e082fb03f0 | ||
|
|
0303b8c605 |
@@ -28,6 +28,10 @@ module.exports = {
|
||||
'error',
|
||||
'never'
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any':
|
||||
['warn', {
|
||||
fixToUnknown: true // This line is optional and only relevant if you are using TypeScript
|
||||
}],
|
||||
'comma-dangle': 'off',
|
||||
'@typescript-eslint/comma-dangle': 'error',
|
||||
'prefer-arrow-callback': 'error'
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ package-lock.json
|
||||
accounts.json
|
||||
notes
|
||||
accounts.dev.json
|
||||
accounts.main.json
|
||||
accounts.main.json
|
||||
.DS_Store
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -4,20 +4,12 @@ FROM node:18
|
||||
# Set the working directory in the container
|
||||
WORKDIR /usr/src/microsoft-rewards-script
|
||||
|
||||
# Install jq
|
||||
RUN apt-get update && apt-get install -y jq
|
||||
|
||||
# Install jq, cron, and gettext-base
|
||||
RUN apt-get update && apt-get install -y jq cron gettext-base
|
||||
|
||||
# Copy all files to the working directory
|
||||
COPY . .
|
||||
|
||||
# Check if "headless" is set to "true" in the config.json file
|
||||
RUN HEADLESS=$(cat src/config.json | jq -r .headless) \
|
||||
&& if [ "$HEADLESS" != "true" ]; then \
|
||||
echo "Error: 'headless' in src/config.json is not true."; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Install dependencies including Playwright
|
||||
RUN apt-get install -y \
|
||||
xvfb \
|
||||
@@ -32,11 +24,23 @@ RUN apt-get install -y \
|
||||
# Install application dependencies
|
||||
RUN npm install
|
||||
|
||||
# Ensure correct permissions for node_modules
|
||||
RUN chmod -R 755 /usr/src/microsoft-rewards-script/node_modules
|
||||
|
||||
# Install Playwright Chromium directly from local node_modules
|
||||
RUN ./node_modules/.bin/playwright install chromium
|
||||
|
||||
# Build the script
|
||||
RUN npm run build
|
||||
|
||||
# Install playwright chromium
|
||||
RUN npx playwright install chromium
|
||||
# Copy cron file to cron directory
|
||||
COPY src/crontab.template /etc/cron.d/microsoft-rewards-cron.template
|
||||
|
||||
# Define the command to run your application
|
||||
CMD ["npm", "start"]
|
||||
# Create the log file to be able to run tail
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
# Ensure correct permissions for the working directory
|
||||
RUN chmod -R 755 /usr/src/microsoft-rewards-script
|
||||
|
||||
# Define the command to run your application with cron optionally
|
||||
CMD sh -c 'node src/updateConfig.js && echo "$TZ" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata && if [ "$RUN_ON_START" = "true" ]; then npm start; fi && envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && crontab /etc/cron.d/microsoft-rewards-cron && cron && tail -f /var/log/cron.log'
|
||||
|
||||
91
README.md
91
README.md
@@ -12,41 +12,80 @@ Under development, however mainly for personal use!
|
||||
6. Run `npm run start` to start the built script
|
||||
|
||||
## Notes ##
|
||||
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run chrome-kill-win` script. (Windows)
|
||||
- If you end the script without closing the browser window first (only with headless as false), you'll be left with hanging chrome instances using resources. Use taskmanager to kill these or use the included `npm run kill-chrome-win` script. (Windows)
|
||||
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
|
||||
|
||||
## Docker (Experimental) ##
|
||||
**Note:** If you had previously built and run the script locally, remove the `/node_modules` and `/dist` from your Microsoft-Rewards-Script folder.
|
||||
|
||||
1. Download the source code
|
||||
2. Make changes to your `accounts.json` and/or `config.json`
|
||||
3. Run `docker build -t microsoft-rewards-script-docker .`
|
||||
- Docker container has to be recreated for any changes regarding the `config.json` and/or `accounts.json`!
|
||||
2. Make changes to your `accounts.json` and `config.json`
|
||||
3. **Headless mode must be enabled.** You can do this in `config.json` or by using the `HEADLESS=true` environmental variable in docker run or docker compose.yaml (see below). Environmental variables are prioritized over the values in config.json.
|
||||
4. The container has in-built scheduling. Customize your schedule using the `CRON_START_TIME` environmental variable. Use [crontab.guru](crontab.guru) if you're unsure how to create a cron schedule.
|
||||
5. **Note:** the container will add between 5 and 50 minutes of randomized variability to your scheduled start times.
|
||||
|
||||
### Option 1: build and run with docker run
|
||||
|
||||
1. Build or re-build the container image with: `docker build -t microsoft-rewards-script-docker .`
|
||||
|
||||
2. Run the container with:
|
||||
|
||||
```bash
|
||||
docker run --name netsky -d \
|
||||
-e TZ=America/New_York \
|
||||
-e HEADLESS=true \
|
||||
-e RUN_ON_START=true \
|
||||
-e CRON_START_TIME="0 5,11 * * *" \
|
||||
microsoft-rewards-script-docker
|
||||
```
|
||||
|
||||
3. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||
|
||||
4. You can view logs with `docker logs netsky`.
|
||||
|
||||
### Option 2: use docker compose
|
||||
|
||||
1. A basic docker compose.yaml has been provided.
|
||||
|
||||
2. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||
|
||||
3. Build and start the container using `docker compose up -d`.
|
||||
|
||||
4. You can view logs with `docker logs netsky`
|
||||
|
||||
|
||||
## Config ##
|
||||
| Setting | Description | Default |
|
||||
| :------------- |:-------------| :-----|
|
||||
| baseURL | MS Rewards page | `https://rewards.bing.com` |
|
||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) |
|
||||
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) |
|
||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) |
|
||||
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) |
|
||||
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) |
|
||||
| workers.doDailySet | Complete daily set items | `true` |
|
||||
| workers.doMorePromotions | Complete promotional items | `true` |
|
||||
| workers.doPunchCards | Complete punchcards | `true` |
|
||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
|
||||
| workers.doMobileSearch | Complete daily mobile searches | `true` |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
|
||||
| scrollRandomResults | Scroll randomly in search results | `true` |
|
||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
||||
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) |
|
||||
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` |
|
||||
| webhook.enabled | Enable or disable your set webhook | `false` |
|
||||
| webhook.url | Your Discord webhook URL | `null` |
|
||||
| Setting | Description | Default | Docker Environmental Variable |
|
||||
| :------------- |:-------------| :-----| :-----|
|
||||
| baseURL | MS Rewards page | `https://rewards.bing.com` | BASE_URL |
|
||||
| sessionPath | Path to where you want sessions/fingerprints to be stored | `sessions` (In ./browser/sessions) | SESSION_PATH |
|
||||
| headless | If the browser window should be visible be ran in the background | `false` (Browser is visible) | HEADLESS *(must be set to `=true` for docker)* |
|
||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) | RUN_ON_ZERO_POINTS |
|
||||
| clusters | Amount of instances ran on launch, 1 per account | `1` (Will run 1 account at the time) | CLUSTERS |
|
||||
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) | SAVE_FINGERPRINT |
|
||||
| workers.doDailySet | Complete daily set items | `true` | DO_DAILY_SET |
|
||||
| workers.doMorePromotions | Complete promotional items | `true` | DO_MORE_PROMOTIONS |
|
||||
| workers.doPunchCards | Complete punchcards | `true` | DO_PUNCH_CARDS |
|
||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` | DO_DESKTOP_SEARCH |
|
||||
| workers.doMobileSearch | Complete daily mobile searches | `true` | DO_MOBILE_SEARCH |
|
||||
| workers.doDailyCheckIn | Complete daily check-in activity | `true` | DO_DAILY_CHECK_IN |
|
||||
| workers.doReadToEarn | Complete read to earn activity | `true` | DO_READ_TO_EARN |
|
||||
| globalTimeout | The length before the action gets timeout | `30000` (30 seconds) | GLOBAL_TIMEOUT |
|
||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `true` (Uses EN-US generated queries) | USE_GEO_LOCALE_QUERIES |
|
||||
| scrollRandomResults | Scroll randomly in search results | `true` | SCROLL_RANDOM_RESULTS |
|
||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` | CLICK_RANDOM_RESULTS |
|
||||
| searchSettings.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) | SEARCH_DELAY_MIN SEARCH_DELAY_MAX |
|
||||
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` | RETRY_MOBILE_SEARCH |
|
||||
| webhook.enabled | Enable or disable your set webhook | `false` | WEBHOOK_ENABLED |
|
||||
| webhook.url | Your Discord webhook URL | `null` | WEBHOOK_URL="" |
|
||||
| cronStartTime | Scheduled script run-time, *only available for docker implementation* | `0 5,11 * * *` (5:00 am, 11:00 am daily) | CRON_START_TIME="" |
|
||||
| | Run the script immediately when the Docker container starts | `true` | RUN_ON_START |
|
||||
|
||||
## Features ##
|
||||
- [x] Multi-Account Support
|
||||
- [x] Session Storing
|
||||
- [x] 2FA Support
|
||||
- [x] Passwordless Support
|
||||
- [x] Headless Support
|
||||
- [x] Discord Webhook Support
|
||||
- [x] Desktop Searches
|
||||
@@ -65,10 +104,14 @@ Under development, however mainly for personal use!
|
||||
- [x] Completing Punchcards
|
||||
- [x] Solving This Or That Quiz (Random)
|
||||
- [x] Solving ABC Quiz
|
||||
- [x] Completing Daily Check In
|
||||
- [x] Completing Read To Earn
|
||||
- [ ] Completing Shopping Game
|
||||
- [ ] Completing Gaming Tab
|
||||
- [x] Clustering Support
|
||||
- [x] Proxy Support
|
||||
- [x] Docker Support (experimental)
|
||||
- [x] Automatic scheduling (via Docker)
|
||||
|
||||
## Disclaimer ##
|
||||
Your account may be at risk of getting banned or suspended using this script, you've been warned!
|
||||
|
||||
13
compose.yaml
Normal file
13
compose.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
microsoft-rewards-script:
|
||||
build: .
|
||||
container_name: netsky
|
||||
environment:
|
||||
- TZ=America/Toronto #change to your local timezone
|
||||
- NODE_ENV=production
|
||||
- HEADLESS=true #do not change
|
||||
### Customize your run schedule, default 5:00 am and 11:00 am, use crontab.guru for formatting
|
||||
- CRON_START_TIME=0 5,11 * * *
|
||||
### Run on start, set to false to only run the script per the cron schedule
|
||||
- RUN_ON_START=true
|
||||
restart: unless-stopped
|
||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "microsoft-rewards-script",
|
||||
"version": "1.4.3",
|
||||
"version": "1.4.10",
|
||||
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
|
||||
"main": "index.js",
|
||||
"engines": {
|
||||
@@ -26,17 +26,17 @@
|
||||
"author": "Netsky",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"eslint": "^8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-modules-newline": "^0.0.6",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.5.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.5",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"fingerprint-generator": "^2.1.46",
|
||||
"fingerprint-injector": "^2.1.46",
|
||||
"playwright": "^1.41.1",
|
||||
"axios": "^1.7.5",
|
||||
"cheerio": "^1.0.0",
|
||||
"fingerprint-generator": "^2.1.56",
|
||||
"fingerprint-injector": "^2.1.56",
|
||||
"playwright": "^1.47.2",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { AccountProxy } from '../interface/Account'
|
||||
https://abrahamjuliot.github.io/creepjs/
|
||||
https://botcheck.luminati.io/
|
||||
http://f.vision/
|
||||
https://pixelscan.net/
|
||||
*/
|
||||
|
||||
class Browser {
|
||||
@@ -43,6 +44,9 @@ class Browser {
|
||||
|
||||
const context = await newInjectedContext(browser, { fingerprint: fingerpint })
|
||||
|
||||
// Set timeout to preferred amount
|
||||
context.setDefaultTimeout(this.bot.config?.globalTimeout ?? 30_000)
|
||||
|
||||
await context.addCookies(sessionData.cookies)
|
||||
|
||||
if (this.bot.config.saveFingerprint) {
|
||||
@@ -58,7 +62,7 @@ class Browser {
|
||||
const fingerPrintData = new FingerprintGenerator().getFingerprint({
|
||||
devices: this.bot.isMobile ? ['mobile'] : ['desktop'],
|
||||
operatingSystems: this.bot.isMobile ? ['android'] : ['windows'],
|
||||
browsers: ['edge']
|
||||
browserListQuery: 'last 2 edge version'
|
||||
})
|
||||
|
||||
return fingerPrintData
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Page } from 'playwright'
|
||||
import { CheerioAPI, load } from 'cheerio'
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
|
||||
import { Counters, DashboardData, MorePromotion, PromotionalItem } from './../interface/DashboardData'
|
||||
import { QuizData } from './../interface/QuizData'
|
||||
import { AppUserData } from '../interface/AppUserData'
|
||||
|
||||
|
||||
export default class BrowserFunc {
|
||||
@@ -95,13 +97,13 @@ export default class BrowserFunc {
|
||||
const scripts = Array.from(document.querySelectorAll('script'))
|
||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||
|
||||
if (targetScript) {
|
||||
return targetScript.innerText
|
||||
} else {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
||||
}
|
||||
return targetScript?.innerText ? targetScript.innerText : null
|
||||
})
|
||||
|
||||
if (!scriptContent) {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
|
||||
// Extract the dashboard object from the script content
|
||||
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
||||
// Extract the dashboard object using regex
|
||||
@@ -110,11 +112,13 @@ export default class BrowserFunc {
|
||||
|
||||
if (match && match[1]) {
|
||||
return JSON.parse(match[1])
|
||||
} else {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||
}
|
||||
}, scriptContent)
|
||||
|
||||
if (!dashboardData) {
|
||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||
}
|
||||
|
||||
return dashboardData
|
||||
}
|
||||
|
||||
@@ -129,10 +133,10 @@ export default class BrowserFunc {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total earnable points
|
||||
* Get total earnable points with web browser
|
||||
* @returns {number} Total earnable points
|
||||
*/
|
||||
async getEarnablePoints(): Promise<number> {
|
||||
async getBrowserEarnablePoints(): Promise<number> {
|
||||
try {
|
||||
const data = await this.getDashboardData()
|
||||
|
||||
@@ -140,7 +144,9 @@ export default class BrowserFunc {
|
||||
let totalEarnablePoints = 0
|
||||
|
||||
// Desktop Search Points
|
||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
if (data.userStatus.counters.pcSearch?.length) {
|
||||
data.userStatus.counters.pcSearch.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
}
|
||||
|
||||
// Mobile Search Points
|
||||
if (data.userStatus.counters.mobileSearch?.length) {
|
||||
@@ -151,16 +157,68 @@ export default class BrowserFunc {
|
||||
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||
|
||||
// More Promotions
|
||||
data.morePromotions.forEach(x => {
|
||||
// Only count points from supported activities
|
||||
if (['quiz', 'urlreward'].includes(x.activityType)) {
|
||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||
}
|
||||
})
|
||||
if (data.morePromotions?.length) {
|
||||
data.morePromotions.forEach(x => {
|
||||
// Only count points from supported activities
|
||||
if (['quiz', 'urlreward'].includes(x.promotionType) && x.exclusiveLockedFeatureStatus !== 'locked') {
|
||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return totalEarnablePoints
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
throw this.bot.log('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): Promise<number> {
|
||||
try {
|
||||
const eligibleOffers = [
|
||||
'ENUS_readarticle3_30points',
|
||||
'Gamification_Sapphire_DailyCheckIn'
|
||||
]
|
||||
let totalEarnablePoints = 0
|
||||
|
||||
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 axios(userDataRequest)).data
|
||||
const userData = userDataResponse.response
|
||||
const eligibleActivities = userData.promotions.filter((x) => eligibleOffers.includes(x.attributes.offerid ?? ''))
|
||||
|
||||
for (const item of eligibleActivities) {
|
||||
if (item.attributes.type === 'msnreadearn') {
|
||||
totalEarnablePoints += parseInt(item.attributes.pointmax ?? '') - parseInt(item.attributes.pointprogress ?? '')
|
||||
break
|
||||
} else if (item.attributes.type === 'checkin') {
|
||||
const checkInDay = parseInt(item.attributes.progress ?? '') % 7
|
||||
|
||||
if (checkInDay < 6 && (new Date()).getDate() != (new Date(item.attributes.last_updated ?? '')).getDate()) {
|
||||
totalEarnablePoints += parseInt(item.attributes['day_' + (checkInDay + 1) + '_points'] ?? '')
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return totalEarnablePoints
|
||||
} catch (error) {
|
||||
throw this.bot.log('GET-APP-EARNABLE-POINTS', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export default class BrowserUtil {
|
||||
|
||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||
const buttons = [
|
||||
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||
{ selector: '#iNext', label: 'iNext' },
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
"doMorePromotions": true,
|
||||
"doPunchCards": true,
|
||||
"doDesktopSearch": true,
|
||||
"doMobileSearch": true
|
||||
"doMobileSearch": true,
|
||||
"doDailyCheckIn": true,
|
||||
"doReadToEarn": true
|
||||
},
|
||||
"globalTimeout": 30000,
|
||||
"searchSettings": {
|
||||
"useGeoLocaleQueries": false,
|
||||
"useGeoLocaleQueries": true,
|
||||
"scrollRandomResults": true,
|
||||
"clickRandomResults": true,
|
||||
"searchDelay": {
|
||||
@@ -25,5 +28,6 @@
|
||||
"webhook": {
|
||||
"enabled": false,
|
||||
"url": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"cronStartTime": "0 5,11 * * *"
|
||||
}
|
||||
1
src/crontab.template
Normal file
1
src/crontab.template
Normal file
@@ -0,0 +1 @@
|
||||
${CRON_START_TIME} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /var/log/cron.log 2>&1
|
||||
@@ -8,6 +8,8 @@ import { Poll } from './activities/Poll'
|
||||
import { Quiz } from './activities/Quiz'
|
||||
import { ThisOrThat } from './activities/ThisOrThat'
|
||||
import { UrlReward } from './activities/UrlReward'
|
||||
import { ReadToEarn } from './activities/ReadToEarn'
|
||||
import { DailyCheckIn } from './activities/DailyCheckIn'
|
||||
|
||||
import { DashboardData } from '../interface/DashboardData'
|
||||
|
||||
@@ -49,4 +51,14 @@ export default class Activities {
|
||||
await urlReward.doUrlReward(page)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,10 @@ import { Page } from 'playwright'
|
||||
import readline from 'readline'
|
||||
|
||||
import { MicrosoftRewardsBot } from '../index'
|
||||
import { saveSessionData } from '../util/Load'
|
||||
import axios from 'axios'
|
||||
import { OAuth } from '../interface/OAuth'
|
||||
import * as crypto from 'crypto'
|
||||
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
@@ -11,6 +15,11 @@ const rl = readline.createInterface({
|
||||
|
||||
export class Login {
|
||||
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) {
|
||||
this.bot = bot
|
||||
@@ -26,14 +35,12 @@ export class Login {
|
||||
|
||||
if (!isLoggedIn) {
|
||||
// Check if account is locked
|
||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { state: 'visible', timeout: 10_000 }).then(() => true).catch(() => false)
|
||||
const isLocked = await page.waitForSelector('.serviceAbusePageContainer', { state: 'visible', timeout: 1000 }).then(() => true).catch(() => false)
|
||||
if (isLocked) {
|
||||
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
||||
throw new Error('Account has been locked!')
|
||||
}
|
||||
|
||||
await page.waitForSelector('#loginHeader', { state: 'visible', timeout: 10_000 })
|
||||
|
||||
await this.execLogin(page, email, password)
|
||||
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
||||
} else {
|
||||
@@ -43,6 +50,9 @@ export class Login {
|
||||
// Check if logged in to bing
|
||||
await this.checkBingLogin(page)
|
||||
|
||||
// Save session
|
||||
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
||||
|
||||
// We're done logging in
|
||||
this.bot.log('LOGIN', 'Logged in successfully')
|
||||
|
||||
@@ -53,46 +63,111 @@ export class Login {
|
||||
}
|
||||
|
||||
private async execLogin(page: Page, email: string, password: string) {
|
||||
await page.type('#i0116', email)
|
||||
try {
|
||||
await this.enterEmail(page, email)
|
||||
await this.enterPassword(page, password)
|
||||
await this.checkLoggedIn(page)
|
||||
} catch (error: any) {
|
||||
this.bot.log('LOGIN', 'An error occurred: ' + error.message, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
private async enterEmail(page: Page, email: string) {
|
||||
await page.fill('#i0116', email)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
this.bot.log('LOGIN', 'Email entered successfully')
|
||||
}
|
||||
|
||||
private async enterPassword(page: Page, password: string) {
|
||||
try {
|
||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||
await this.bot.utils.wait(2000)
|
||||
|
||||
await page.type('#i0118', password)
|
||||
await page.fill('#i0118', password)
|
||||
await page.click('#idSIButton9')
|
||||
|
||||
// When erroring at this stage it means a 2FA code is required
|
||||
} catch (error) {
|
||||
this.bot.log('LOGIN', '2FA code required')
|
||||
|
||||
// Wait for user input
|
||||
const code = await new Promise<string>((resolve) => {
|
||||
rl.question('Enter 2FA code:\n', (input) => {
|
||||
rl.close()
|
||||
resolve(input)
|
||||
})
|
||||
})
|
||||
|
||||
await page.type('input[name="otc"]', code)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
} finally {
|
||||
this.bot.log('LOGIN', 'Password entered successfully')
|
||||
} catch {
|
||||
this.bot.log('LOGIN', 'Password entry failed or 2FA required')
|
||||
await this.handle2FA(page)
|
||||
}
|
||||
}
|
||||
|
||||
const currentURL = new URL(page.url())
|
||||
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: any) {
|
||||
this.bot.log('LOGIN', `2FA handling failed: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
while (currentURL.pathname !== '/' || currentURL.hostname !== 'rewards.bing.com') {
|
||||
private async get2FACode(page: Page): Promise<string | null> {
|
||||
try {
|
||||
const element = await page.waitForSelector('#displaySign', { state: 'visible', timeout: 2000 })
|
||||
return await element.textContent()
|
||||
} catch {
|
||||
await page.click('button[aria-describedby="confirmSendTitle"]')
|
||||
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('LOGIN', `Press the number ${numberToPress} on your Authenticator app to approve the login`)
|
||||
this.bot.log('LOGIN', 'If you press the wrong number or the "DENY" button, try again in 60 seconds')
|
||||
|
||||
await page.waitForSelector('#i0281', { state: 'detached', timeout: 60000 })
|
||||
|
||||
this.bot.log('LOGIN', 'Login successfully approved!')
|
||||
break
|
||||
} catch {
|
||||
this.bot.log('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('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('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)
|
||||
currentURL.href = page.url()
|
||||
const currentURL = new URL(page.url())
|
||||
if (currentURL.hostname === targetHostname && currentURL.pathname === targetPathname) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for login to complete
|
||||
await page.waitForSelector('html[data-role-name="RewardsPortal"]', { timeout: 10_000 })
|
||||
this.bot.log('LOGIN', 'Successfully logged into the rewards portal')
|
||||
}
|
||||
|
||||
private async checkBingLogin(page: Page): Promise<void> {
|
||||
@@ -133,4 +208,50 @@ 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)
|
||||
|
||||
const currentUrl = new URL(page.url())
|
||||
let code: string
|
||||
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
this.bot.log('LOGIN-APP', 'Waiting for authorization')
|
||||
if (currentUrl.hostname === 'login.live.com' && currentUrl.pathname === '/oauth20_desktop.srf') {
|
||||
code = currentUrl.searchParams.get('code')!
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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 = {
|
||||
url: this.tokenUrl,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
data: body.toString()
|
||||
}
|
||||
|
||||
const tokenResponse = await axios(tokenRequest)
|
||||
const tokenData: OAuth = await tokenResponse.data
|
||||
|
||||
this.bot.log('LOGIN-APP', 'Successfully authorized')
|
||||
return tokenData.access_token
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ export class Workers {
|
||||
await page.goto(punchCard.parentPromotion.destinationUrl, { referer: this.bot.config.baseURL })
|
||||
|
||||
// Wait for new page to load, max 10 seconds, however try regardless in case of error
|
||||
await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => { })
|
||||
await page.waitForLoadState('networkidle', { timeout: 5000 }).catch(() => { })
|
||||
|
||||
await this.solveActivities(page, activitiesUncompleted, punchCard)
|
||||
|
||||
@@ -87,7 +87,7 @@ export class Workers {
|
||||
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) {
|
||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
||||
@@ -132,13 +132,13 @@ export class Workers {
|
||||
}
|
||||
|
||||
|
||||
let selector = `[data-bi-id="${activity.offerId}"]`
|
||||
let selector = `[data-bi-id^="${activity.offerId}"]`
|
||||
|
||||
if (punchCard) {
|
||||
selector = await this.bot.browser.func.getPunchCardActivity(activityPage, activity)
|
||||
|
||||
} else if (activity.name.toLowerCase().includes('membercenter')) {
|
||||
selector = `[data-bi-id="${activity.name}"]`
|
||||
selector = `[data-bi-id^="${activity.name}"]`
|
||||
}
|
||||
|
||||
// Click element, it will be opened in a new tab
|
||||
@@ -149,7 +149,7 @@ export class Workers {
|
||||
|
||||
// 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.
|
||||
*/
|
||||
await activityPage.waitForLoadState('networkidle', { timeout: 10_000 }).catch(() => { })
|
||||
|
||||
48
src/functions/activities/DailyCheckIn.ts
Normal file
48
src/functions/activities/DailyCheckIn.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import axios from 'axios'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { DashboardData } from '../../interface/DashboardData'
|
||||
|
||||
|
||||
export class DailyCheckIn extends Workers {
|
||||
public async doDailyCheckIn(accessToken: string, data: DashboardData) {
|
||||
this.bot.log('DAILY-CHECK-IN', 'Starting Daily Check In')
|
||||
|
||||
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 = {
|
||||
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 axios(claimRequest)
|
||||
const claimedPoint = parseInt((await claimResponse.data).response.activity.p)
|
||||
|
||||
this.bot.log('DAILY-CHECK-IN', claimedPoint > 0 ? `Claimed ${claimedPoint} points` : 'Already claimed today')
|
||||
} catch (error) {
|
||||
this.bot.log('DAILY-CHECK-IN', '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 axios from 'axios'
|
||||
import { randomBytes } from 'crypto'
|
||||
|
||||
import { Workers } from '../Workers'
|
||||
|
||||
import { DashboardData } from '../../interface/DashboardData'
|
||||
|
||||
|
||||
export class ReadToEarn extends Workers {
|
||||
public async doReadToEarn(accessToken: string, data: DashboardData) {
|
||||
this.bot.log('READ-TO-EARN', 'Starting Read to Earn')
|
||||
|
||||
try {
|
||||
let geoLocale = data.userProfile.attributes.country
|
||||
geoLocale = (this.bot.config.searchSettings.useGeoLocaleQueries && geoLocale.length === 2) ? geoLocale.toLowerCase() : 'us'
|
||||
|
||||
const userDataRequest = {
|
||||
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 axios(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 axios(claimRequest)
|
||||
const newBalance = (await claimResponse.data).response.balance
|
||||
|
||||
if (newBalance == userBalance) {
|
||||
this.bot.log('READ-TO-EARN', 'Read all available articles')
|
||||
break
|
||||
} else {
|
||||
this.bot.log('READ-TO-EARN', `Read article ${i + 1} of ${articleCount} max | Gained ${newBalance - userBalance} Points`)
|
||||
userBalance = newBalance
|
||||
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
|
||||
}
|
||||
}
|
||||
|
||||
this.bot.log('READ-TO-EARN', 'Completed Read to Earn')
|
||||
} catch (error) {
|
||||
this.bot.log('READ-TO-EARN', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ import { GoogleSearch } from '../../interface/Search'
|
||||
|
||||
|
||||
export class Search extends Workers {
|
||||
|
||||
private searchPageURL = 'https://bing.com'
|
||||
private bingHome = 'https://bing.com'
|
||||
private searchPageURL = ''
|
||||
|
||||
public async doSearch(page: Page, data: DashboardData) {
|
||||
this.bot.log('SEARCH-BING', 'Starting bing searches')
|
||||
this.bot.log('SEARCH-BING', 'Starting Bing searches')
|
||||
|
||||
page = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
@@ -34,7 +34,7 @@ export class Search extends Workers {
|
||||
googleSearchQueries = [...new Set(googleSearchQueries)]
|
||||
|
||||
// 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
|
||||
|
||||
@@ -42,6 +42,8 @@ export class Search extends Workers {
|
||||
// Mobile search doesn't seem to like related queries?
|
||||
googleSearchQueries.forEach(x => { this.bot.isMobile ? queries.push(x.topic) : queries.push(x.topic, ...x.related) })
|
||||
|
||||
await this.bot.browser.utils.tryDismissBingCookieBanner(page)
|
||||
|
||||
// Loop over Google search queries
|
||||
for (let i = 0; i < queries.length; i++) {
|
||||
const query = queries[i] as string
|
||||
@@ -98,7 +100,7 @@ export class Search extends Workers {
|
||||
for (const term of relatedTerms.slice(1, 3)) {
|
||||
this.bot.log('SEARCH-BING-EXTRA', `${missingPoints} Points Remaining | Query: ${term} | Mobile: ${this.bot.isMobile}`)
|
||||
|
||||
searchCounters = await this.bingSearch(page, query.topic)
|
||||
searchCounters = await this.bingSearch(page, term)
|
||||
const newMissingPoints = this.calculatePoints(searchCounters)
|
||||
|
||||
// If the new point amount is the same as before
|
||||
@@ -134,12 +136,16 @@ export class Search extends Workers {
|
||||
// Try a max of 5 times
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
// This page had already been set to the Bing.com page or the previous search listing, we just need to select it
|
||||
searchPage = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||
|
||||
// Go to top of the page
|
||||
await searchPage.evaluate(() => {
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
|
||||
await this.bot.utils.wait(500)
|
||||
|
||||
const searchBar = '#sb_form_q'
|
||||
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
|
||||
await searchPage.click(searchBar) // Focus on the textarea
|
||||
@@ -151,14 +157,20 @@ export class Search extends Workers {
|
||||
await searchPage.keyboard.type(query)
|
||||
await searchPage.keyboard.press('Enter')
|
||||
|
||||
await this.bot.utils.wait(1000)
|
||||
|
||||
// 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
|
||||
|
||||
if (this.bot.config.searchSettings.scrollRandomResults) {
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.randomScroll(searchPage)
|
||||
await this.randomScroll(resultPage)
|
||||
}
|
||||
|
||||
if (this.bot.config.searchSettings.clickRandomResults) {
|
||||
await this.bot.utils.wait(2000)
|
||||
await this.clickRandomLink(searchPage)
|
||||
await this.clickRandomLink(resultPage)
|
||||
}
|
||||
|
||||
// Delay between searches
|
||||
@@ -172,12 +184,13 @@ export class Search extends Workers {
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
this.bot.log('SEARCH-BING', 'Search failed, An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-BING', `Retrying search, attempt ${i}/5`, 'warn')
|
||||
|
||||
// Reset the tabs
|
||||
const lastTab = await this.bot.browser.utils.getLatestTab(searchPage)
|
||||
await this.closeTabs(lastTab, this.searchPageURL)
|
||||
await this.closeTabs(lastTab)
|
||||
|
||||
await this.bot.utils.wait(4000)
|
||||
}
|
||||
@@ -243,7 +256,7 @@ export class Search extends Workers {
|
||||
|
||||
return response.data[1] as string[]
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-BING-RELTATED', 'An error occurred:' + error, 'error')
|
||||
this.bot.log('SEARCH-BING-RELATED', 'An error occurred:' + error, 'error')
|
||||
}
|
||||
return []
|
||||
}
|
||||
@@ -258,11 +271,13 @@ export class Search extends Workers {
|
||||
|
||||
private async randomScroll(page: Page) {
|
||||
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) => {
|
||||
window.scrollBy(0, scrollAmount)
|
||||
}, scrollAmount)
|
||||
await page.evaluate((scrollPos) => {
|
||||
window.scrollTo(0, scrollPos)
|
||||
}, randomScrollPosition)
|
||||
|
||||
} catch (error) {
|
||||
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')
|
||||
@@ -271,23 +286,21 @@ export class Search extends Workers {
|
||||
|
||||
private async clickRandomLink(page: Page) {
|
||||
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
|
||||
// Stay for 10 seconds for page to load and "visit"
|
||||
await this.bot.utils.wait(10_000)
|
||||
|
||||
// Will get current tab if no new one is created
|
||||
// Will get current tab if no new one is created, this will always be the visited site or the result page if it failed to click
|
||||
let lastTab = await this.bot.browser.utils.getLatestTab(page)
|
||||
|
||||
// Let website load, if it doesn't load within 5 sec. exit regardless
|
||||
await this.bot.utils.wait(5000)
|
||||
|
||||
let lastTabURL = new URL(lastTab.url()) // Get new tab info
|
||||
let lastTabURL = new URL(lastTab.url()) // Get new tab info, this is the website we're visiting
|
||||
|
||||
// Check if the URL is different from the original one, don't loop more than 5 times.
|
||||
let i = 0
|
||||
while (lastTabURL.href !== searchListingURL.href && i < 5) {
|
||||
while (lastTabURL.href !== this.searchPageURL && i < 5) {
|
||||
|
||||
await this.closeTabs(lastTab, searchListingURL.href)
|
||||
await this.closeTabs(lastTab)
|
||||
|
||||
// End of loop, refresh lastPage
|
||||
lastTab = await this.bot.browser.utils.getLatestTab(page) // Finally update the lastTab var again
|
||||
@@ -300,26 +313,29 @@ export class Search extends Workers {
|
||||
}
|
||||
}
|
||||
|
||||
private async closeTabs(lastTab: Page, url: string) {
|
||||
private async closeTabs(lastTab: Page) {
|
||||
const browser = lastTab.context()
|
||||
const tabs = browser.pages()
|
||||
|
||||
// If more than 3 tabs are open, close the last tab
|
||||
// If more than 2 tabs are open, close the last tab
|
||||
if (tabs.length > 2) {
|
||||
await lastTab.close()
|
||||
|
||||
// If only 1 tab is open, open a new one to search in
|
||||
} else if (tabs.length === 1) {
|
||||
const newPage = await browser.newPage()
|
||||
await newPage.goto(url)
|
||||
await this.bot.utils.wait(1000)
|
||||
await newPage.goto(this.bingHome)
|
||||
await this.bot.utils.wait(3000)
|
||||
this.searchPageURL = newPage.url()
|
||||
|
||||
// Else go back one page
|
||||
// Else reset the last tab back to the search listing or Bing.com
|
||||
} else {
|
||||
await lastTab.goBack()
|
||||
lastTab = await this.bot.browser.utils.getLatestTab(lastTab)
|
||||
await lastTab.goto(this.searchPageURL ? this.searchPageURL : this.bingHome)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private calculatePoints(counters: Counters) {
|
||||
const mobileData = counters.mobileSearch?.[0] // Mobile searches
|
||||
const genericData = counters.pcSearch?.[0] // Normal searches
|
||||
@@ -332,4 +348,4 @@ export class Search extends Workers {
|
||||
|
||||
return missingPoints
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
77
src/index.ts
77
src/index.ts
@@ -34,6 +34,7 @@ export class MicrosoftRewardsBot {
|
||||
private accounts: Account[]
|
||||
private workers: Workers
|
||||
private login = new Login(this)
|
||||
private accessToken: string = ''
|
||||
|
||||
constructor() {
|
||||
this.log = log
|
||||
@@ -134,14 +135,19 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
// Login into MS Rewards, then go to rewards homepage
|
||||
await this.login.login(this.homePage, account.email, account.password)
|
||||
this.accessToken = await this.login.getMobileAccessToken(this.homePage, account.email)
|
||||
|
||||
await this.browser.func.goHome(this.homePage)
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
log('MAIN-POINTS', `Current point count: ${data.userStatus.availablePoints}`)
|
||||
|
||||
const earnablePoints = await this.browser.func.getEarnablePoints()
|
||||
const browserEnarablePoints = await this.browser.func.getBrowserEarnablePoints()
|
||||
const appEarnablePoints = await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
const earnablePoints = browserEnarablePoints + appEarnablePoints
|
||||
this.collectedPoints = earnablePoints
|
||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today`)
|
||||
log('MAIN-POINTS', `You can earn ${earnablePoints} points today (Browser: ${browserEnarablePoints} points, App: ${appEarnablePoints} points)`)
|
||||
|
||||
// If runOnZeroPoints is false and 0 points to earn, don't continue
|
||||
if (!this.config.runOnZeroPoints && this.collectedPoints === 0) {
|
||||
@@ -178,11 +184,11 @@ export class MicrosoftRewardsBot {
|
||||
}
|
||||
|
||||
// Save cookies
|
||||
const cookies = await browser.cookies()
|
||||
await saveSessionData(this.config.sessionPath, account.email, this.isMobile, cookies)
|
||||
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||
|
||||
// Close desktop browser
|
||||
return await this.closeBrowser(browser, account.email)
|
||||
await this.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
// Mobile
|
||||
@@ -200,55 +206,62 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
const data = await this.browser.func.getDashboardData()
|
||||
|
||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
||||
if (!data.userStatus.counters.mobileSearch) {
|
||||
log('MAIN', 'No mobile searches found, stopping!')
|
||||
|
||||
// Close mobile browser
|
||||
return await this.closeBrowser(browser, account.email)
|
||||
// Do daily check in
|
||||
if (this.config.workers.doDailyCheckIn) {
|
||||
await this.activities.doDailyCheckIn(this.accessToken, data)
|
||||
}
|
||||
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
// Do read to earn
|
||||
if (this.config.workers.doReadToEarn) {
|
||||
await this.activities.doReadToEarn(this.accessToken, data)
|
||||
}
|
||||
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
// If no mobile searches data found, stop (Does not exist on new accounts)
|
||||
if (data.userStatus.counters.mobileSearch) {
|
||||
// Open a new tab to where the tasks are going to be completed
|
||||
const workerPage = await browser.newPage()
|
||||
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
// Go to homepage on worker page
|
||||
await this.browser.func.goHome(workerPage)
|
||||
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
// Do mobile searches
|
||||
if (this.config.workers.doMobileSearch) {
|
||||
await this.activities.doSearch(workerPage, data)
|
||||
|
||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
||||
// Retry until all points are gathered when (retryMobileSearch is enabled)
|
||||
if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
|
||||
log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...')
|
||||
// Fetch current search points
|
||||
const mobileSearchPoints = (await this.browser.func.getSearchPoints()).mobileSearch?.[0]
|
||||
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
// If the remaining mobile points does not equal 0, restart and assume the generated UA is invalid
|
||||
// Retry until all points are gathered when (retryMobileSearch is enabled)
|
||||
if (this.config.searchSettings.retryMobileSearch && mobileSearchPoints && ((mobileSearchPoints.pointProgressMax - mobileSearchPoints.pointProgress) > 0)) {
|
||||
log('MAIN', 'Unable to complete mobile searches, bad User-Agent? Retrying...')
|
||||
|
||||
// Retry
|
||||
await this.Mobile(account)
|
||||
// Close mobile browser
|
||||
await this.closeBrowser(browser, account.email)
|
||||
|
||||
// Retry
|
||||
await this.Mobile(account)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log('MAIN', 'No mobile searches found!')
|
||||
}
|
||||
|
||||
// Fetch new points
|
||||
const earnablePoints = await this.browser.func.getEarnablePoints()
|
||||
const earnablePoints = await this.browser.func.getBrowserEarnablePoints() + await this.browser.func.getAppEarnablePoints(this.accessToken)
|
||||
|
||||
// If the new earnable is 0, means we got all the points, else retract
|
||||
this.collectedPoints = earnablePoints === 0 ? this.collectedPoints : (this.collectedPoints - earnablePoints)
|
||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
||||
|
||||
// Close mobile browser
|
||||
return await this.closeBrowser(browser, account.email)
|
||||
await this.closeBrowser(browser, account.email)
|
||||
return
|
||||
}
|
||||
|
||||
private async closeBrowser(browser: BrowserContext, email: string) {
|
||||
// Save cookies
|
||||
const cookies = await browser.cookies()
|
||||
await saveSessionData(this.config.sessionPath, email, this.isMobile, cookies)
|
||||
await saveSessionData(this.config.sessionPath, browser, email, this.isMobile)
|
||||
|
||||
// Close browser
|
||||
await browser.close()
|
||||
|
||||
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'
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export interface Config {
|
||||
runOnZeroPoints: boolean;
|
||||
clusters: number;
|
||||
workers: Workers;
|
||||
globalTimeout: number;
|
||||
searchSettings: SearchSettings;
|
||||
webhook: Webhook;
|
||||
saveFingerprint: boolean;
|
||||
@@ -34,4 +35,6 @@ export interface Workers {
|
||||
doPunchCards: boolean;
|
||||
doDesktopSearch: boolean;
|
||||
doMobileSearch: boolean;
|
||||
doDailyCheckIn: boolean;
|
||||
doReadToEarn: boolean;
|
||||
}
|
||||
|
||||
@@ -351,6 +351,8 @@ export interface MorePromotion {
|
||||
legalText: string;
|
||||
legalLinkText: string;
|
||||
deviceType: string;
|
||||
exclusiveLockedFeatureType: string;
|
||||
exclusiveLockedFeatureStatus: string;
|
||||
}
|
||||
|
||||
export interface PunchCard {
|
||||
|
||||
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;
|
||||
}
|
||||
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
|
||||
43
src/updateConfig.js
Executable file
43
src/updateConfig.js
Executable file
@@ -0,0 +1,43 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const configPath = path.join(__dirname, '../dist/config.json')
|
||||
|
||||
// Read the existing config file
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
|
||||
|
||||
// Update the config with environment variables if they are set
|
||||
config.baseURL = process.env.BASE_URL || config.baseURL
|
||||
config.sessionPath = process.env.SESSION_PATH || config.sessionPath
|
||||
config.headless = process.env.HEADLESS ? process.env.HEADLESS === 'true' : config.headless
|
||||
config.runOnZeroPoints = process.env.RUN_ON_ZERO_POINTS ? process.env.RUN_ON_ZERO_POINTS === 'true' : config.runOnZeroPoints
|
||||
config.clusters = process.env.CLUSTERS ? parseInt(process.env.CLUSTERS, 10) : config.clusters
|
||||
config.saveFingerprint = process.env.SAVE_FINGERPRINT ? process.env.SAVE_FINGERPRINT === 'true' : config.saveFingerprint
|
||||
config.globalTimeout = process.env.GLOBAL_TIMEOUT ? parseInt(process.env.GLOBAL_TIMEOUT, 10) : config.globalTimeout
|
||||
|
||||
config.workers.doDailySet = process.env.DO_DAILY_SET ? process.env.DO_DAILY_SET === 'true' : config.workers.doDailySet
|
||||
config.workers.doMorePromotions = process.env.DO_MORE_PROMOTIONS ? process.env.DO_MORE_PROMOTIONS === 'true' : config.workers.doMorePromotions
|
||||
config.workers.doPunchCards = process.env.DO_PUNCH_CARDS ? process.env.DO_PUNCH_CARDS === 'true' : config.workers.doPunchCards
|
||||
config.workers.doDesktopSearch = process.env.DO_DESKTOP_SEARCH ? process.env.DO_DESKTOP_SEARCH === 'true' : config.workers.doDesktopSearch
|
||||
config.workers.doMobileSearch = process.env.DO_MOBILE_SEARCH ? process.env.DO_MOBILE_SEARCH === 'true' : config.workers.doMobileSearch
|
||||
config.workers.doDailyCheckIn = process.env.DO_DAILY_CHECK_IN ? process.env.DO_DAILY_CHECK_IN === 'true' : config.workers.doDailyCheckIn
|
||||
config.workers.doReadToEarn = process.env.DO_READ_TO_EARN ? process.env.DO_READ_TO_EARN === 'true' : config.workers.doReadToEarn
|
||||
|
||||
config.searchSettings.useGeoLocaleQueries = process.env.USE_GEO_LOCALE_QUERIES ? process.env.USE_GEO_LOCALE_QUERIES === 'true' : config.searchSettings.useGeoLocaleQueries
|
||||
config.searchSettings.scrollRandomResults = process.env.SCROLL_RANDOM_RESULTS ? process.env.SCROLL_RANDOM_RESULTS === 'true' : config.searchSettings.scrollRandomResults
|
||||
config.searchSettings.clickRandomResults = process.env.CLICK_RANDOM_RESULTS ? process.env.CLICK_RANDOM_RESULTS === 'true' : config.searchSettings.clickRandomResults
|
||||
config.searchSettings.searchDelay.min = process.env.SEARCH_DELAY_MIN ? parseInt(process.env.SEARCH_DELAY_MIN, 10) : config.searchSettings.searchDelay.min
|
||||
config.searchSettings.searchDelay.max = process.env.SEARCH_DELAY_MAX ? parseInt(process.env.SEARCH_DELAY_MAX, 10) : config.searchSettings.searchDelay.max
|
||||
config.searchSettings.retryMobileSearch = process.env.RETRY_MOBILE_SEARCH ? process.env.RETRY_MOBILE_SEARCH === 'true' : config.searchSettings.retryMobileSearch
|
||||
|
||||
config.webhook.enabled = process.env.WEBHOOK_ENABLED ? process.env.WEBHOOK_ENABLED === 'true' : config.webhook.enabled
|
||||
config.webhook.url = process.env.WEBHOOK_URL || config.webhook.url
|
||||
|
||||
// Write the updated config back to the file
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
|
||||
console.log('Config file updated with environment variables')
|
||||
} catch (error) {
|
||||
console.error(`Failed to write updated config file to ${configPath}:`, error)
|
||||
process.exit(1)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Cookie } from 'playwright'
|
||||
import { BrowserContext, Cookie } from 'playwright'
|
||||
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
@@ -67,8 +67,10 @@ export async function loadSessionData(sessionPath: string, email: string, isMobi
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveSessionData(sessionPath: string, email: string, isMobile: boolean, cookies: Cookie[]): Promise<string> {
|
||||
export async function saveSessionData(sessionPath: string, browser: BrowserContext, email: string, isMobile: boolean): Promise<string> {
|
||||
try {
|
||||
const cookies = await browser.cookies()
|
||||
|
||||
// Fetch path
|
||||
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user