mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-09 17:06:15 +00:00
feat: Simplify update process by removing deprecated methods and enhancing README; improve type safety in tests
This commit is contained in:
@@ -33,21 +33,15 @@ npm start
|
||||
**Automatic update script** that keeps your bot up-to-date with the latest version.
|
||||
|
||||
**Features:**
|
||||
- Two update methods: Git-based or GitHub API (no Git needed)
|
||||
- Uses GitHub API (downloads ZIP - no Git required)
|
||||
- Preserves your configuration and accounts
|
||||
- No merge conflicts with GitHub API method
|
||||
- No merge conflicts, always clean
|
||||
- Automatic dependency installation and rebuild
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Auto-detect method from config.jsonc
|
||||
# Run update manually
|
||||
node setup/update/update.mjs
|
||||
|
||||
# Force GitHub API method (recommended)
|
||||
node setup/update/update.mjs --no-git
|
||||
|
||||
# Force Git method
|
||||
node setup/update/update.mjs --git
|
||||
```
|
||||
|
||||
**Automatic updates:** The bot checks for updates on startup (controlled by `update.enabled` in config.jsonc).
|
||||
|
||||
@@ -1007,12 +1007,6 @@ export class AccountCreator {
|
||||
if (!suggestionsContainer) {
|
||||
log(false, 'CREATOR', 'No suggestions found from Microsoft', 'warn', 'yellow')
|
||||
|
||||
// Debug: check HTML content
|
||||
const pageContent = await this.page.content()
|
||||
const hasDataTestId = pageContent.includes('data-testid="suggestions"')
|
||||
const hasToolbar = pageContent.includes('role="toolbar"')
|
||||
log(false, 'CREATOR', `Debug - suggestions in HTML: ${hasDataTestId}, toolbar: ${hasToolbar}`, 'warn', 'yellow')
|
||||
|
||||
// CRITICAL FIX: Generate a new email automatically instead of freezing
|
||||
log(false, 'CREATOR', '🔄 Generating a new email automatically...', 'log', 'cyan')
|
||||
|
||||
|
||||
28
src/index.ts
28
src/index.ts
@@ -232,14 +232,6 @@ export class MicrosoftRewardsBot {
|
||||
|
||||
log('main', 'BANNER', `Microsoft Rewards Bot v${version}`)
|
||||
log('main', 'BANNER', `PID: ${process.pid} | Workers: ${this.config.clusters}`)
|
||||
|
||||
const upd = this.config.update || {}
|
||||
const updTargets: string[] = []
|
||||
if (upd.method && upd.method !== 'zip') updTargets.push(`Update: ${upd.method}`)
|
||||
if (upd.docker) updTargets.push('Docker')
|
||||
if (updTargets.length > 0) {
|
||||
log('main', 'BANNER', `Auto-Update: ${updTargets.join(', ')}`)
|
||||
}
|
||||
}
|
||||
|
||||
private getVersion(): string {
|
||||
@@ -770,28 +762,12 @@ export class MicrosoftRewardsBot {
|
||||
return 0
|
||||
}
|
||||
|
||||
const args: string[] = []
|
||||
|
||||
// Determine update method from config (github-api is default and recommended)
|
||||
const method = upd.method || 'github-api'
|
||||
|
||||
if (method === 'github-api' || method === 'api' || method === 'zip') {
|
||||
// Use GitHub API method (no Git needed, no conflicts)
|
||||
args.push('--no-git')
|
||||
} else {
|
||||
// Unknown method, default to github-api
|
||||
log('main', 'UPDATE', `Unknown update method "${method}", using github-api`, 'warn')
|
||||
args.push('--no-git')
|
||||
}
|
||||
|
||||
// Add Docker flag if enabled
|
||||
if (upd.docker) args.push('--docker')
|
||||
|
||||
// New update.mjs uses GitHub API only and takes no CLI arguments
|
||||
log('main', 'UPDATE', `Running update script: ${scriptRel}`, 'log')
|
||||
|
||||
// Run update script as a child process and capture exit code
|
||||
return new Promise<number>((resolve) => {
|
||||
const child = spawn(process.execPath, [scriptAbs, ...args], { stdio: 'inherit' })
|
||||
const child = spawn(process.execPath, [scriptAbs], { stdio: 'inherit' })
|
||||
child.on('close', (code) => {
|
||||
log('main', 'UPDATE', `Update script exited with code ${code ?? 0}`, code === 0 ? 'log' : 'warn')
|
||||
resolve(code ?? 0)
|
||||
|
||||
@@ -83,11 +83,10 @@ export interface ConfigProxy {
|
||||
|
||||
export interface ConfigUpdate {
|
||||
enabled?: boolean; // Master toggle for auto-updates (default: true)
|
||||
method?: 'github-api' | 'api' | 'zip'; // Update method (default: "github-api")
|
||||
docker?: boolean; // if true, run docker update routine (compose pull/up) after completion
|
||||
scriptPath?: string; // optional custom path to update script relative to repo root
|
||||
autoUpdateConfig?: boolean; // if true, allow auto-update of config.jsonc when remote changes it (default: false to preserve user settings)
|
||||
autoUpdateAccounts?: boolean; // if true, allow auto-update of accounts.json when remote changes it (default: false to preserve credentials)
|
||||
// DEPRECATED (removed in v2.56.2+): method, docker - update.mjs now uses GitHub API only
|
||||
}
|
||||
|
||||
export interface ConfigVacation {
|
||||
|
||||
@@ -70,11 +70,10 @@ function stripJsonComments(input: string): string {
|
||||
|
||||
// Normalize both legacy (flat) and new (nested) config schemas into the flat Config interface
|
||||
function normalizeConfig(raw: unknown): Config {
|
||||
// TYPE SAFETY NOTE: Using `any` here is necessary for backwards compatibility
|
||||
// JUSTIFIED USE OF `any`: The config format has evolved from flat → nested structure over time
|
||||
// This needs to support BOTH formats for backward compatibility with existing user configs
|
||||
// Runtime validation happens through explicit property checks and the Config interface return type ensures type safety at function boundary
|
||||
// Alternative approaches (discriminated unions, conditional types) would require extensive runtime checks making code significantly more complex
|
||||
// TYPE SAFETY NOTE: Using `any` here is intentional and justified
|
||||
// Reason: Config format evolved from flat → nested structure. This function must support BOTH
|
||||
// for backward compatibility. Runtime validation via explicit checks ensures safety.
|
||||
// Return type (Config interface) provides type safety at function boundary.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const n = (raw || {}) as any
|
||||
|
||||
|
||||
@@ -49,6 +49,14 @@ if (typeof cleanupInterval.unref === 'function') {
|
||||
cleanupInterval.unref()
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the webhook buffer cleanup interval
|
||||
* Call this during graceful shutdown to prevent memory leaks
|
||||
*/
|
||||
export function stopWebhookCleanup(): void {
|
||||
clearInterval(cleanupInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create a webhook buffer for the given URL
|
||||
* Buffers batch log messages to reduce Discord API calls
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
|
||||
import { LoginState, LoginStateDetector } from '../src/util/LoginStateDetector'
|
||||
|
||||
@@ -7,6 +7,9 @@ import { LoginState, LoginStateDetector } from '../src/util/LoginStateDetector'
|
||||
* Tests for LoginStateDetector - login flow state machine
|
||||
*/
|
||||
|
||||
// Type helper for mock Page objects in tests
|
||||
type MockPage = Parameters<typeof LoginStateDetector.detectState>[0]
|
||||
|
||||
test('LoginState enum contains expected states', () => {
|
||||
assert.ok(LoginState.EmailPage, 'Should have EmailPage state')
|
||||
assert.ok(LoginState.PasswordPage, 'Should have PasswordPage state')
|
||||
@@ -16,19 +19,19 @@ test('LoginState enum contains expected states', () => {
|
||||
})
|
||||
|
||||
test('detectState returns LoginStateDetection structure', async () => {
|
||||
// Mock page object
|
||||
// Mock page object with proper Playwright Page interface
|
||||
const mockPage = {
|
||||
url: () => 'https://rewards.bing.com/',
|
||||
locator: (selector: string) => ({
|
||||
locator: (_selector: string) => ({
|
||||
first: () => ({
|
||||
isVisible: () => Promise.resolve(true),
|
||||
textContent: () => Promise.resolve('Test')
|
||||
})
|
||||
}),
|
||||
evaluate: () => Promise.resolve(150)
|
||||
}
|
||||
} as unknown as Parameters<typeof LoginStateDetector.detectState>[0]
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage)
|
||||
|
||||
assert.ok(detection, 'Should return detection object')
|
||||
assert.ok(typeof detection.state === 'string', 'Should have state property')
|
||||
@@ -57,7 +60,7 @@ test('detectState identifies LoggedIn state on rewards domain', async () => {
|
||||
evaluate: () => Promise.resolve(200)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.LoggedIn, 'Should detect LoggedIn state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -85,7 +88,7 @@ test('detectState identifies EmailPage state on login.live.com', async () => {
|
||||
evaluate: () => Promise.resolve(100)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.EmailPage, 'Should detect EmailPage state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -112,7 +115,7 @@ test('detectState identifies PasswordPage state', async () => {
|
||||
evaluate: () => Promise.resolve(100)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.PasswordPage, 'Should detect PasswordPage state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -139,7 +142,7 @@ test('detectState identifies TwoFactorRequired state', async () => {
|
||||
evaluate: () => Promise.resolve(100)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.TwoFactorRequired, 'Should detect TwoFactorRequired state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -167,7 +170,7 @@ test('detectState identifies PasskeyPrompt state', async () => {
|
||||
evaluate: () => Promise.resolve(100)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.PasskeyPrompt, 'Should detect PasskeyPrompt state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -181,7 +184,7 @@ test('detectState identifies Blocked state', async () => {
|
||||
return {
|
||||
first: () => ({
|
||||
isVisible: () => Promise.resolve(false),
|
||||
textContent: () => Promise.resolve("We can't sign you in")
|
||||
textContent: () => Promise.resolve('We can\'t sign you in')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -195,7 +198,7 @@ test('detectState identifies Blocked state', async () => {
|
||||
evaluate: () => Promise.resolve(100)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.Blocked, 'Should detect Blocked state')
|
||||
assert.equal(detection.confidence, 'high', 'Should have high confidence')
|
||||
@@ -213,7 +216,7 @@ test('detectState returns Unknown for ambiguous pages', async () => {
|
||||
evaluate: () => Promise.resolve(50)
|
||||
}
|
||||
|
||||
const detection = await LoginStateDetector.detectState(mockPage as any)
|
||||
const detection = await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
|
||||
assert.equal(detection.state, LoginState.Unknown, 'Should return Unknown for ambiguous pages')
|
||||
assert.equal(detection.confidence, 'low', 'Should have low confidence')
|
||||
@@ -231,7 +234,7 @@ test('detectState handles errors gracefully', async () => {
|
||||
}
|
||||
|
||||
try {
|
||||
await LoginStateDetector.detectState(mockPage as any)
|
||||
await LoginStateDetector.detectState(mockPage as MockPage)
|
||||
assert.fail('Should throw error')
|
||||
} catch (e) {
|
||||
assert.ok(e instanceof Error, 'Should throw Error instance')
|
||||
|
||||
Reference in New Issue
Block a user