mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 05:53:57 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef6ad569ff | ||
|
|
da9ba91c5c | ||
|
|
deb2d58b1b | ||
|
|
66a82c2584 | ||
|
|
8a022d5983 | ||
|
|
64048e35d7 | ||
|
|
cf7f7ac790 | ||
|
|
f7aa5039f9 | ||
|
|
e082fb03f0 | ||
|
|
0303b8c605 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ accounts.json
|
|||||||
notes
|
notes
|
||||||
accounts.dev.json
|
accounts.dev.json
|
||||||
accounts.main.json
|
accounts.main.json
|
||||||
|
.DS_Store
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ RUN apt-get update && apt-get install -y jq
|
|||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Check if "headless" is set to "true" in the config.json file
|
# 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) \
|
RUN HEADLESS=$(cat src/config.json | jq -r .headless) \
|
||||||
&& if [ "$HEADLESS" != "true" ]; then \
|
&& if [ "$HEADLESS" != "true" ]; then \
|
||||||
echo "Error: 'headless' in src/config.json is not true."; \
|
echo "Error: 'headless' in src/config.json is not true."; \
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -12,14 +12,31 @@ Under development, however mainly for personal use!
|
|||||||
6. Run `npm run start` to start the built script
|
6. Run `npm run start` to start the built script
|
||||||
|
|
||||||
## Notes ##
|
## 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.
|
- If you automate this script, set it to run at least 2 times a day to make sure it picked up all tasks, set `"runOnZeroPoints": false` so it doesn't run when no points are found.
|
||||||
|
|
||||||
## Docker (Experimental) ##
|
## Docker (Experimental) ##
|
||||||
1. Download the source code
|
1. Download the source code
|
||||||
2. Make changes to your `accounts.json` and/or `config.json`
|
2. Make changes to your `accounts.json`
|
||||||
3. Run `docker build -t microsoft-rewards-script-docker .`
|
3. Make sure to change `"headless": false` to `"headless": true` in your `config.json`
|
||||||
- Docker container has to be recreated for any changes regarding the `config.json` and/or `accounts.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 ##
|
## Config ##
|
||||||
| Setting | Description | Default |
|
| Setting | Description | Default |
|
||||||
@@ -35,6 +52,7 @@ Under development, however mainly for personal use!
|
|||||||
| workers.doPunchCards | Complete punchcards | `true` |
|
| workers.doPunchCards | Complete punchcards | `true` |
|
||||||
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
|
| workers.doDesktopSearch | Complete daily desktop searches | `true` |
|
||||||
| workers.doMobileSearch | Complete daily mobile searches | `true` |
|
| workers.doMobileSearch | Complete daily mobile searches | `true` |
|
||||||
|
| 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) |
|
| searchSettings.useGeoLocaleQueries | Generate search queries based on your geo-location | `false` (Uses EN-US generated queries) |
|
||||||
| scrollRandomResults | Scroll randomly in search results | `true` |
|
| scrollRandomResults | Scroll randomly in search results | `true` |
|
||||||
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
| searchSettings.clickRandomResults | Visit random website from search result| `true` |
|
||||||
|
|||||||
8
compose.yaml
Normal file
8
compose.yaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
microsoft-rewards-script:
|
||||||
|
build: .
|
||||||
|
container_name: netsky
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/microsoft-rewards-script
|
||||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "microsoft-rewards-script",
|
"name": "microsoft-rewards-script",
|
||||||
"version": "1.4.3",
|
"version": "1.4.6",
|
||||||
"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": "^6.7.0",
|
"@typescript-eslint/eslint-plugin": "^6.20.0",
|
||||||
"eslint": "^8.54.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-plugin-modules-newline": "^0.0.6",
|
"eslint-plugin-modules-newline": "^0.0.6",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.7",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"fingerprint-generator": "^2.1.46",
|
"fingerprint-generator": "^2.1.49",
|
||||||
"fingerprint-injector": "^2.1.46",
|
"fingerprint-injector": "^2.1.49",
|
||||||
"playwright": "^1.41.1",
|
"playwright": "^1.42.0",
|
||||||
"ts-node": "^10.9.2"
|
"ts-node": "^10.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ class Browser {
|
|||||||
|
|
||||||
const context = await newInjectedContext(browser, { fingerprint: fingerpint })
|
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)
|
await context.addCookies(sessionData.cookies)
|
||||||
|
|
||||||
if (this.bot.config.saveFingerprint) {
|
if (this.bot.config.saveFingerprint) {
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ export default class BrowserFunc {
|
|||||||
const scripts = Array.from(document.querySelectorAll('script'))
|
const scripts = Array.from(document.querySelectorAll('script'))
|
||||||
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
const targetScript = scripts.find(script => script.innerText.includes('var dashboard'))
|
||||||
|
|
||||||
if (targetScript) {
|
return targetScript?.innerText ? targetScript.innerText : null
|
||||||
return targetScript.innerText
|
|
||||||
} else {
|
|
||||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Script containing dashboard data not found', 'error')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!scriptContent) {
|
||||||
|
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
// Extract the dashboard object from the script content
|
// Extract the dashboard object from the script content
|
||||||
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
const dashboardData = await this.bot.homePage.evaluate(scriptContent => {
|
||||||
// Extract the dashboard object using regex
|
// Extract the dashboard object using regex
|
||||||
@@ -110,11 +110,13 @@ export default class BrowserFunc {
|
|||||||
|
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
return JSON.parse(match[1])
|
return JSON.parse(match[1])
|
||||||
} else {
|
|
||||||
throw this.bot.log('GET-DASHBOARD-DATA', 'Dashboard data not found within script', 'error')
|
|
||||||
}
|
}
|
||||||
}, scriptContent)
|
}, scriptContent)
|
||||||
|
|
||||||
|
if (!dashboardData) {
|
||||||
|
throw this.bot.log('GET-DASHBOARD-DATA', 'Unable to parse dashboard script', 'error')
|
||||||
|
}
|
||||||
|
|
||||||
return dashboardData
|
return dashboardData
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +142,9 @@ export default class BrowserFunc {
|
|||||||
let totalEarnablePoints = 0
|
let totalEarnablePoints = 0
|
||||||
|
|
||||||
// Desktop Search Points
|
// 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
|
// Mobile Search Points
|
||||||
if (data.userStatus.counters.mobileSearch?.length) {
|
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))
|
data.dailySetPromotions[this.bot.utils.getFormattedDate()]?.forEach(x => totalEarnablePoints += (x.pointProgressMax - x.pointProgress))
|
||||||
|
|
||||||
// More Promotions
|
// More Promotions
|
||||||
data.morePromotions.forEach(x => {
|
if (data.morePromotions?.length) {
|
||||||
// Only count points from supported activities
|
data.morePromotions.forEach(x => {
|
||||||
if (['quiz', 'urlreward'].includes(x.activityType)) {
|
// Only count points from supported activities
|
||||||
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
if (['quiz', 'urlreward'].includes(x.promotionType)) {
|
||||||
}
|
totalEarnablePoints += (x.pointProgressMax - x.pointProgress)
|
||||||
})
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return totalEarnablePoints
|
return totalEarnablePoints
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export default class BrowserUtil {
|
|||||||
|
|
||||||
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
async tryDismissAllMessages(page: Page): Promise<boolean> {
|
||||||
const buttons = [
|
const buttons = [
|
||||||
|
{ selector: '#acceptButton', label: 'AcceptButton' },
|
||||||
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
{ selector: '#iLandingViewAction', label: 'iLandingViewAction' },
|
||||||
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
{ selector: '#iShowSkip', label: 'iShowSkip' },
|
||||||
{ selector: '#iNext', label: 'iNext' },
|
{ selector: '#iNext', label: 'iNext' },
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"doDesktopSearch": true,
|
"doDesktopSearch": true,
|
||||||
"doMobileSearch": true
|
"doMobileSearch": true
|
||||||
},
|
},
|
||||||
|
"globalTimeout": 30000,
|
||||||
"searchSettings": {
|
"searchSettings": {
|
||||||
"useGeoLocaleQueries": false,
|
"useGeoLocaleQueries": false,
|
||||||
"scrollRandomResults": true,
|
"scrollRandomResults": true,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Page } from 'playwright'
|
|||||||
import readline from 'readline'
|
import readline from 'readline'
|
||||||
|
|
||||||
import { MicrosoftRewardsBot } from '../index'
|
import { MicrosoftRewardsBot } from '../index'
|
||||||
|
import { saveSessionData } from '../util/Load'
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const rl = readline.createInterface({
|
||||||
input: process.stdin,
|
input: process.stdin,
|
||||||
@@ -26,14 +27,12 @@ export class Login {
|
|||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
// Check if account is locked
|
// 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) {
|
if (isLocked) {
|
||||||
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
this.bot.log('LOGIN', 'This account has been locked!', 'error')
|
||||||
throw new Error('Account has been locked!')
|
throw new Error('Account has been locked!')
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.waitForSelector('#loginHeader', { state: 'visible', timeout: 10_000 })
|
|
||||||
|
|
||||||
await this.execLogin(page, email, password)
|
await this.execLogin(page, email, password)
|
||||||
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
this.bot.log('LOGIN', 'Logged into Microsoft successfully')
|
||||||
} else {
|
} else {
|
||||||
@@ -43,6 +42,9 @@ export class Login {
|
|||||||
// Check if logged in to bing
|
// Check if logged in to bing
|
||||||
await this.checkBingLogin(page)
|
await this.checkBingLogin(page)
|
||||||
|
|
||||||
|
// Save session
|
||||||
|
await saveSessionData(this.bot.config.sessionPath, page.context(), email, this.bot.isMobile)
|
||||||
|
|
||||||
// We're done logging in
|
// We're done logging in
|
||||||
this.bot.log('LOGIN', 'Logged in successfully')
|
this.bot.log('LOGIN', 'Logged in successfully')
|
||||||
|
|
||||||
@@ -53,35 +55,41 @@ export class Login {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async execLogin(page: Page, email: string, password: string) {
|
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 {
|
try {
|
||||||
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
// Enter email
|
||||||
await this.bot.utils.wait(2000)
|
await page.fill('#i0116', email)
|
||||||
|
|
||||||
await page.type('#i0118', password)
|
|
||||||
await page.click('#idSIButton9')
|
await page.click('#idSIButton9')
|
||||||
|
|
||||||
// When erroring at this stage it means a 2FA code is required
|
this.bot.log('LOGIN', 'Email entered successfully')
|
||||||
} catch (error) {
|
|
||||||
this.bot.log('LOGIN', '2FA code required')
|
|
||||||
|
|
||||||
// Wait for user input
|
try {
|
||||||
const code = await new Promise<string>((resolve) => {
|
// Enter password
|
||||||
rl.question('Enter 2FA code:\n', (input) => {
|
await page.waitForSelector('#i0118', { state: 'visible', timeout: 2000 })
|
||||||
rl.close()
|
await this.bot.utils.wait(2000)
|
||||||
resolve(input)
|
|
||||||
|
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.fill('input[name="otc"]', code)
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
}
|
||||||
|
|
||||||
} finally {
|
|
||||||
this.bot.log('LOGIN', 'Password entered successfully')
|
this.bot.log('LOGIN', 'Password entered successfully')
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.bot.log('LOGIN', 'An error occurred:' + error, 'error')
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentURL = new URL(page.url())
|
const currentURL = new URL(page.url())
|
||||||
|
|||||||
12
src/index.ts
12
src/index.ts
@@ -178,11 +178,11 @@ export class MicrosoftRewardsBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save cookies
|
// Save cookies
|
||||||
const cookies = await browser.cookies()
|
await saveSessionData(this.config.sessionPath, browser, account.email, this.isMobile)
|
||||||
await saveSessionData(this.config.sessionPath, account.email, this.isMobile, cookies)
|
|
||||||
|
|
||||||
// Close desktop browser
|
// Close desktop browser
|
||||||
return await this.closeBrowser(browser, account.email)
|
await this.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mobile
|
// Mobile
|
||||||
@@ -242,13 +242,13 @@ export class MicrosoftRewardsBot {
|
|||||||
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
log('MAIN-POINTS', `The script collected ${this.collectedPoints} points today`)
|
||||||
|
|
||||||
// Close mobile browser
|
// Close mobile browser
|
||||||
return await this.closeBrowser(browser, account.email)
|
await this.closeBrowser(browser, account.email)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
private async closeBrowser(browser: BrowserContext, email: string) {
|
private async closeBrowser(browser: BrowserContext, email: string) {
|
||||||
// Save cookies
|
// Save cookies
|
||||||
const cookies = await browser.cookies()
|
await saveSessionData(this.config.sessionPath, browser, email, this.isMobile)
|
||||||
await saveSessionData(this.config.sessionPath, email, this.isMobile, cookies)
|
|
||||||
|
|
||||||
// Close browser
|
// Close browser
|
||||||
await browser.close()
|
await browser.close()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export interface Config {
|
|||||||
runOnZeroPoints: boolean;
|
runOnZeroPoints: boolean;
|
||||||
clusters: number;
|
clusters: number;
|
||||||
workers: Workers;
|
workers: Workers;
|
||||||
|
globalTimeout: number;
|
||||||
searchSettings: SearchSettings;
|
searchSettings: SearchSettings;
|
||||||
webhook: Webhook;
|
webhook: Webhook;
|
||||||
saveFingerprint: boolean;
|
saveFingerprint: boolean;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Cookie } from 'playwright'
|
import { BrowserContext, Cookie } from 'playwright'
|
||||||
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
import { BrowserFingerprintWithHeaders } from 'fingerprint-generator'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -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 {
|
try {
|
||||||
|
const cookies = await browser.cookies()
|
||||||
|
|
||||||
// Fetch path
|
// Fetch path
|
||||||
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
const sessionDir = path.join(__dirname, '../browser/', sessionPath, email)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user