UNPKG

@stack.thefennec.dev/telegram-export-parser

Version:

TypeScript library for parsing Telegram Desktop's data export with full type safety

228 lines • 8.59 kB
#!/usr/bin/env node "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.main = main; exports.parseCliArgs = parseCliArgs; exports.formatMessagePreview = formatMessagePreview; exports.displayStatistics = displayStatistics; const main_1 = require("./processors/main"); /** * Parses command line arguments into structured options object. * * Handles various argument formats and provides sensible defaults. * Validates file path argument is present and accessible. * * @param args - Raw command line arguments from process.argv * @returns Parsed and validated CLI options * @throws {Error} When required file path argument is missing * * @example * ```typescript * const options = parseCliArgs(['node', 'cli.js', 'export.json', '--verbose']) * // Returns: { filePath: 'export.json', verbose: true, statsOnly: false, limit: 0 } * ``` */ function parseCliArgs(args) { const filePath = args[2]; if (!filePath) { console.error('Usage: npm run parse-export <file.json> [--verbose] [--stats-only] [--limit=N]'); console.error(''); console.error('Options:'); console.error(' --verbose Show detailed message information'); console.error(' --stats-only Show only statistics, skip message processing'); console.error(' --limit=N Process only first N messages (default: all)'); process.exit(1); } return { filePath, verbose: args.includes('--verbose'), statsOnly: args.includes('--stats-only'), limit: parseInt(args.find(arg => arg.startsWith('--limit='))?.split('=')[1] || '0', 10) }; } /** * Formats message content for console display with length constraints. * * Truncates long text content and handles various message types gracefully. * Provides consistent formatting for different message formats. * * @param message - Telegram message or event to format * @param maxLength - Maximum length for text content (default: 100) * @returns Formatted string representation of message content * * @example * ```typescript * const formatted = formatMessagePreview(textMessage, 50) * // Returns: "Hello world! This is a long message that..." * ``` */ function formatMessagePreview(message, maxLength = 100) { if ('textEntities' in message && message.textEntities.length > 0) { const text = message.textEntities.map(entity => entity.text).join(''); return text.length > maxLength ? `${text.substring(0, maxLength)}...` : text; } if ('action' in message) { return `[${message.action}]`; } return '[No text content]'; } /** * Processes and displays statistics for the Telegram export. * * Analyzes the entire export to provide comprehensive statistics including * message counts, date ranges, participant information, and message type * distribution. * * @param processor - Initialized TelegramExportProcessor instance * * @example * ```typescript * const processor = TelegramExportProcessor.fromFile('export.json') * displayStatistics(processor) * // Outputs comprehensive chat statistics to console * ``` */ function displayStatistics(processor) { console.log('šŸ“Š EXPORT STATISTICS'); console.log('===================='); console.log(`šŸ’¬ Chat: ${processor.conversation.name}`); console.log(`šŸ” Type: ${processor.conversation.type}`); console.log(`šŸ†” ID: ${processor.conversation.id}`); console.log(`šŸ“ Total Messages: ${processor.totalMessages}`); // Date range analysis const dateRange = processor.getDateRange(); console.log(`šŸ“… Date Range: ${dateRange.earliest.toLocaleDateString()} - ${dateRange.latest.toLocaleDateString()}`); // Actor analysis const actors = processor.extractAllActors(); console.log(`šŸ‘„ Unique Participants: ${actors.size}`); // Message type analysis const messageTypes = new Map(); const eventTypes = new Map(); let messageCount = 0; let eventCount = 0; for (const item of processor.messages()) { if ('action' in item) { eventCount++; const action = item.action; eventTypes.set(action, (eventTypes.get(action) || 0) + 1); } else { messageCount++; // Determine message type from properties let type = 'text'; if ('mediaType' in item) type = item.mediaType; else if ('photoURL' in item) type = 'photo'; else if ('pollQuestion' in item) type = 'poll'; messageTypes.set(type, (messageTypes.get(type) || 0) + 1); } } console.log(`šŸ’¬ User Messages: ${messageCount}`); console.log(`⚔ Service Events: ${eventCount}`); // Top participants if (actors.size > 0) { console.log('\nšŸ‘„ PARTICIPANTS'); console.log('================'); Array.from(actors.values()) .slice(0, 10) .forEach(actor => { console.log(`• ${actor.displayName} (ID: ${actor.id})`); }); } // Message types breakdown if (messageTypes.size > 0) { console.log('\nšŸ“ MESSAGE TYPES'); console.log('================'); Array.from(messageTypes.entries()) .sort(([, a], [, b]) => b - a) .forEach(([type, count]) => { console.log(`• ${type}: ${count}`); }); } // Event types breakdown if (eventTypes.size > 0) { console.log('\n⚔ SERVICE EVENTS'); console.log('================='); Array.from(eventTypes.entries()) .sort(([, a], [, b]) => b - a) .forEach(([type, count]) => { console.log(`• ${type}: ${count}`); }); } } /** * Main CLI application entry point. * * Orchestrates the entire parsing and analysis process based on command line * options. Handles file loading, processing, and output formatting with * comprehensive error handling and progress indication. * * @param args - Command line arguments from process.argv * @throws {Error} Various errors related to file access, parsing, or processing * * @example * ```typescript * // Called automatically when script is executed * main(process.argv) * ``` */ function main(args) { try { const options = parseCliArgs(args); console.log(`šŸš€ Processing Telegram export: ${options.filePath}`); console.log(''); // Load and initialize processor const processor = main_1.TelegramExportProcessor.fromFile(options.filePath); // Always show basic statistics displayStatistics(processor); // Skip message processing if stats-only mode if (options.statsOnly) { console.log('\nāœ… Statistics complete (--stats-only mode)'); return; } // Process messages with optional limit console.log('\nšŸ“‹ MESSAGE PROCESSING'); console.log('====================='); let processedCount = 0; let errorCount = 0; for (const message of processor.messages()) { if (options.limit > 0 && processedCount >= options.limit) { break; } try { processedCount++; if (options.verbose) { const timestamp = ('sentAt' in message ? message.sentAt : message.date).toISOString(); const sender = 'sender' in message ? message.sender.displayName : message.actor?.displayName; const preview = formatMessagePreview(message); console.log(`[${processedCount}] ${timestamp} - ${sender}: ${preview}`); } else if (processedCount % 1000 === 0) { console.log(`Processed ${processedCount} messages...`); } } catch (error) { errorCount++; console.warn(`āš ļø Error processing message ${processedCount}:`, error); } } console.log(''); console.log('āœ… PROCESSING COMPLETE'); console.log('======================'); console.log(`šŸ“ Messages processed: ${processedCount}`); if (errorCount > 0) { console.log(`āš ļø Errors encountered: ${errorCount}`); } } catch (error) { console.error('āŒ Fatal error:', error); process.exit(1); } } // Execute main function if script is run directly if (require.main === module) { main(process.argv); } //# sourceMappingURL=cli.js.map