Small update to be deployed quickly. (#358)

* chore: Update TypeScript configuration and add @types/node as a dev dependency

* feat: Add unified cross-platform setup script for easier configuration and installation

* docs: Revise README for improved setup instructions and clarity

* feat: Enhance setup scripts with improved prerequisite checks and user prompts

* feat: Refactor setup scripts and enhance browser handling with automatic Playwright installation
This commit is contained in:
Light
2025-09-16 09:34:49 +02:00
committed by GitHub
parent b66114d4dd
commit 02160a07d9
11 changed files with 480 additions and 285 deletions

View File

@@ -25,19 +25,40 @@ class Browser {
}
async createBrowser(proxy: AccountProxy, email: string): Promise<BrowserContext> {
const browser = await playwright.chromium.launch({
//channel: 'msedge', // Uses Edge instead of chrome
headless: this.bot.config.headless,
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
args: [
'--no-sandbox',
'--mute-audio',
'--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors'
]
})
// Optional automatic browser installation (set AUTO_INSTALL_BROWSERS=1)
if (process.env.AUTO_INSTALL_BROWSERS === '1') {
try {
// Dynamically import child_process to avoid overhead otherwise
const { execSync } = await import('child_process') as any
execSync('npx playwright install chromium', { stdio: 'ignore' })
} catch { /* silent */ }
}
let browser: any
try {
browser = await playwright.chromium.launch({
//channel: 'msedge', // Uses Edge instead of chrome
headless: this.bot.config.headless,
...(proxy.url && { proxy: { username: proxy.username, password: proxy.password, server: `${proxy.url}:${proxy.port}` } }),
args: [
'--no-sandbox',
'--mute-audio',
'--disable-setuid-sandbox',
'--ignore-certificate-errors',
'--ignore-certificate-errors-spki-list',
'--ignore-ssl-errors'
]
})
} catch (e: any) {
const msg = (e instanceof Error ? e.message : String(e))
// Common missing browser executable guidance
if (/Executable doesn't exist/i.test(msg)) {
this.bot.log(this.bot.isMobile, 'BROWSER', 'Chromium not installed for Playwright. Run: "npx playwright install chromium" (or set AUTO_INSTALL_BROWSERS=1 to auto attempt).', 'error')
} else {
this.bot.log(this.bot.isMobile, 'BROWSER', 'Failed to launch browser: ' + msg, 'error')
}
throw e
}
const sessionData = await loadSessionData(this.bot.config.sessionPath, email, this.bot.isMobile, this.bot.config.saveFingerprint)

View File

@@ -170,14 +170,28 @@ export class MicrosoftRewardsBot {
const errors: string[] = []
this.axios = new Axios(account.proxy)
const verbose = process.env.DEBUG_REWARDS_VERBOSE === '1'
const formatFullErr = (label: string, e: any) => {
const base = shortErr(e)
if (verbose && e instanceof Error) {
return `${label}:${base} :: ${e.stack?.split('\n').slice(0,4).join(' | ')}`
}
return `${label}:${base}`
}
if (this.config.parallel) {
const mobileInstance = new MicrosoftRewardsBot(true)
mobileInstance.axios = this.axios
// Run both and capture results
const [desktopResult, mobileResult] = await Promise.all([
this.Desktop(account).catch(e => { errors.push(`desktop:${shortErr(e)}`); return null }),
mobileInstance.Mobile(account).catch(e => { errors.push(`mobile:${shortErr(e)}`); return null })
])
// Run both and capture results with detailed logging
const desktopPromise = this.Desktop(account).catch(e => {
log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('desktop', e)); return null
})
const mobilePromise = mobileInstance.Mobile(account).catch(e => {
log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('mobile', e)); return null
})
const [desktopResult, mobileResult] = await Promise.all([desktopPromise, mobilePromise])
if (desktopResult) {
desktopInitial = desktopResult.initialPoints
desktopCollected = desktopResult.collectedPoints
@@ -188,14 +202,20 @@ export class MicrosoftRewardsBot {
}
} else {
this.isMobile = false
const desktopResult = await this.Desktop(account).catch(e => { errors.push(`desktop:${shortErr(e)}`); return null })
const desktopResult = await this.Desktop(account).catch(e => {
log(false, 'TASK', `Desktop flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('desktop', e)); return null
})
if (desktopResult) {
desktopInitial = desktopResult.initialPoints
desktopCollected = desktopResult.collectedPoints
}
this.isMobile = true
const mobileResult = await this.Mobile(account).catch(e => { errors.push(`mobile:${shortErr(e)}`); return null })
const mobileResult = await this.Mobile(account).catch(e => {
log(true, 'TASK', `Mobile flow failed early for ${account.email}: ${e instanceof Error ? e.message : e}`,'error')
errors.push(formatFullErr('mobile', e)); return null
})
if (mobileResult) {
mobileInitial = mobileResult.initialPoints
mobileCollected = mobileResult.collectedPoints
@@ -221,6 +241,12 @@ export class MicrosoftRewardsBot {
}
log(this.isMobile, 'MAIN-PRIMARY', 'Completed tasks for ALL accounts', 'log', 'green')
// Extra diagnostic summary when verbose
if (process.env.DEBUG_REWARDS_VERBOSE === '1') {
for (const summary of this.accountSummaries) {
log('main','SUMMARY-DEBUG',`Account ${summary.email} collected D:${summary.desktopCollected} M:${summary.mobileCollected} TOTAL:${summary.totalCollected} ERRORS:${summary.errors.length ? summary.errors.join(';') : 'none'}`)
}
}
// If in worker mode (clusters>1) send summaries to primary
if (this.config.clusters > 1 && !cluster.isPrimary) {
if (process.send) {
@@ -235,6 +261,7 @@ export class MicrosoftRewardsBot {
// Desktop
async Desktop(account: Account) {
log(false,'FLOW','Desktop() invoked')
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()
@@ -311,6 +338,7 @@ export class MicrosoftRewardsBot {
// Mobile
async Mobile(account: Account) {
log(true,'FLOW','Mobile() invoked')
const browser = await this.browserFactory.createBrowser(account.proxy, account.email)
this.homePage = await browser.newPage()

View File

@@ -1,78 +0,0 @@
// Fallback in case @types/node not installed yet; ensures Process/stubs to reduce red squiggles.
// Prefer installing @types/node for full types.
interface ProcessEnv { [key: string]: string | undefined }
interface Process {
pid: number
exit(code?: number): never
send?(message: any): void
on(event: string, listener: (...args: any[]) => void): any
stdin: { on(event: string, listener: (...args: any[]) => void): any }
stdout: { write(chunk: any): boolean }
env: ProcessEnv
}
declare var process: Process
// Minimal axios module declaration
declare module 'axios' {
export interface AxiosRequestConfig { [key: string]: any }
export interface AxiosResponse<T = any> { data: T }
export interface AxiosInstance {
defaults: any
request<T=any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>
}
export interface AxiosStatic {
(config: AxiosRequestConfig): Promise<AxiosResponse>
request<T=any>(config: AxiosRequestConfig): Promise<AxiosResponse<T>>
create(config?: AxiosRequestConfig): AxiosInstance
}
const axios: AxiosStatic
export default axios
}
// Minimal readline
declare module 'readline' {
export interface Interface { question(query: string, cb: (answer: string)=>void): void; close(): void }
export function createInterface(opts: any): Interface
export default {} as any
}
// Minimal crypto
declare module 'crypto' {
export function randomBytes(size: number): { toString(encoding: string): string }
}
// Minimal os module
declare module 'os' {
export function platform(): string
}
// Minimal cheerio subset
declare module 'cheerio' {
export interface CheerioAPI {
(selector: any): any
load(html: string): CheerioAPI
text(): string
}
export function load(html: string): CheerioAPI
}
declare module 'cluster' {
import { EventEmitter } from 'events'
interface WorkerLike extends EventEmitter {
id: number
process: { pid: number }
send?(message: any): void
on(event: 'message', listener: (msg: any) => void): any
}
interface Cluster extends EventEmitter {
isPrimary: boolean
fork(env?: NodeJS.ProcessEnv): WorkerLike
workers?: Record<string, WorkerLike>
on(event: 'exit', listener: (worker: WorkerLike, code: number) => void): any
}
const cluster: Cluster
export default cluster
}

View File

@@ -1,57 +0,0 @@
// Minimal module declaration to silence TS complaints if upstream types not found.
// You should replace with actual types if the package provides them.
// Basic playwright stubs (only what we currently need). Replace with real @types if available.
declare module 'playwright' {
export interface Cookie { name: string; value: string; domain?: string; path?: string; expires?: number; httpOnly?: boolean; secure?: boolean; sameSite?: 'Lax'|'Strict'|'None' }
export interface BrowserContext {
newPage(): Promise<Page>
setDefaultTimeout(timeout: number): void
addCookies(cookies: Cookie[]): Promise<void>
cookies(): Promise<Cookie[]>
pages(): Page[]
close(): Promise<void>
}
export interface Browser {
newPage(): Promise<Page>
context(): BrowserContext
close(): Promise<void>
pages?(): Page[]
}
export interface Keyboard {
type(text: string): Promise<any>
press(key: string): Promise<any>
down(key: string): Promise<any>
up(key: string): Promise<any>
}
export interface Locator {
first(): Locator
click(opts?: any): Promise<any>
isVisible(opts?: any): Promise<boolean>
nth(index: number): Locator
}
export interface Page {
goto(url: string, opts?: any): Promise<any>
waitForLoadState(state?: string, opts?: any): Promise<any>
waitForSelector(selector: string, opts?: any): Promise<any>
fill(selector: string, value: string): Promise<any>
keyboard: Keyboard
click(selector: string, opts?: any): Promise<any>
close(): Promise<any>
url(): string
route(match: string, handler: any): Promise<any>
locator(selector: string): Locator
$: (selector: string) => Promise<any>
context(): BrowserContext
reload(opts?: any): Promise<any>
evaluate<R=any>(pageFunction: any, arg?: any): Promise<R>
content(): Promise<string>
waitForTimeout(timeout: number): Promise<void>
}
export interface ChromiumType { launch(opts?: any): Promise<Browser> }
export const chromium: ChromiumType
}
declare module 'rebrowser-playwright' {
export * from 'playwright'
}

View File

@@ -1,25 +0,0 @@
// Minimal shims to silence TypeScript errors in environments without @types/node
// If possible, install @types/node instead for full typing.
declare const __dirname: string
declare namespace NodeJS { interface Process { pid: number; send?: (msg: any) => void; exit(code?: number): void; } }
declare const process: NodeJS.Process
declare module 'cluster' {
interface Worker { process: { pid: number }; on(event: 'message', cb: (msg: any) => void): void }
const isPrimary: boolean
function fork(): Worker
function on(event: 'exit', cb: (worker: Worker, code: number) => void): void
export { isPrimary, fork, on, Worker }
export default { isPrimary, fork, on }
}
declare module 'fs' { const x: any; export = x }
declare module 'path' { const x: any; export = x }
// Do NOT redeclare 'Page' to avoid erasing actual Playwright types if present.
// If types are missing, install: npm i -D @types/node