mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 14:03:58 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ce2a72ee36 | ||
|
|
755237caa1 | ||
|
|
2b4cd505c0 | ||
|
|
a39a861dab | ||
|
|
8d19129906 |
@@ -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'
|
||||||
|
|||||||
@@ -37,4 +37,4 @@ COPY src/crontab.template /etc/cron.d/microsoft-rewards-cron.template
|
|||||||
RUN touch /var/log/cron.log
|
RUN touch /var/log/cron.log
|
||||||
|
|
||||||
# Define the command to run your application with cron optionally
|
# Define the command to run your application with cron optionally
|
||||||
CMD sh -c 'echo "$TZ" > /etc/timezone && dpkg-reconfigure -f noninteractive tzdata && if [ "$RUN_ON_START" = "true" ]; then npm start; fi && envsubst < /etc/cron.d/microsoft-rewards-cron.template > /etc/cron.d/microsoft-rewards-cron && crontab /etc/cron.d/microsoft-rewards-cron && cron && tail -f /var/log/cron.log'
|
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'
|
||||||
|
|||||||
38
README.md
38
README.md
@@ -16,11 +16,14 @@ 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) ##
|
||||||
|
**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
|
1. Download the source code
|
||||||
2. Make changes to your `accounts.json`
|
2. Make changes to your `accounts.json` and `config.json`
|
||||||
3. **Headless mode must be enabled when using Docker.** You can do this using the `HEADLESS=true` environmental variable in docker run or docker compose.yaml (see below). Environmental variables are always prioritized over the values in config.json.
|
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 will run scheduled. Customize your schedule using the `CRON_START_TIME` environmental variable. Use [crontab.guru](crontab.guru) if you're unsure how to create a cron schedule.
|
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.
|
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
|
### Option 1: build and run with docker run
|
||||||
|
|
||||||
1. Build or re-build the container image with: `docker build -t microsoft-rewards-script-docker .`
|
1. Build or re-build the container image with: `docker build -t microsoft-rewards-script-docker .`
|
||||||
@@ -31,14 +34,12 @@ Under development, however mainly for personal use!
|
|||||||
docker run --name netsky -d \
|
docker run --name netsky -d \
|
||||||
-e TZ=America/New_York \
|
-e TZ=America/New_York \
|
||||||
-e HEADLESS=true \
|
-e HEADLESS=true \
|
||||||
-e SEARCH_DELAY_MIN=10000 \
|
-e RUN_ON_START=true \
|
||||||
-e SEARCH_DELAY_MAX=20000 \
|
|
||||||
-e CLUSTERS=1 \
|
|
||||||
-e CRON_START_TIME="0 5,11 * * *" \
|
-e CRON_START_TIME="0 5,11 * * *" \
|
||||||
microsoft-rewards-script-docker
|
microsoft-rewards-script-docker
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Optionally, change any environmental variables other than `HEADLESS`, which must stay `=true`
|
3. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||||
|
|
||||||
4. You can view logs with `docker logs netsky`.
|
4. You can view logs with `docker logs netsky`.
|
||||||
|
|
||||||
@@ -46,9 +47,9 @@ Under development, however mainly for personal use!
|
|||||||
|
|
||||||
1. A basic docker compose.yaml has been provided.
|
1. A basic docker compose.yaml has been provided.
|
||||||
|
|
||||||
2. Optionally, change any environmental variables other than `HEADLESS`, which must stay `=true`
|
2. Optionally, customize your config by adding any other environmental variables from the table below.
|
||||||
|
|
||||||
3. Build or rebuild and start the container using `docker compose up -d --build`
|
3. Build and start the container using `docker compose up -d`.
|
||||||
|
|
||||||
4. You can view logs with `docker logs netsky`
|
4. You can view logs with `docker logs netsky`
|
||||||
|
|
||||||
@@ -62,17 +63,17 @@ Under development, however mainly for personal use!
|
|||||||
| runOnZeroPoints | Run the rest of the script if 0 points can be earned | `false` (Will not run on 0 points) | RUN_ON_ZERO_POINTS |
|
| 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 |
|
| 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 |
|
| saveFingerprint | Re-use the same fingerprint each time | `false` (Will generate a new fingerprint each time) | SAVE_FINGERPRINT |
|
||||||
| workers.doDailySet | Complete daily set items | `true` | WORKERS_DO_DAILY_SET |
|
| workers.doDailySet | Complete daily set items | `true` | DO_DAILY_SET |
|
||||||
| workers.doMorePromotions | Complete promotional items | `true` | WORKERS_DO_MORE_PROMOTIONS |
|
| workers.doMorePromotions | Complete promotional items | `true` | DO_MORE_PROMOTIONS |
|
||||||
| workers.doPunchCards | Complete punchcards | `true` | WORKERS_DO_PUNCH_CARDS |
|
| workers.doPunchCards | Complete punchcards | `true` | DO_PUNCH_CARDS |
|
||||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` | WORKERS_DO_DESKTOP_SEARCH |
|
| workers.doDesktopSearch | Complete daily desktop searches | `true` | DO_DESKTOP_SEARCH |
|
||||||
| workers.doMobileSearch | Complete daily mobile searches | `true` | WORKERS_DO_MOBILE_SEARCH |
|
| workers.doMobileSearch | Complete daily mobile searches | `true` | DO_MOBILE_SEARCH |
|
||||||
| globalTimeout | The length before the action gets timeout | `30000` (30 seconds) | GLOBAL_TIMEOUT |
|
| globalTimeout | The length before the action gets timeout | `30000` (30 seconds) | GLOBAL_TIMEOUT |
|
||||||
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) | SEARCH_SETTINGS_USE_GEO_LOCALE_QUERIES |
|
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) | USE_GEO_LOCALE_QUERIES |
|
||||||
| scrollRandomResults | Scroll randomly in search results | `true` | SEARCH_SETTINGS_SCROLL_RANDOM_RESULTS |
|
| scrollRandomResults | Scroll randomly in search results | `true` | SCROLL_RANDOM_RESULTS |
|
||||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` | SEARCH_SETTINGS_CLICK_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.searchDelay | Minimum and maximum time in miliseconds between search queries | `min: 10000` (10 seconds) `max: 20000` (20 seconds) | SEARCH_DELAY_MIN SEARCH_DELAY_MAX |
|
||||||
| searchSettings.retryMobileSearch | Keep retrying mobile searches until completed (indefinite)| `false` | SEARCH_SETTINGS_RETRY_MOBILE_SEARCH |
|
| 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.enabled | Enable or disable your set webhook | `false` | WEBHOOK_ENABLED |
|
||||||
| webhook.url | Your Discord webhook URL | `null` | WEBHOOK_URL="" |
|
| 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="" |
|
| cronStartTime | Scheduled script run-time, *only available for docker implementation* | `0 5,11 * * *` (5:00 am, 11:00 am daily) | CRON_START_TIME="" |
|
||||||
@@ -82,6 +83,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
|
||||||
|
|||||||
25
compose.yaml
25
compose.yaml
@@ -6,29 +6,8 @@ services:
|
|||||||
- TZ=America/Toronto #change to your local timezone
|
- TZ=America/Toronto #change to your local timezone
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- HEADLESS=true #do not change
|
- HEADLESS=true #do not change
|
||||||
### the following are optional, you only need to include them if you want to enter a custom value, removing them will use the default values
|
### Customize your run schedule, default 5:00 am and 11:00 am, use crontab.guru for formatting
|
||||||
- BASE_URL=https://rewards.bing.com
|
|
||||||
- SESSION_PATH=sessions
|
|
||||||
- RUN_ON_ZERO_POINTS=false
|
|
||||||
- CLUSTERS=1
|
|
||||||
- SAVE_FINGERPRINT=false
|
|
||||||
- WORKERS_DO_DAILY_SET=true
|
|
||||||
- WORKERS_DO_MORE_PROMOTIONS=true
|
|
||||||
- WORKERS_DO_PUNCH_CARDS=true
|
|
||||||
- WORKERS_DO_DESKTOP_SEARCH=true
|
|
||||||
- WORKERS_DO_MOBILE_SEARCH=true
|
|
||||||
- SEARCH_SETTINGS_USE_GEO_LOCALE_QUERIES=false
|
|
||||||
- SEARCH_SETTINGS_SCROLL_RANDOM_RESULTS=true
|
|
||||||
- SEARCH_SETTINGS_CLICK_RANDOM_RESULTS=true
|
|
||||||
- SEARCH_SETTINGS_SEARCH_DELAY_MIN=10000 # Set the search delay longer, e.g. MIN=180000 and MAX=270000 if you live in a region where MS enforces a search cooldown
|
|
||||||
- SEARCH_SETTINGS_SEARCH_DELAY_MAX=20000
|
|
||||||
- SEARCH_SETTINGS_RETRY_MOBILE_SEARCH=true
|
|
||||||
- WEBHOOK_ENABLED=false
|
|
||||||
- WEBHOOK_URL=
|
|
||||||
### Customize your run schedule, default 5:00 am and 11:00 am, use crontab.guru if you're not sure
|
|
||||||
- CRON_START_TIME=0 5,11 * * *
|
- CRON_START_TIME=0 5,11 * * *
|
||||||
### Run on start, set as false to only run the script per the cron schedule
|
### Run on start, set to false to only run the script per the cron schedule
|
||||||
- RUN_ON_START=true
|
- RUN_ON_START=true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
|
||||||
- .:/usr/src/microsoft-rewards-script
|
|
||||||
|
|||||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "microsoft-rewards-script",
|
"name": "microsoft-rewards-script",
|
||||||
"version": "1.4.7",
|
"version": "1.4.8",
|
||||||
"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": {
|
||||||
@@ -26,17 +26,17 @@
|
|||||||
"author": "Netsky",
|
"author": "Netsky",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^7.11.0",
|
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-plugin-modules-newline": "^0.0.6",
|
"eslint-plugin-modules-newline": "^0.0.6",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.5.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.4",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0",
|
||||||
"fingerprint-generator": "^2.1.51",
|
"fingerprint-generator": "^2.1.54",
|
||||||
"fingerprint-injector": "^2.1.51",
|
"fingerprint-injector": "^2.1.54",
|
||||||
"playwright": "^1.44.1",
|
"playwright": "^1.46.1",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export default class BrowserFunc {
|
|||||||
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.promotionType)) {
|
if (['quiz', 'urlreward'].includes(x.promotionType) && !x.attributes.is_unlocked) {
|
||||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
${CRON_START_TIME} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /var/log/cron.log 2>&1
|
${CRON_START_TIME} TZ=${TZ} /bin/bash /usr/src/microsoft-rewards-script/src/run_daily.sh >> /var/log/cron.log 2>&1
|
||||||
|
|||||||
@@ -56,51 +56,110 @@ export class Login {
|
|||||||
|
|
||||||
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.enterPassword(page, password)
|
||||||
await page.click('#idSIButton9')
|
await this.checkLoggedIn(page)
|
||||||
|
} catch (error: any) {
|
||||||
this.bot.log('LOGIN', 'Email entered successfully')
|
this.bot.log('LOGIN', 'An error occurred: ' + error.message, 'error')
|
||||||
|
|
||||||
try {
|
|
||||||
// Enter password
|
|
||||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
|
||||||
await this.bot.utils.wait(2000)
|
|
||||||
|
|
||||||
await page.fill('#i0118', password)
|
|
||||||
await page.click('#idSIButton9')
|
|
||||||
|
|
||||||
// When erroring at this stage it means a 2FA code is required
|
|
||||||
} catch (error) {
|
|
||||||
this.bot.log('LOGIN', '2FA code required')
|
|
||||||
|
|
||||||
// Wait for user input
|
|
||||||
const code = await new Promise<string>((resolve) => {
|
|
||||||
rl.question('Enter 2FA code:\n', (input) => {
|
|
||||||
rl.close()
|
|
||||||
resolve(input)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.fill('input[name="otc"]', code)
|
|
||||||
await page.keyboard.press('Enter')
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bot.log('LOGIN', 'Password entered successfully')
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentURL = new URL(page.url())
|
private async enterEmail(page: Page, email: string) {
|
||||||
|
await page.fill('#i0116', email)
|
||||||
|
await page.click('#idSIButton9')
|
||||||
|
this.bot.log('LOGIN', 'Email entered successfully')
|
||||||
|
}
|
||||||
|
|
||||||
while (currentURL.pathname !== '/' || currentURL.hostname !== 'rewards.bing.com') {
|
private async enterPassword(page: Page, password: string) {
|
||||||
|
try {
|
||||||
|
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||||
|
await this.bot.utils.wait(2000)
|
||||||
|
await page.fill('#i0118', password)
|
||||||
|
await page.click('#idSIButton9')
|
||||||
|
this.bot.log('LOGIN', 'Password entered successfully')
|
||||||
|
} catch {
|
||||||
|
this.bot.log('LOGIN', 'Password entry failed or 2FA required')
|
||||||
|
await this.handle2FA(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handle2FA(page: Page) {
|
||||||
|
try {
|
||||||
|
const numberToPress = await this.get2FACode(page)
|
||||||
|
if (numberToPress) {
|
||||||
|
// Authentictor App verification
|
||||||
|
await this.authAppVerification(page, numberToPress)
|
||||||
|
} else {
|
||||||
|
// SMS verification
|
||||||
|
await this.authSMSVerification(page)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
this.bot.log('LOGIN', `2FA handling failed: ${error.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
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: 10_000 })
|
||||||
|
this.bot.log('LOGIN', 'Successfully logged into the rewards portal')
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkBingLogin(page: Page): Promise<void> {
|
private async checkBingLogin(page: Page): Promise<void> {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ 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.attributes.is_unlocked) ?? []
|
||||||
|
|
||||||
if (!activitiesUncompleted.length) {
|
if (!activitiesUncompleted.length) {
|
||||||
this.bot.log('MORE-PROMOTIONS', 'All "More Promotion" items have already been completed')
|
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) {
|
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')) {
|
||||||
selector = `[data-bi-id="${activity.name}"]`
|
selector = `[data-bi-id^="${activity.name}"]`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click element, it will be opened in a new tab
|
// Click element, it will be opened in a new tab
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ export class Search extends Workers {
|
|||||||
// Mobile search doesn't seem to like related queries?
|
// 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) })
|
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
|
// Loop over Google search queries
|
||||||
for (let i = 0; i < queries.length; i++) {
|
for (let i = 0; i < queries.length; i++) {
|
||||||
const query = queries[i] as string
|
const query = queries[i] as string
|
||||||
@@ -98,7 +100,7 @@ export class Search extends Workers {
|
|||||||
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('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)
|
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
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
# Set up environment variables
|
# Set up environment variables
|
||||||
export PATH=$PATH:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin
|
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
|
# Change directory to the application directory
|
||||||
cd /usr/src/microsoft-rewards-script
|
cd /usr/src/microsoft-rewards-script
|
||||||
|
|
||||||
|
|||||||
41
src/updateConfig.js
Executable file
41
src/updateConfig.js
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
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.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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user