@stack.thefennec.dev/telegram-export-parser
Version:
TypeScript library for parsing Telegram Desktop's data export with full type safety
456 lines • 17 kB
TypeScript
/**
* @fileoverview Base parsing utilities for Telegram export message processing.
*
* This module provides the foundational parsing functions that implement a **mixin-based architecture**
* for building complex message types from simple, composable components. The design allows for
* flexible message construction by combining base message properties with specialized mixins.
*
* ## Mixin Architecture Overview
*
* The parser uses a sophisticated mixin system where different message aspects are parsed
* separately and then combined to create complete message objects:
*
* ```
* BaseMessage (core properties)
* ├── + MediaWithDimensions (width, height)
* ├── + MediaWithDuration (audio/video length)
* ├── + MediaWithThumbnail (preview images)
* ├── + SelfDestructibleMessage (disappearing messages)
* └── = Complete Message Type (e.g., VideoMessage)
* ```
*
* ## Message Composition Examples
*
* **Video Message Construction:**
* ```typescript
* // Start with base message properties
* const base = parseBaseMessage(rawData)
* const mediaBase = parseBaseMediaMessage(rawData, base)
*
* // Add video-specific mixins
* const dimensions = parseMediaWithDimensions(rawData)
* const duration = parseMediaWithDuration(rawData)
* const thumbnail = parseMediaWithThumbnail(rawData)
*
* // Combine into complete VideoMessage
* const videoMessage = { ...mediaBase, ...dimensions, ...duration, ...thumbnail }
* ```
*
* **Photo Message Construction:**
* ```typescript
* const base = parseBaseMessage(rawData)
* const dimensions = parseMediaWithDimensions(rawData)
* const selfDestruct = parseSelfDestructibleMessage(rawData)
*
* // Photos use different structure than media messages
* const photoMessage = { ...base, ...dimensions, ...selfDestruct, photoURL, photoFileSize }
* ```
*
* This approach provides maximum flexibility while maintaining type safety and code reuse.
*/
import type { Actor, BaseEvent, BaseMediaMessage, BaseMessage, ExportedFile, InvoiceInfo, MediaWithDimensions, MediaWithDuration, MediaWithThumbnail, RawReaction, RawTelegramMessage, Reaction, SelfDestructibleMessage } from '../types';
/**
* Transforms Telegram date formats into standardized Date objects.
*
* Telegram exports contain dates in multiple formats for accuracy and compatibility.
* This function prioritizes Unix timestamps (more precise) over string dates.
*
* @param date - Human-readable date string (fallback format)
* @param dateUnixtime - Unix timestamp string (preferred for accuracy)
* @returns Standardized Date object
*
* @example
* ```typescript
* // Prefer Unix timestamp when available
* const precise = parseDate('2023-12-01 15:30:00', '1701432600')
*
* // Fallback to string parsing
* const fallback = parseDate('2023-12-01 15:30:00')
* ```
*/
export declare const parseDate: (date: string, dateUnixtime?: string) => Date;
/**
* Parses message edit timestamps with proper null handling.
*
* Edit timestamps are only present when a message has been modified.
* This function maintains the distinction between "never edited" (undefined)
* and "edited at a specific time" (Date object).
*
* @param editedUnixtime - Unix timestamp when message was last edited
* @returns Date if message was edited, undefined if never edited
*
* @example
* ```typescript
* const editTime = parseEditedDate(1701432600) // Date object
* const neverEdited = parseEditedDate(undefined) // undefined
*
* if (editTime) {
* console.log(`Message edited at ${editTime.toISOString()}`)
* }
* ```
*/
export declare const parseEditedDate: (editedUnixtime?: number) => Date | undefined;
/**
* Transforms member name arrays into structured Actor objects.
*
* Used for service events like group member additions/removals where
* only display names are available. Leverages actor parsing to create
* proper Actor objects with type detection.
*
* @param members - Array of member display names
* @returns Array of successfully parsed Actor objects
*
* @example
* ```typescript
* const memberNames = ['John Doe', 'Jane Smith', 'Helper Bot']
* const actors = parseMembers(memberNames)
* // Returns Actor objects with detected types (user, bot, etc.)
* ```
*/
export declare const parseMembers: (members?: string[]) => Actor[];
/**
* Transforms file paths into standardized ExportedFile references.
*
* Telegram exports handle files in three distinct states, each requiring
* different representation in the parsed output for proper application handling.
*
* **File States:**
* - **Downloaded**: File exists locally → URL object for file access
* - **Not Included**: File excluded from export → null (explicit absence)
* - **No File**: No file was attached → undefined (no file field)
*
* @param filePath - Raw file path string from export
* @param basePath - Base directory for resolving relative paths
* @returns ExportedFile with appropriate state representation
*
* @example
* ```typescript
* // Downloaded file
* parseExportedFile('photos/image.jpg') // file:///path/to/export/photos/image.jpg
*
* // File not included in export
* parseExportedFile('File not included, change data exporting settings to download.') // null
*
* // No file attached
* parseExportedFile(undefined) // undefined
* ```
*/
export declare const parseExportedFile: (filePath: string | undefined, basePath?: string) => ExportedFile;
/**
* Transforms raw reaction data into structured objects with actor resolution.
*
* Reactions contain emoji identifiers, usage counts, and recent user activity.
* This function resolves user references and handles both standard and custom emoji.
*
* @param raw - Raw reaction data from Telegram export
* @returns Complete Reaction object with resolved user references
*
* @example
* ```typescript
* const reaction = parseReaction({
* emoji: '👍',
* count: 5,
* recent: [
* { from: 'John', from_id: '123', date: '2023-12-01' },
* { from: 'Jane', from_id: '456', date: '2023-12-01' }
* ]
* })
*
* console.log(`${reaction.emoji}: ${reaction.count} reactions`)
* reaction.recent.forEach(r => console.log(`${r.sender.displayName} reacted`))
* ```
*/
export declare const parseReaction: (raw: RawReaction) => Reaction;
/**
* Parses invoice data with format flexibility and error resilience.
*
* Invoice information can appear as JSON strings or pre-parsed objects.
* This function handles both formats with graceful degradation for malformed data.
*
* @param invoiceStr - Invoice data in various formats
* @returns Parsed InvoiceInfo or undefined if no valid invoice data
*
* @example
* ```typescript
* // JSON string format
* const invoice1 = parseInvoiceInfo('{"title":"Premium","amount":999,"currency":"USD"}')
*
* // Object format
* const invoice2 = parseInvoiceInfo({ title: 'Service', amount: 1500, currency: 'EUR' })
*
* // Invalid data - graceful fallback
* const invoice3 = parseInvoiceInfo('invalid json')
* // Returns fallback invoice with description set to original string
* ```
*/
export declare const parseInvoiceInfo: (invoiceStr: string | InvoiceInfo | undefined) => InvoiceInfo | undefined;
/**
* **Core Foundation Parser** - Constructs BaseMessage with all universal properties.
*
* This is the **primary building block** for all user messages in the mixin architecture.
* It handles the complex parsing of actors, text entities, reactions, and metadata
* that every message type shares, regardless of content.
*
* **Parsed Components:**
* - **Identity**: Message ID, type, timestamps
* - **Actors**: Sender, forwarded/saved sources, via bots
* - **Content**: Text entities with rich formatting
* - **Interactions**: Reactions, replies, inline buttons
* - **Metadata**: Edit history, forwarding chains
*
* **Mixin Usage Pattern:**
* ```typescript
* // Start with base properties
* const base = parseBaseMessage(rawData)
*
* // Add type-specific mixins
* const withMedia = parseBaseMediaMessage(rawData, base)
* const withDimensions = parseMediaWithDimensions(rawData)
*
* // Combine into complete message type
* const videoMessage: VideoMessage = { ...withMedia, ...withDimensions, mediaType: 'video_file' }
* ```
*
* @param raw - Complete raw message data from Telegram export
* @returns BaseMessage with all fundamental properties parsed and actors resolved
*
* @example
* ```typescript
* const base = parseBaseMessage({
* id: 12345,
* from: 'John Doe',
* from_id: 'user987654321',
* date: '2023-12-01 15:30:00',
* text_entities: [{ type: 'plain', text: 'Hello world!' }],
* reactions: [{ emoji: '👍', count: 5 }]
* })
*
* console.log(`Message ${base.id} from ${base.sender.displayName}`)
* console.log(`Reactions: ${base.reactions.length}`)
* ```
*/
export declare const parseBaseMessage: (raw: RawTelegramMessage) => BaseMessage;
/**
* **Service Event Foundation Parser** - Constructs BaseEvent for system messages.
*
* Service events represent system-generated messages (calls, joins, pins, etc.)
* rather than user content. This parser creates the foundation for all event types
* with appropriate actor resolution and minimal required properties.
*
* **Key Differences from BaseMessage:**
* - Uses service actor parsing (actor/actor_id fields)
* - Simpler property set (no replies, reactions, etc.)
* - Different timestamp handling for service events
* - Focuses on action performer rather than message sender
*
* @param raw - Raw service message data from Telegram export
* @returns BaseEvent with core service message properties
*
* @example
* ```typescript
* const event = parseBaseEvent({
* id: 12346,
* actor: 'Admin User',
* actor_id: 'user123456789',
* action: 'invite_members',
* date: '2023-12-01 15:45:00'
* })
*
* console.log(`${event.actor.displayName} performed action at ${event.date}`)
* ```
*/
export declare const parseBaseEvent: (raw: RawTelegramMessage) => BaseEvent;
/**
* **Media Foundation Mixin** - Extends BaseMessage with file attachment properties.
*
* This mixin transforms a BaseMessage into a BaseMediaMessage by adding file-related
* properties. Used as the foundation for all message types that contain downloadable files
* (videos, audio, documents, stickers, animations).
*
* **Added Properties:**
* - **fileURL**: Parsed file reference for access
* - **fileName**: Original filename from upload
* - **fileSize**: File size in bytes
* - **mimeType**: Content type for proper handling
*
* **Mixin Combination Pattern:**
* ```typescript
* const base = parseBaseMessage(raw) // Core message properties
* const media = parseBaseMediaMessage(raw, base) // + File properties
* const duration = parseMediaWithDuration(raw) // + Duration for video/audio
*
* const videoMessage = { ...media, ...duration, mediaType: 'video_file' }
* ```
*
* @param raw - Raw message data containing file information
* @param base - BaseMessage to extend with media properties
* @returns BaseMediaMessage combining message and file properties
*
* @example
* ```typescript
* const baseMsg = parseBaseMessage(rawData)
* const mediaMsg = parseBaseMediaMessage(rawData, baseMsg)
*
* if (mediaMsg.fileURL) {
* console.log(`File: ${mediaMsg.fileName} (${mediaMsg.fileSize} bytes)`)
* // Access file at mediaMsg.fileURL
* }
* ```
*/
export declare const parseBaseMediaMessage: (raw: RawTelegramMessage, base: BaseMessage) => BaseMediaMessage;
/**
* **Visual Dimensions Mixin** - Adds width/height properties for visual media.
*
* This mixin extracts dimensional information for media that has measurable size.
* Essential for proper display scaling, aspect ratio calculation, and UI layout.
*
* **Compatible Message Types:**
* - PhotoMessage (image dimensions)
* - VideoMessage (video resolution)
* - AnimationMessage (GIF/animation size)
* - StickerMessage (sticker dimensions)
* - DocumentMessage (when document is an image)
*
* **Mixin Application:**
* ```typescript
* // For photos (direct on BaseMessage)
* const photo = { ...parseBaseMessage(raw), ...parseMediaWithDimensions(raw) }
*
* // For videos (combined with media base)
* const video = {
* ...parseBaseMediaMessage(raw, base),
* ...parseMediaWithDimensions(raw),
* ...parseMediaWithDuration(raw)
* }
* ```
*
* @param raw - Raw message data with width/height fields
* @returns MediaWithDimensions containing visual size properties
*
* @example
* ```typescript
* const dimensions = parseMediaWithDimensions(raw)
*
* if (dimensions.width && dimensions.height) {
* const aspectRatio = dimensions.width / dimensions.height
* console.log(`Media: ${dimensions.width}x${dimensions.height} (${aspectRatio.toFixed(2)}:1)`)
* }
* ```
*/
export declare const parseMediaWithDimensions: (raw: RawTelegramMessage) => MediaWithDimensions;
/**
* **Thumbnail Preview Mixin** - Adds thumbnail properties for media with preview images.
*
* Many media types include small preview images for efficient loading and display
* before the full media is accessed. This mixin handles thumbnail file references
* and metadata.
*
* **Compatible Message Types:**
* - VideoMessage (video preview frame)
* - AnimationMessage (first frame preview)
* - DocumentMessage (document preview)
* - MusicMessage (album art thumbnail)
* - VideoNoteMessage (circular video preview)
*
* **Performance Benefits:**
* - Quick preview loading for better UX
* - Reduced bandwidth for media galleries
* - Fallback display when full media unavailable
*
* @param raw - Raw message data with thumbnail fields
* @returns MediaWithThumbnail containing preview image properties
*
* @example
* ```typescript
* const thumbnail = parseMediaWithThumbnail(raw)
*
* // Show thumbnail while loading full video
* if (thumbnail.thumbnailURL) {
* console.log(`Thumbnail: ${thumbnail.thumbnailURL}`)
* console.log(`Thumbnail size: ${thumbnail.thumbnailFileSize} bytes`)
* }
* ```
*/
export declare const parseMediaWithThumbnail: (raw: RawTelegramMessage) => MediaWithThumbnail;
/**
* **Duration Timing Mixin** - Adds playback duration for time-based media.
*
* Time-based media (audio, video, voice notes) require duration information
* for proper playback controls, progress bars, and user interface elements.
* This mixin extracts and standardizes duration data.
*
* **Compatible Message Types:**
* - VideoMessage (video length)
* - VideoNoteMessage (video note duration)
* - MusicMessage (song/track length)
* - VoiceNoteMessage (voice recording length)
* - AnimationMessage (GIF/animation duration)
* - StickerMessage (animated sticker length)
*
* **Duration Applications:**
* - Media player progress bars
* - Playback time displays
* - Auto-advance in playlists
* - Media filtering by length
*
* @param raw - Raw message data with duration_seconds field
* @returns MediaWithDuration containing playback length information
*
* @example
* ```typescript
* const duration = parseMediaWithDuration(raw)
*
* const minutes = Math.floor(duration.durationSeconds / 60)
* const seconds = duration.durationSeconds % 60
* console.log(`Duration: ${minutes}:${seconds.toString().padStart(2, '0')}`)
*
* // Use for media controls
* if (duration.durationSeconds > 0) {
* setupProgressBar(duration.durationSeconds)
* }
* ```
*/
export declare const parseMediaWithDuration: (raw: RawTelegramMessage) => MediaWithDuration;
/**
* **Self-Destruct Timer Mixin** - Adds disappearing message functionality.
*
* Some messages can be configured to automatically delete after being viewed,
* providing ephemeral messaging capabilities. This mixin handles the timer
* configuration for such messages.
*
* **Compatible Message Types:**
* - PhotoMessage (disappearing photos)
* - DocumentMessage (disappearing files)
* - VideoMessage (disappearing videos - rare)
*
* **Privacy & Security Features:**
* - Automatic deletion after viewing
* - Configurable timer duration
* - Enhanced privacy for sensitive content
* - No permanent storage requirement
*
* **Timer Behavior:**
* - undefined: Permanent message (never disappears)
* - number: Seconds until auto-deletion after first view
*
* @param raw - Raw message data with self_destruct_period_seconds field
* @returns SelfDestructibleMessage with timer configuration
*
* @example
* ```typescript
* const selfDestruct = parseSelfDestructibleMessage(raw)
*
* if (selfDestruct.selfDestructPeriodSeconds) {
* console.log(`⏰ Disappears in ${selfDestruct.selfDestructPeriodSeconds} seconds`)
*
* // Set up auto-deletion timer
* setTimeout(() => {
* deleteMessage(messageId)
* }, selfDestruct.selfDestructPeriodSeconds * 1000)
* } else {
* console.log('📌 Permanent message')
* }
* ```
*/
export declare const parseSelfDestructibleMessage: (raw: RawTelegramMessage) => SelfDestructibleMessage;
//# sourceMappingURL=base.d.ts.map