Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sessions/
dist/
.dev/
.idea
node_modules/
/config.json
# ClawBot 微信推送的扫码凭证(含 bot token),严禁提交
Expand All @@ -20,3 +20,6 @@ note
# 本地私密配置与编辑器工作目录,严禁提交
/.env
/.zcode/
# 浏览器会话数据库(含 Cookie/账号信息),严禁提交
/sessions/
/src/sessions/
4 changes: 2 additions & 2 deletions config.example.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sessionPath": "sessions",
"headless": false,
"headless": true,
"clusters": 1,
"errorDiagnostics": false,
"ensureStreakProtection": true,
Expand Down Expand Up @@ -51,7 +51,7 @@
"max": "11min"
},
"chinaApi": {
"appkey": ""
"appkey": "52930078ecae0a24e7f922740f59d613"
}
},
"experimental": {
Expand Down
113 changes: 113 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"sessionPath": "sessions",
"headless": true,
"clusters": 1,
"errorDiagnostics": false,
"ensureStreakProtection": true,
"autoClaimPunchcardRewards": false,
"skipNonPointTasks": true,
"accountDelay": {
"min": "1min",
"max": "3min"
},
"workers": {
"doDailySet": true,
"doClaimBonusPoints": true,
"doMorePromotions": true,
"doPunchCards": true,
"doAppPromotions": true,
"doDesktopSearch": true,
"doMobileSearch": true,
"doBonusSearches": false,
"doDailyCheckIn": true,
"doReadToEarn": true,
"doActivateSearchPerk": true,
"doVisualSearch": false
},
"activities": {
"urlReward": true,
"searchOnBing": true
},
"searchOnBingLocalQueries": false,
"globalTimeout": "50sec",
"searchSettings": {
"scrollRandomResults": true,
"clickRandomResults": true,
"runOnZeroPoints": false,
"maxBonusSearches": 110,
"parallelSearching": false,
"clusterSearch": true,
"queryEngines": [
"china",
"local"
],
"searchResultVisitTime": "20sec",
"searchDelay": {
"min": "6min",
"max": "12min"
},
"readDelay": {
"min": "6min",
"max": "11min"
},
"chinaApi": {
"appkey": ""
}
},
"experimental": {
"apiSearch": false,
"apiSearchOnBing": false,
"blockMedia": false,
"edgeBrowsing": false
},
"debugLogs": true,
"consoleLogFilter": {
"enabled": false,
"mode": "whitelist",
"levels": ["error", "warn"],
"keywords": ["starting account"],
"regexPatterns": []
},
"proxy": {
"queryEngine": false,
"ignoreCertificateErrors": false
},
"webhook": {
"discord": {
"enabled": false,
"url": ""
},
"telegram": {
"enabled": false,
"botToken": "",
"chatId": ""
},
"pushplus": {
"enabled": false,
"token": "",
"title": "Microsoft-Rewards-Script",
"template": "txt",
"channel": ""
},
"clawbot": {
"enabled": false,
"authFile": ""
},
"ntfy": {
"enabled": false,
"url": "",
"topic": "",
"token": "",
"title": "Microsoft-Rewards-Script",
"tags": ["bot", "notify"],
"priority": 3
},
"webhookLogFilter": {
"enabled": false,
"mode": "whitelist",
"levels": ["error"],
"keywords": ["starting account", "select number", "collected"],
"regexPatterns": []
}
}
}
46 changes: 46 additions & 0 deletions env.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Account Credentials
# Copy this file to .env and fill in your details.
# .env should be in your .gitignore, never commit your credentials!
#
# Add one numbered block per account. Slots may be skipped; for example,
# ACCOUNT_2 can be used even when ACCOUNT_1 is not configured.
#
# LANG_CODE accepts a BCP 47 language tag such as nl, it, or pt-BR.
# GEO_LOCALE accepts a two-letter country code. With auto, the Microsoft
# profile country is cached after a successful dashboard request and used for
# later browser fingerprints and request headers.

# Account 1
ACCOUNT_1_EMAIL=tmor2026
ACCOUNT_1_PASSWORD=Wang930513
#ACCOUNT_1_TOTP_SECRET=
#ACCOUNT_1_RECOVERY_EMAIL=
# 国内账号推荐:GEO_LOCALE=CN、LANG_CODE=zh-CN(配合 china 热搜源)
ACCOUNT_1_LANG_CODE=zh-CN
ACCOUNT_1_GEO_LOCALE=CN
# Browser proxy: HTTP/HTTPS/SOCKS4/SOCKS5. SOCKS proxies must not require authentication.
#ACCOUNT_1_PROXY_HTTP=false
#ACCOUNT_1_PROXY_URL=
#ACCOUNT_1_PROXY_PORT=0
#ACCOUNT_1_PROXY_USERNAME=
#ACCOUNT_1_PROXY_PASSWORD=
ACCOUNT_1_SAVE_FINGERPRINT_MOBILE=true
ACCOUNT_1_SAVE_FINGERPRINT_DESKTOP=true

# Account 2 (duplicate and use any positive slot number for more accounts)
#ACCOUNT_2_EMAIL=email_2@example.com
#ACCOUNT_2_PASSWORD=your_password
#ACCOUNT_2_TOTP_SECRET=
#ACCOUNT_2_RECOVERY_EMAIL=
#ACCOUNT_2_GEO_LOCALE=auto
#ACCOUNT_2_LANG_CODE=en
#ACCOUNT_2_PROXY_HTTP=false
#ACCOUNT_2_PROXY_URL=
#ACCOUNT_2_PROXY_PORT=0
#ACCOUNT_2_PROXY_USERNAME=
#ACCOUNT_2_PROXY_PASSWORD=

# Control API (used when API_MODE=true in compose.yaml)
# See scripts/api/README.md for full documentation.
# Generate a token: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
#API_TOKEN=
4 changes: 2 additions & 2 deletions env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
# later browser fingerprints and request headers.

# Account 1
ACCOUNT_1_EMAIL=email@example.com
ACCOUNT_1_PASSWORD=your_password
ACCOUNT_1_EMAIL=tmor2026
ACCOUNT_1_PASSWORD=Wang930513
#ACCOUNT_1_TOTP_SECRET=
#ACCOUNT_1_RECOVERY_EMAIL=
# 国内账号推荐:GEO_LOCALE=CN、LANG_CODE=zh-CN(配合 china 热搜源)
Expand Down
26 changes: 26 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
"pre-build": "npm i && rimraf dist && npx patchright install chromium",
"install:browser": "npx patchright install chromium",
"build": "rimraf dist && tsc && node ./scripts/main/copyAssets.js",
"start": "node ./dist/index.js",
"ts-start": "ts-node ./src/index.ts",
"dev": "ts-node ./src/index.ts -dev",
"start": "cross-env HTTPS_PROXY= HTTP_PROXY= https_proxy= http_proxy= node ./dist/index.js",
"ts-start": "cross-env HTTPS_PROXY= HTTP_PROXY= https_proxy= http_proxy= ts-node ./src/index.ts",
"dev": "cross-env HTTPS_PROXY= HTTP_PROXY= https_proxy= http_proxy= ts-node ./src/index.ts -dev",
"api": "node ./scripts/api/server.js",
"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 .",
Expand Down Expand Up @@ -42,6 +42,7 @@
"@types/node": "^24.10.1",
"@types/qrcode-terminal": "^0.12.2",
"@types/semver": "^7.7.1",
"cross-env": "^10.1.0",
"eslint": "^10.4.0",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.6.0",
Expand Down
21 changes: 18 additions & 3 deletions src/browser/BrowserFunc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ export default class BrowserFunc {
if (response.data) return response.data
throw new Error('Dashboard data missing from API response')
} catch (error) {
throw this.bot.logger.error(
this.bot.logger.error(
this.bot.isMobile,
'GET-DASHBOARD-DATA',
`获取仪表板数据失败: ${error instanceof Error ? error.message : String(error)}`
)
throw error
}
}

Expand Down Expand Up @@ -208,8 +209,22 @@ export default class BrowserFunc {

async bootstrap(page: Page): Promise<void> {
try {
// /earn is the offers page
await page.goto(URLs.rewards.earn, { waitUntil: 'domcontentloaded' })
// /earn is the offers page(代理连接不稳定时重试 3 次)
let gotoOk = false
for (let i = 1; i <= 3 && !gotoOk; i++) {
try {
await page.goto(URLs.rewards.earn, { waitUntil: 'domcontentloaded', timeout: 30000 })
gotoOk = true
} catch (e) {
if (i === 3) throw e
this.bot.logger.warn(
this.bot.isMobile,
'BOOTSTRAP',
`访问 /earn 失败(尝试 ${i}/3): ${e instanceof Error ? e.message : String(e)},重试中...`
)
await this.bot.utils.wait(2000)
}
}

const earnDom = await page.content()
const earnRaw = await this.fetchBootstrapHtml(page, URLs.rewards.earn, '/earn')
Expand Down
20 changes: 17 additions & 3 deletions src/browser/BrowserUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ export default class BrowserUtils {

const newTab = pages[pages.length - 1]
if (!newTab) {
throw this.bot.logger.error(this.bot.isMobile, 'GET-NEW-TAB', '找不到任何标签页!')
this.bot.logger.error(this.bot.isMobile, 'GET-NEW-TAB', '找不到任何标签页!')
throw new Error('找不到任何标签页!')
}

return newTab
Expand Down Expand Up @@ -213,14 +214,27 @@ export default class BrowserUtils {
`尝试点击选择器: ${selector},选项: ${JSON.stringify(options)}`
)

// Wait for selector to exist before clicking
await page.waitForSelector(selector, { timeout: 1000 }).catch(() => {})
// Wait for selector to be visible before clicking
await page.waitForSelector(selector, { state: 'visible', timeout: 2000 }).catch(() => {})

// ghost-cursor expects its own Playwright Page type from a different
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const cursor = createCursor(page as any)
await cursor.click(selector, options)

return true
} catch (error) {
this.bot.logger.warn(
this.bot.isMobile,
'GHOST-CLICK',
`ghost-cursor点击失败,尝试原生点击: ${error instanceof Error ? error.message : String(error)}`
)
}

// Fallback: use native Playwright click
try {
await page.locator(selector).first().click({ timeout: 2000 })
this.bot.logger.debug(this.bot.isMobile, 'GHOST-CLICK', `原生点击成功: ${selector}`)
return true
} catch (error) {
this.bot.logger.warn(
Expand Down
Loading