From 84484817ad599bd88f594e3600ecb70439d13734 Mon Sep 17 00:00:00 2001 From: LightZirconite Date: Thu, 13 Nov 2025 21:33:01 +0100 Subject: [PATCH] fix: Update package version to 2.56.12 and enhance webhook summary with detailed statistics --- package-lock.json | 6 +- package.json | 2 +- src/flows/SummaryReporter.ts | 188 +++++++++++++++++++++++++---- src/index.ts | 9 +- tests/webhookPreview.test.ts | 221 +++++++++++++++++++++++++++++++++++ 5 files changed, 400 insertions(+), 26 deletions(-) create mode 100644 tests/webhookPreview.test.ts diff --git a/package-lock.json b/package-lock.json index b14f81f..8ccf5be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "microsoft-rewards-bot", - "version": "2.56.11", + "version": "2.56.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "microsoft-rewards-bot", - "version": "2.56.11", + "version": "2.56.12", "hasInstallScript": true, "license": "CC-BY-NC-SA-4.0", "dependencies": { @@ -4258,4 +4258,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index d9ae587..af35485 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "microsoft-rewards-bot", - "version": "2.56.11", + "version": "2.56.12", "description": "Automate Microsoft Rewards points collection", "private": true, "main": "index.js", diff --git a/src/flows/SummaryReporter.ts b/src/flows/SummaryReporter.ts index 738e5e4..6f66fad 100644 --- a/src/flows/SummaryReporter.ts +++ b/src/flows/SummaryReporter.ts @@ -19,7 +19,12 @@ export interface AccountResult { email: string pointsEarned: number runDuration: number + initialPoints: number // Points avant l'exécution + finalPoints: number // Points après l'exécution + desktopPoints: number // Points gagnés sur Desktop + mobilePoints: number // Points gagnés sur Mobile errors?: string[] + banned?: boolean } export interface SummaryData { @@ -41,10 +46,10 @@ export class SummaryReporter { } /** - * Send comprehensive summary via webhook + * Send comprehensive summary via webhook with complete statistics */ async sendWebhookSummary(summary: SummaryData): Promise { - if (!this.config.webhook?.enabled) { + if (!this.config.webhook?.enabled && !this.config.conclusionWebhook?.enabled) { return } @@ -60,40 +65,183 @@ export class SummaryReporter { ? `${minutes}m ${seconds}s` : `${seconds}s` - let description = `╔════════════════════════════════════════╗\n` - description += `║ Daily Run Summary ║\n` - description += `╚════════════════════════════════════════╝\n\n` - description += `⏱️ **Duration:** ${durationText}\n` - description += `💰 **Total Points:** ${summary.totalPoints}\n` - description += `✅ **Success Rate:** ${summary.successCount}/${summary.accounts.length} accounts\n\n` + // Calculate global statistics + const totalDesktop = summary.accounts.reduce((sum, acc) => sum + acc.desktopPoints, 0) + const totalMobile = summary.accounts.reduce((sum, acc) => sum + acc.mobilePoints, 0) + const totalInitial = summary.accounts.reduce((sum, acc) => sum + acc.initialPoints, 0) + const totalFinal = summary.accounts.reduce((sum, acc) => sum + acc.finalPoints, 0) + const bannedCount = summary.accounts.filter(acc => acc.banned).length + + // Build structured embed description + let description = `┌${'─'.repeat(48)}┐\n` + description += `│ ${' '.repeat(10)}📊 EXECUTION SUMMARY${' '.repeat(11)}│\n` + description += `└${'─'.repeat(48)}┘\n\n` + + // Global Overview + description += `**🌐 GLOBAL STATISTICS**\n` + description += `┌${'─'.repeat(48)}┐\n` + description += `│ ⏱️ Duration: \`${durationText}\`${' '.repeat(48 - 14 - durationText.length)}│\n` + description += `│ 💰 Total Earned: **${summary.totalPoints}** points${' '.repeat(48 - 22 - String(summary.totalPoints).length)}│\n` + description += `│ 🖥️ Desktop: **${totalDesktop}** pts | 📱 Mobile: **${totalMobile}** pts${' '.repeat(48 - 28 - String(totalDesktop).length - String(totalMobile).length)}│\n` + description += `│ ✅ Success: ${summary.successCount}/${summary.accounts.length} accounts${' '.repeat(48 - 18 - String(summary.successCount).length - String(summary.accounts.length).length)}│\n` + if (summary.failureCount > 0) { + description += `│ ❌ Failed: ${summary.failureCount} accounts${' '.repeat(48 - 14 - String(summary.failureCount).length)}│\n` + } + if (bannedCount > 0) { + description += `│ 🚫 Banned: ${bannedCount} accounts${' '.repeat(48 - 14 - String(bannedCount).length)}│\n` + } + description += `└${'─'.repeat(48)}┘\n\n` + + // Account Details + description += `**📄 ACCOUNT BREAKDOWN**\n\n` + + const accountsWithErrors: AccountResult[] = [] - // Add individual account results with better formatting - description += `📊 **Detailed Results:**\n` - description += `${'─'.repeat(45)}\n` for (const account of summary.accounts) { - const status = account.errors?.length ? '❌' : '✅' - const emailShort = account.email.length > 25 ? account.email.substring(0, 22) + '...' : account.email - description += `${status} \`${emailShort}\`\n` - description += ` 💎 Points: **${account.pointsEarned}** | ⏱️ Time: ${Math.round(account.runDuration / 1000)}s\n` + const status = account.banned ? '🚫' : (account.errors?.length ? '❌' : '✅') + const emailShort = account.email.length > 30 ? account.email.substring(0, 27) + '...' : account.email + const durationSec = Math.round(account.runDuration / 1000) - if (account.errors?.length) { - description += ` ⚠️ Error: *${account.errors[0]}*\n` + description += `${status} **${emailShort}**\n` + description += `┌${'─'.repeat(46)}┐\n` + + // Points Earned Breakdown + description += `│ 📊 Points Earned: **+${account.pointsEarned}** points${' '.repeat(46 - 23 - String(account.pointsEarned).length)}│\n` + description += `│ └─ Desktop: **${account.desktopPoints}** pts${' '.repeat(46 - 20 - String(account.desktopPoints).length)}│\n` + description += `│ └─ Mobile: **${account.mobilePoints}** pts${' '.repeat(46 - 19 - String(account.mobilePoints).length)}│\n` + description += `├${'─'.repeat(46)}┤\n` + + // Account Total Balance (formula) + description += `│ 💳 Account Total Balance${' '.repeat(23)}│\n` + description += `│ \`${account.initialPoints}\` + \`${account.pointsEarned}\` = **\`${account.finalPoints}\` pts**${' '.repeat(46 - 17 - String(account.initialPoints).length - String(account.pointsEarned).length - String(account.finalPoints).length)}│\n` + description += `│ (Initial + Earned = Final)${' '.repeat(18)}│\n` + description += `├${'─'.repeat(46)}┤\n` + + // Duration + description += `│ ⏱️ Duration: ${durationSec}s${' '.repeat(46 - 13 - String(durationSec).length)}│\n` + + description += `└${'─'.repeat(46)}┘\n\n` + + // Collect accounts with errors for separate webhook + if ((account.errors?.length || account.banned) && account.email) { + accountsWithErrors.push(account) } - description += `\n` } + // Footer summary + description += `┌${'─'.repeat(48)}┐\n` + description += `│ 🌐 TOTAL ACROSS ALL ACCOUNTS${' '.repeat(22)}│\n` + description += `├${'─'.repeat(48)}┤\n` + description += `│ Initial Balance: \`${totalInitial}\` points${' '.repeat(48 - 25 - String(totalInitial).length)}│\n` + description += `│ Final Balance: \`${totalFinal}\` points${' '.repeat(48 - 23 - String(totalFinal).length)}│\n` + description += `│ Total Earned: **+${summary.totalPoints}** points${' '.repeat(48 - 23 - String(summary.totalPoints).length)}│\n` + description += `└${'─'.repeat(48)}┘\n` + + const color = bannedCount > 0 ? 0xFF0000 : summary.failureCount > 0 ? 0xFFAA00 : 0x00FF00 + + // Send main summary webhook await ConclusionWebhook( this.config, - '📊 Daily Run Complete', + '🎉 Daily Rewards Collection Complete', description, undefined, - summary.failureCount > 0 ? 0xFF5555 : 0x00FF00 + color ) + + // Send separate error report if there are accounts with issues + if (accountsWithErrors.length > 0) { + await this.sendErrorReport(accountsWithErrors) + } } catch (error) { log('main', 'SUMMARY', `Failed to send webhook: ${error instanceof Error ? error.message : String(error)}`, 'error') } } + /** + * Send separate webhook for accounts with errors or bans + */ + private async sendErrorReport(accounts: AccountResult[]): Promise { + try { + let errorDescription = `┌${'─'.repeat(48)}┐\n` + errorDescription += `│ ${' '.repeat(10)}⚠️ ERROR REPORT${' '.repeat(16)}│\n` + errorDescription += `└${'─'.repeat(48)}┘\n\n` + + errorDescription += `**${accounts.length} account(s) encountered issues:**\n\n` + + for (const account of accounts) { + const status = account.banned ? '🚫 BANNED' : '❌ ERROR' + const emailShort = account.email.length > 35 ? account.email.substring(0, 32) + '...' : account.email + + errorDescription += `${status} | **${emailShort}**\n` + errorDescription += `┌${'─'.repeat(46)}┐\n` + + // Show what was attempted + errorDescription += `│ 📊 Progress${' '.repeat(35)}│\n` + errorDescription += `│ Desktop: ${account.desktopPoints} pts earned${' '.repeat(46 - 21 - String(account.desktopPoints).length)}│\n` + errorDescription += `│ Mobile: ${account.mobilePoints} pts earned${' '.repeat(46 - 20 - String(account.mobilePoints).length)}│\n` + errorDescription += `│ Total: ${account.pointsEarned} pts${' '.repeat(46 - 13 - String(account.pointsEarned).length)}│\n` + errorDescription += `├${'─'.repeat(46)}┤\n` + + // Error details + if (account.banned) { + errorDescription += `│ 🚫 Status: Account Banned/Suspended${' '.repeat(9)}│\n` + if (account.errors?.length && account.errors[0]) { + errorDescription += `│ 💬 Reason:${' '.repeat(36)}│\n` + const lines = this.wrapText(account.errors[0], 42) + for (const line of lines) { + errorDescription += `│ ${line}${' '.repeat(46 - 3 - line.length)}│\n` + } + } + } else if (account.errors?.length && account.errors[0]) { + errorDescription += `│ ❌ Error Details:${' '.repeat(29)}│\n` + const lines = this.wrapText(account.errors[0], 42) + for (const line of lines) { + errorDescription += `│ ${line}${' '.repeat(46 - 3 - line.length)}│\n` + } + } + + errorDescription += `└${'─'.repeat(46)}┘\n\n` + } + + errorDescription += `**📋 Recommended Actions:**\n` + errorDescription += `• Check account status manually\n` + errorDescription += `• Review error messages above\n` + errorDescription += `• Verify credentials if login failed\n` + errorDescription += `• Consider proxy rotation if rate-limited\n` + + await ConclusionWebhook( + this.config, + '⚠️ Execution Errors & Warnings', + errorDescription, + undefined, + 0xFF0000 // Red color for errors + ) + } catch (error) { + log('main', 'SUMMARY', `Failed to send error report webhook: ${error instanceof Error ? error.message : String(error)}`, 'error') + } + } + + /** + * Wrap text to fit within specified width + */ + private wrapText(text: string, maxWidth: number): string[] { + const words = text.split(' ') + const lines: string[] = [] + let currentLine = '' + + for (const word of words) { + if ((currentLine + word).length > maxWidth) { + if (currentLine) lines.push(currentLine.trim()) + currentLine = word + ' ' + } else { + currentLine += word + ' ' + } + } + + if (currentLine.trim()) lines.push(currentLine.trim()) + return lines + } + /** * Send push notification via Ntfy */ diff --git a/src/index.ts b/src/index.ts index 19cbd2a..de57148 100644 --- a/src/index.ts +++ b/src/index.ts @@ -728,12 +728,17 @@ export class MicrosoftRewardsBot { private async sendConclusion(summaries: AccountSummary[]) { if (summaries.length === 0) return - // Convert AccountSummary to AccountResult format + // Convert AccountSummary to AccountResult format with full statistics const accountResults: AccountResult[] = summaries.map(s => ({ email: s.email, pointsEarned: s.totalCollected, runDuration: s.durationMs, - errors: s.errors.length > 0 ? s.errors : undefined + initialPoints: s.initialTotal, + finalPoints: s.endTotal, + desktopPoints: s.desktopCollected, + mobilePoints: s.mobileCollected, + errors: s.errors.length > 0 ? s.errors : undefined, + banned: s.banned?.status ?? false })) const startTime = new Date(Date.now() - summaries.reduce((sum, s) => sum + s.durationMs, 0)) diff --git a/tests/webhookPreview.test.ts b/tests/webhookPreview.test.ts new file mode 100644 index 0000000..6faeeb4 --- /dev/null +++ b/tests/webhookPreview.test.ts @@ -0,0 +1,221 @@ +/** + * Webhook Preview Test + * + * This test generates a preview of the improved webhook formats: + * 1. Main Summary Webhook (clean, no errors) + * 2. Separate Error Report Webhook (for accounts with issues) + */ + +import { describe, it } from 'node:test' + +describe('Webhook Preview - Improved Format', () => { + it('should display main summary webhook and separate error report', () => { + // Mock data simulating 3 accounts with different outcomes + const accounts = [ + { + email: 'success.account@outlook.com', + pointsEarned: 340, + desktopPoints: 150, + mobilePoints: 190, + initialPoints: 12450, + finalPoints: 12790, + runDuration: 245000, + errors: [], + banned: false + }, + { + email: 'partial.success@hotmail.com', + pointsEarned: 210, + desktopPoints: 150, + mobilePoints: 60, + initialPoints: 8920, + finalPoints: 9130, + runDuration: 198000, + errors: ['Mobile search: Timeout after 3 retries - network instability detected'], + banned: false + }, + { + email: 'banned.account@live.com', + pointsEarned: 0, + desktopPoints: 0, + mobilePoints: 0, + initialPoints: 5430, + finalPoints: 5430, + runDuration: 45000, + errors: ['Account suspended - security check required by Microsoft'], + banned: true + } + ] + + const totalPoints = accounts.reduce((sum, acc) => sum + acc.pointsEarned, 0) + const totalDesktop = accounts.reduce((sum, acc) => sum + acc.desktopPoints, 0) + const totalMobile = accounts.reduce((sum, acc) => sum + acc.mobilePoints, 0) + const totalInitial = accounts.reduce((sum, acc) => sum + acc.initialPoints, 0) + const totalFinal = accounts.reduce((sum, acc) => sum + acc.finalPoints, 0) + const bannedCount = accounts.filter(acc => acc.banned).length + const successCount = accounts.filter(acc => !acc.errors?.length && !acc.banned).length + const failureCount = accounts.length - successCount + const durationText = '8m 8s' + + // ==================== MAIN SUMMARY WEBHOOK ==================== + let mainDescription = `┌${'─'.repeat(48)}┐\n` + mainDescription += `│ ${' '.repeat(10)}📊 EXECUTION SUMMARY${' '.repeat(11)}│\n` + mainDescription += `└${'─'.repeat(48)}┘\n\n` + + // Global Overview + mainDescription += '**🌐 GLOBAL STATISTICS**\n' + mainDescription += `┌${'─'.repeat(48)}┐\n` + mainDescription += `│ ⏱️ Duration: \`${durationText}\`${' '.repeat(48 - 14 - durationText.length)}│\n` + mainDescription += `│ 💰 Total Earned: **${totalPoints}** points${' '.repeat(48 - 22 - String(totalPoints).length)}│\n` + mainDescription += `│ 🖥️ Desktop: **${totalDesktop}** pts | 📱 Mobile: **${totalMobile}** pts${' '.repeat(48 - 28 - String(totalDesktop).length - String(totalMobile).length)}│\n` + mainDescription += `│ ✅ Success: ${successCount}/${accounts.length} accounts${' '.repeat(48 - 18 - String(successCount).length - String(accounts.length).length)}│\n` + mainDescription += `│ ❌ Failed: ${failureCount} accounts${' '.repeat(48 - 14 - String(failureCount).length)}│\n` + mainDescription += `│ 🚫 Banned: ${bannedCount} accounts${' '.repeat(48 - 14 - String(bannedCount).length)}│\n` + mainDescription += `└${'─'.repeat(48)}┘\n\n` + + // Account Details (NO ERRORS - Clean Summary) + mainDescription += '**📄 ACCOUNT BREAKDOWN**\n\n' + + const accountsWithErrors = [] + + for (const account of accounts) { + const status = account.banned ? '🚫' : (account.errors?.length ? '❌' : '✅') + const emailShort = account.email.length > 30 ? account.email.substring(0, 27) + '...' : account.email + const durationSec = Math.round(account.runDuration / 1000) + + mainDescription += `${status} **${emailShort}**\n` + mainDescription += `┌${'─'.repeat(46)}┐\n` + + // Points Earned Breakdown + mainDescription += `│ 📊 Points Earned: **+${account.pointsEarned}** points${' '.repeat(46 - 23 - String(account.pointsEarned).length)}│\n` + mainDescription += `│ └─ Desktop: **${account.desktopPoints}** pts${' '.repeat(46 - 20 - String(account.desktopPoints).length)}│\n` + mainDescription += `│ └─ Mobile: **${account.mobilePoints}** pts${' '.repeat(46 - 19 - String(account.mobilePoints).length)}│\n` + mainDescription += `├${'─'.repeat(46)}┤\n` + + // Account Total Balance (Formula: Initial + Earned = Final) + mainDescription += `│ 💳 Account Total Balance${' '.repeat(23)}│\n` + mainDescription += `│ \`${account.initialPoints}\` + \`${account.pointsEarned}\` = **\`${account.finalPoints}\` pts**${' '.repeat(46 - 17 - String(account.initialPoints).length - String(account.pointsEarned).length - String(account.finalPoints).length)}│\n` + mainDescription += `│ (Initial + Earned = Final)${' '.repeat(18)}│\n` + mainDescription += `├${'─'.repeat(46)}┤\n` + + // Duration + mainDescription += `│ ⏱️ Duration: ${durationSec}s${' '.repeat(46 - 13 - String(durationSec).length)}│\n` + + mainDescription += `└${'─'.repeat(46)}┘\n\n` + + // Collect accounts with errors for separate report + if (account.errors?.length || account.banned) { + accountsWithErrors.push(account) + } + } + + // Footer Summary + mainDescription += `┌${'─'.repeat(48)}┐\n` + mainDescription += `│ 🌐 TOTAL ACROSS ALL ACCOUNTS${' '.repeat(22)}│\n` + mainDescription += `├${'─'.repeat(48)}┤\n` + mainDescription += `│ Initial Balance: \`${totalInitial}\` points${' '.repeat(48 - 25 - String(totalInitial).length)}│\n` + mainDescription += `│ Final Balance: \`${totalFinal}\` points${' '.repeat(48 - 23 - String(totalFinal).length)}│\n` + mainDescription += `│ Total Earned: **+${totalPoints}** points${' '.repeat(48 - 23 - String(totalPoints).length)}│\n` + mainDescription += `└${'─'.repeat(48)}┘\n` + + // ==================== ERROR REPORT WEBHOOK ==================== + let errorDescription = `┌${'─'.repeat(48)}┐\n` + errorDescription += `│ ${' '.repeat(10)}⚠️ ERROR REPORT${' '.repeat(16)}│\n` + errorDescription += `└${'─'.repeat(48)}┘\n\n` + + errorDescription += `**${accountsWithErrors.length} account(s) encountered issues:**\n\n` + + for (const account of accountsWithErrors) { + const status = account.banned ? '🚫 BANNED' : '❌ ERROR' + const emailShort = account.email.length > 35 ? account.email.substring(0, 32) + '...' : account.email + + errorDescription += `${status} | **${emailShort}**\n` + errorDescription += `┌${'─'.repeat(46)}┐\n` + + // Show what was attempted + errorDescription += `│ 📊 Progress${' '.repeat(35)}│\n` + errorDescription += `│ Desktop: ${account.desktopPoints} pts earned${' '.repeat(46 - 21 - String(account.desktopPoints).length)}│\n` + errorDescription += `│ Mobile: ${account.mobilePoints} pts earned${' '.repeat(46 - 20 - String(account.mobilePoints).length)}│\n` + errorDescription += `│ Total: ${account.pointsEarned} pts${' '.repeat(46 - 13 - String(account.pointsEarned).length)}│\n` + errorDescription += `├${'─'.repeat(46)}┤\n` + + // Error details with word wrapping + if (account.banned) { + errorDescription += `│ 🚫 Status: Account Banned/Suspended${' '.repeat(9)}│\n` + if (account.errors?.length && account.errors[0]) { + errorDescription += `│ 💬 Reason:${' '.repeat(36)}│\n` + const errorText = account.errors[0] + const words = errorText.split(' ') + let line = '' + for (const word of words) { + if ((line + word).length > 42) { + errorDescription += `│ ${line.trim()}${' '.repeat(46 - 3 - line.trim().length)}│\n` + line = word + ' ' + } else { + line += word + ' ' + } + } + if (line.trim()) { + errorDescription += `│ ${line.trim()}${' '.repeat(46 - 3 - line.trim().length)}│\n` + } + } + } else if (account.errors?.length && account.errors[0]) { + errorDescription += `│ ❌ Error Details:${' '.repeat(29)}│\n` + const errorText = account.errors[0] + const words = errorText.split(' ') + let line = '' + for (const word of words) { + if ((line + word).length > 42) { + errorDescription += `│ ${line.trim()}${' '.repeat(46 - 3 - line.trim().length)}│\n` + line = word + ' ' + } else { + line += word + ' ' + } + } + if (line.trim()) { + errorDescription += `│ ${line.trim()}${' '.repeat(46 - 3 - line.trim().length)}│\n` + } + } + + errorDescription += `└${'─'.repeat(46)}┘\n\n` + } + + errorDescription += '**📋 Recommended Actions:**\n' + errorDescription += '• Check account status manually\n' + errorDescription += '• Review error messages above\n' + errorDescription += '• Verify credentials if login failed\n' + errorDescription += '• Consider proxy rotation if rate-limited\n' + + // ==================== DISPLAY PREVIEW ==================== + console.log('\n' + '='.repeat(70)) + console.log('📊 WEBHOOK PREVIEW - IMPROVED FORMAT') + console.log('='.repeat(70)) + + console.log('\n✅ WEBHOOK #1 - MAIN SUMMARY (Clean, No Errors)') + console.log('─'.repeat(70)) + console.log('🎯 Title: 🎉 Daily Rewards Collection Complete') + console.log('🎨 Color: Green (all success) / Orange (partial failures) / Red (bans detected)') + console.log('\n📝 Description:') + console.log(mainDescription) + + console.log('='.repeat(70)) + console.log('\n⚠️ WEBHOOK #2 - ERROR REPORT (Separate, Only if Errors Exist)') + console.log('─'.repeat(70)) + console.log('🎯 Title: ⚠️ Execution Errors & Warnings') + console.log('🎨 Color: Red (always)') + console.log('\n📝 Description:') + console.log(errorDescription) + + console.log('='.repeat(70)) + console.log('\n✅ KEY IMPROVEMENTS IMPLEMENTED:') + console.log(' ✓ Errors moved to separate webhook (main summary stays clean)') + console.log(' ✓ Account total shown as formula: `Initial + Earned = Final`') + console.log(' ✓ Complete per-account breakdown: Desktop + Mobile points') + console.log(' ✓ Global totals: Initial balance, Final balance, Total earned') + console.log(' ✓ Individual account totals clearly displayed') + console.log(' ✓ Error details with automatic word wrapping') + console.log(' ✓ Professional box structure throughout') + console.log(' ✓ Recommended actions in error report') + console.log(' ✓ Status indicators: ✅ Success, ❌ Error, 🚫 Banned\n') + }) +})