10 Commits

Author SHA1 Message Date
TheNetsky
ef6ad569ff 1.4.6 2024-05-09 10:04:11 +02:00
Netsky
da9ba91c5c Merge pull request #98 from jordyamc/main
Bug when detecting earnable points from more promotions
2024-05-07 15:12:10 +02:00
Jordy Mendoza
deb2d58b1b - Fix bug where the script was using x.activityType instead of x.promotionType this was causing that the script wasn't detecting earnable points from more promotions 2024-04-21 23:50:33 -06:00
Netsky
66a82c2584 Merge pull request #89 from mgrimace/docker
Clarify docker instructions, add basic compose.yaml support
2024-04-04 16:43:57 +02:00
mgrimace
8a022d5983 Clarify docker instructions, add basic compose.yaml support
This should help folks get started with docker and hopefully make it easier for testing
2024-04-03 17:59:16 -04:00
TheNetsky
64048e35d7 1.4.5
- Fix login not working
- Sessions are now saved after logging in
- Check if promotions, and searches are available before counting total point amount
2024-03-01 12:19:48 +01:00
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
13 changed files with 109 additions and 59 deletions

3
.gitignore vendored
View File

@@ -5,4 +5,5 @@ package-lock.json
accounts.json
notes
accounts.dev.json
accounts.main.json
accounts.main.json
.DS_Store

View File

@@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y jq
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."; \

View File

@@ -12,14 +12,31 @@ 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`!
2. Make changes to your `accounts.json`
3. Make sure to change `"headless": false` to `"headless": true` in your `config.json`
4. Note, the container has to be recreated for any changes regarding the `config.json` and/or `accounts.json`!
### Option 1: build and run with docker run
1. Run `docker build -t microsoft-rewards-script-docker .` to build the container
2. Run the container with `docker run --name netsky -d microsoft-rewards-script-docker` or, omit the detached flag `-d` to view the script output in your terminal.
3. Optionally, change the name of the container by changing `--name netsky` to your preferred container name
4. The container will exit after completing the script, run it again using `docker start netsky`
5. If you are running the container `-d` detached, you can view logs with `docker logs netsky`
### Option 2: use docker compose
1. A basic docker compose.yaml has been provided, which can be run with `docker compose up -d` or, omit the detached flag `-d` to view the script output in your terminal.
2. The container will exit after completing the script, run it again using `docker start netsky`
3. If you are running the container `-d` detached, you can view logs with `docker logs netsky`
## Config ##
| Setting | Description | Default |
@@ -35,6 +52,7 @@ Under development, however mainly for personal use!
| workers.doPunchCards | Complete punchcards | `true` |
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
| workers.doMobileSearch | Complete daily mobile searches | `true` |
| globalTimeout | The length before the action gets timeout | 30 seconds (30.000 miliseconds) |
| 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` |

8
compose.yaml Normal file
View File

@@ -0,0 +1,8 @@
services:
microsoft-rewards-script:
build: .
container_name: netsky
environment:
- NODE_ENV=production
volumes:
- .:/usr/src/microsoft-rewards-script

View File

@@ -1,6 +1,6 @@
{
"name": "microsoft-rewards-script",
"version": "1.4.3",
"version": "1.4.6",
"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": "^6.20.0",
"eslint": "^8.56.0",
"eslint-plugin-modules-newline": "^0.0.6",
"typescript": "^5.3.3"
},
"dependencies": {
"axios": "^1.6.5",
"axios": "^1.6.7",
"cheerio": "^1.0.0-rc.12",
"fingerprint-generator": "^2.1.46",
"fingerprint-injector": "^2.1.46",
"playwright": "^1.41.1",
"fingerprint-generator": "^2.1.49",
"fingerprint-injector": "^2.1.49",
"playwright": "^1.42.0",
"ts-node": "^10.9.2"
}
}

View File

@@ -43,6 +43,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) {

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
}
@@ -140,7 +142,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,12 +155,14 @@ 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)) {
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
}
})
}
return totalEarnablePoints
} catch (error) {

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

@@ -12,6 +12,7 @@
"doDesktopSearch": true,
"doMobileSearch": true
},
"globalTimeout": 30000,
"searchSettings": {
"useGeoLocaleQueries": false,
"scrollRandomResults": true,
@@ -26,4 +27,4 @@
"enabled": false,
"url": ""
}
}
}

View File

@@ -2,6 +2,7 @@ import { Page } from 'playwright'
import readline from 'readline'
import { MicrosoftRewardsBot } from '../index'
import { saveSessionData } from '../util/Load'
const rl = readline.createInterface({
input: process.stdin,
@@ -26,14 +27,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 +42,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,35 +55,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

@@ -178,11 +178,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
@@ -242,13 +242,13 @@ export class MicrosoftRewardsBot {
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()

View File

@@ -5,6 +5,7 @@ export interface Config {
runOnZeroPoints: boolean;
clusters: number;
workers: Workers;
globalTimeout: number;
searchSettings: SearchSettings;
webhook: Webhook;
saveFingerprint: boolean;

View File

@@ -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)