@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
JavaScript
;
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