11 Commits

Author SHA1 Message Date
TheNetsky
cf7f7ac790 Merge branch 'main' of https://github.com/TheNetsky/Microsoft-Rewards-Script 2024-02-05 11:47:50 +01:00
TheNetsky
f7aa5039f9 1.4.4
- Fixed login getting stuck
- Updated packages
- Fixed some errors not throwing
2024-02-05 11:47:48 +01:00
Netsky
e082fb03f0 Merge pull request #56 from Omniphantasm/patch-1
Update README.md
2024-01-26 18:55:10 +01:00
Omniphantasm
0303b8c605 Update README.md
Script is kill-chrome-win, changed readme to reflect that.
2024-01-26 12:35:30 -05:00
TheNetsky
2fea17c415 1.4.3
- Fixed browser generating mobile browser after the first account
- Hopefully fixed script erroring after not finding parent promotions
- Added support for MacOS (Thanks @alecm858)
- Made some changes to search result scrolling
2024-01-20 12:11:02 +01:00
Netsky
c5beccb54b Update README.md 2024-01-17 22:23:55 +01:00
TheNetsky
b566ccaece 1.4.2
- Scroll back to top before searching
2024-01-17 18:50:43 +01:00
TheNetsky
15b2b827eb 1.4.1
- Fixed typos
- Added Dockerfile (experimental)
- Increased scroll amount
2024-01-14 14:09:57 +01:00
Netsky
02518ee4ba Update README.md 2024-01-07 17:00:23 +01:00
Netsky
69819b5631 Add config explaination 2024-01-07 16:59:15 +01:00
Netsky
b389b87792 Set runOnZeroPoints to false by default 2024-01-07 16:38:47 +01:00
10 changed files with 140 additions and 47 deletions

43
Dockerfile Normal file
View File

@@ -0,0 +1,43 @@
# Use an official Node.js runtime as a base image
FROM node:18
# Set the working directory in the container
WORKDIR /usr/src/microsoft-rewards-script
# Install jq
RUN apt-get update && apt-get install -y jq
# Copy all files to the working directory
COPY . .
# Check if "headless" is set to "true" in the config.json file
# DELETE BELOW IF YOU WANT TO RUN THE DOCKER SCRIPT HEADFULL!
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 \
libgbm-dev \
libnss3 \
libasound2 \
libxss1 \
libatk-bridge2.0-0 \
libgtk-3-0 \
&& rm -rf /var/lib/apt/lists/*
# Install application dependencies
RUN npm install
# Build the script
RUN npm run build
# Install playwright chromium
RUN npx playwright install chromium
# Define the command to run your application
CMD ["npm", "start"]

View File

@@ -12,9 +12,36 @@ 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) ##
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`!
## 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` |
## Features ##
- [x] Multi-Account Support

View File

@@ -1,6 +1,6 @@
{
"name": "microsoft-rewards-script",
"version": "1.4.0",
"version": "1.4.4",
"description": "Automatically do tasks for Microsoft Rewards but in TS!",
"main": "index.js",
"engines": {
@@ -11,7 +11,8 @@
"start": "node ./dist/index.js",
"ts-start": "ts-node ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev",
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\""
"kill-chrome-win": "powershell -Command \"Get-Process | Where-Object { $_.MainModule.FileVersionInfo.FileDescription -eq 'Google Chrome for Testing' } | ForEach-Object { Stop-Process -Id $_.Id -Force }\"",
"create-docker": "docker build -t microsoft-rewards-script-docker ."
},
"keywords": [
"Bing Rewards",
@@ -25,17 +26,17 @@
"author": "Netsky",
"license": "ISC",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.7.0",
"eslint": "^8.54.0",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"eslint": "^8.56.0",
"eslint-plugin-modules-newline": "^0.0.6",
"typescript": "^5.3.3"
},
"dependencies": {
"axios": "^1.6.2",
"axios": "^1.6.7",
"cheerio": "^1.0.0-rc.12",
"fingerprint-generator": "^2.1.46",
"fingerprint-injector": "^2.1.46",
"playwright": "^1.40.1",
"fingerprint-generator": "^2.1.48",
"fingerprint-injector": "^2.1.48",
"playwright": "^1.41.2",
"ts-node": "^10.9.2"
}
}

View File

@@ -95,13 +95,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 +110,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
}

View File

@@ -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' },

View File

@@ -2,7 +2,7 @@
"baseURL": "https://rewards.bing.com",
"sessionPath": "sessions",
"headless": false,
"runOnZeroPoints": true,
"runOnZeroPoints": false,
"clusters": 1,
"saveFingerprint": false,
"workers": {
@@ -26,4 +26,4 @@
"enabled": false,
"url": ""
}
}
}

View File

@@ -53,35 +53,41 @@ export class Login {
}
private async execLogin(page: Page, email: string, password: string) {
await page.type('#i0116', email)
await page.click('#idSIButton9')
this.bot.log('LOGIN', 'Email entered successfully')
try {
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
await this.bot.utils.wait(2000)
await page.type('#i0118', password)
// Enter email
await page.fill('#i0116', email)
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')
this.bot.log('LOGIN', 'Email entered successfully')
// Wait for user input
const code = await new Promise<string>((resolve) => {
rl.question('Enter 2FA code:\n', (input) => {
rl.close()
resolve(input)
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.type('input[name="otc"]', code)
await page.keyboard.press('Enter')
await page.fill('input[name="otc"]', code)
await page.keyboard.press('Enter')
}
} finally {
this.bot.log('LOGIN', 'Password entered successfully')
} catch (error) {
this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
}
const currentURL = new URL(page.url())

View File

@@ -38,7 +38,7 @@ export class Workers {
// Punch Card
async doPunchCard(page: Page, data: DashboardData) {
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion.complete) ?? [] // Only return uncompleted punch cards
const punchCardsUncompleted = data.punchCards?.filter(x => !x.parentPromotion?.complete) ?? [] // Only return uncompleted punch cards
if (!punchCardsUncompleted.length) {
this.bot.log('PUNCH-CARD', 'All "Punch Cards" have already been completed')
@@ -95,7 +95,7 @@ export class Workers {
}
// Solve Activities
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" item')
this.bot.log('MORE-PROMOTIONS', 'Started solving "More Promotions" items')
page = await this.bot.browser.utils.getLatestTab(page)

View File

@@ -1,5 +1,6 @@
import { Page } from 'playwright'
import axios from 'axios'
import { platform } from 'os'
import { Workers } from '../Workers'
@@ -128,17 +129,25 @@ export class Search extends Workers {
}
private async bingSearch(searchPage: Page, query: string) {
const platformControlKey = platform() === 'darwin' ? 'Meta' : 'Control'
// Try a max of 5 times
for (let i = 0; i < 5; i++) {
try {
// Go to top of the page
await searchPage.evaluate(() => {
window.scrollTo(0, 0)
})
const searchBar = '#sb_form_q'
await searchPage.waitForSelector(searchBar, { state: 'visible', timeout: 10_000 })
await searchPage.click(searchBar) // Focus on the textarea
await this.bot.utils.wait(500)
await searchPage.keyboard.down('Control')
await searchPage.keyboard.down(platformControlKey)
await searchPage.keyboard.press('A')
await searchPage.keyboard.press('Backspace')
await searchPage.keyboard.up('Control')
await searchPage.keyboard.up(platformControlKey)
await searchPage.keyboard.type(query)
await searchPage.keyboard.press('Enter')
@@ -152,6 +161,7 @@ export class Search extends Workers {
await this.clickRandomLink(searchPage)
}
// Delay between searches
await this.bot.utils.wait(Math.floor(this.bot.utils.randomNumber(this.bot.config.searchSettings.searchDelay.min, this.bot.config.searchSettings.searchDelay.max)))
return await this.bot.browser.func.getSearchPoints()
@@ -248,10 +258,11 @@ export class Search extends Workers {
private async randomScroll(page: Page) {
try {
// Press the arrow down key to scroll
for (let i = 0; i < this.bot.utils.randomNumber(5, 400); i++) {
await page.keyboard.press('ArrowDown')
}
const scrollAmount = this.bot.utils.randomNumber(5, 5000)
await page.evaluate((scrollAmount) => {
window.scrollBy(0, scrollAmount)
}, scrollAmount)
} catch (error) {
this.bot.log('SEARCH-RANDOM-SCROLL', 'An error occurred:' + error, 'error')

View File

@@ -125,6 +125,8 @@ export class MicrosoftRewardsBot {
// Desktop
async Desktop(account: Account) {
this.isMobile = false
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()