UNPKG

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

Version:

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

454 lines 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.INLINE_BUTTON_TYPES = exports.CONVERSATION_TYPES = exports.REACTION_TYPES = exports.ACTOR_TYPES = exports.MEDIA_TYPES = exports.MESSAGE_TYPES = void 0; exports.isActor = isActor; exports.isReaction = isReaction; exports.isConversation = isConversation; exports.isInlineButton = isInlineButton; exports.isLocationInfo = isLocationInfo; exports.isContactInfo = isContactInfo; exports.isPoll = isPoll; exports.isInvoiceInfo = isInvoiceInfo; exports.isTelegramChatExport = isTelegramChatExport; // ===================================================== // CORE TYPE CONSTANTS // ===================================================== /** * Message type identifiers used to distinguish between user messages and service events. * * @example * ```typescript * if (item.type === MESSAGE_TYPES.MESSAGE) { * // Handle user message * } else if (item.type === MESSAGE_TYPES.SERVICE) { * // Handle service event * } * ``` */ exports.MESSAGE_TYPES = { /** Regular user-generated message */ MESSAGE: "message", /** System-generated service event */ SERVICE: "service" }; /** * Media type identifiers for different kinds of file attachments. * * These constants correspond to the media_type field in Telegram exports * and help identify the specific type of media content. * * @example * ```typescript * if (mediaMessage.mediaType === MEDIA_TYPES.VIDEO_FILE) { * // Handle video file * } * ``` */ exports.MEDIA_TYPES = { /** Static or animated sticker image */ STICKER: "sticker", /** Animated GIF or short video loop */ ANIMATION: "animation", /** Music or audio file with metadata */ AUDIO_FILE: "audio_file", /** Regular video file with full controls */ VIDEO_FILE: "video_file", /** Circular video message (video note) */ VIDEO_NOTE: "video_message", /** Voice message recording */ VOICE_NOTE: "voice_message", /** Generic document or file attachment */ FILE: "file" }; /** * Actor type identifiers for different kinds of message senders. * * Actors represent who sent a message, from regular users to bots, * channels, and special cases like deleted accounts. * * @example * ```typescript * if (actor.type === 'bot') { * console.log(`Bot message from ${actor.displayName}`) * } * ``` */ exports.ACTOR_TYPES = [ /** Regular Telegram user account */ "user", /** Automated bot account */ "bot", /** Public or private channel */ "channel", /** User posting on behalf of a channel */ "channel_author", /** Account that has been deleted */ "deleted_user" ]; // ===================================================== // INTERACTION TYPES // ===================================================== /** * Reaction type identifiers for message reactions. * * @example * ```typescript * if (reaction.type === 'custom_emoji') { * // Handle custom emoji reaction * } * ``` */ exports.REACTION_TYPES = [ /** Standard Unicode emoji reaction */ "emoji", /** Custom emoji or sticker reaction */ "custom_emoji" ]; // ===================================================== // CONVERSATION TYPES // ===================================================== /** * Conversation type identifiers for different kinds of Telegram chats. * * @example * ```typescript * if (conversation.type === 'public_channel') { * // Handle public channel * } * ``` */ exports.CONVERSATION_TYPES = [ /** One-on-one chat between two users */ "personal_chat", /** Small private group (legacy format) */ "private_group", /** Private supergroup with advanced features */ "private_supergroup", /** Public supergroup anyone can join */ "public_supergroup", /** Public channel anyone can subscribe to */ "public_channel", /** Private channel requiring invite */ "private_channel" ]; // ===================================================== // UI INTERACTION TYPES // ===================================================== /** * Inline button type identifiers for bot interaction buttons. * * These buttons appear below messages and provide various interaction * methods like URLs, callbacks, and payment flows. * * @example * ```typescript * if (button.type === 'url') { * // Handle URL button * window.open(button.url) * } * ``` */ exports.INLINE_BUTTON_TYPES = [ /** Opens a URL when pressed */ "url", /** Sends callback data to the bot */ "callback", /** Switches to inline mode */ "switch_inline", /** Switches to inline query mode */ "switch_inline_query", /** Switches to inline query in current chat */ "switch_inline_query_current_chat", /** Launches a game */ "callback_game", /** Initiates payment flow */ "pay" ]; // ===================================================== // TYPE VALIDATION HELPERS // ===================================================== /** * Helper to safely check if an object has required properties with correct types. */ function hasRequiredProperties(obj, checks) { if (typeof obj !== 'object' || obj === null) { return false; } const target = obj; for (const [key, validator] of Object.entries(checks)) { if (!(key in target) || !validator(target[key])) { return false; } } return true; } /** * Helper to validate arrays with element type checking. */ function isArrayOf(obj, elementValidator) { return Array.isArray(obj) && obj.every(elementValidator); } /** * Helper to validate strings against allowed values. */ function isOneOf(value, allowedValues) { return typeof value === 'string' && allowedValues.includes(value); } // ===================================================== // TYPE GUARD FUNCTIONS // ===================================================== /** * Type guard to validate Actor objects. * * Ensures the object has all required Actor properties with correct types * and validates the actor type against known values. * * @param obj - The value to validate * @returns True if the value is a valid Actor * * @example * ```typescript * if (isActor(data)) { * // TypeScript now knows data is Actor * console.log(`Actor: ${data.displayName} (${data.type})`) * } * ``` */ function isActor(obj) { return hasRequiredProperties(obj, { type: (val) => isOneOf(val, exports.ACTOR_TYPES), displayName: (val) => typeof val === 'string', id: (val) => val === undefined || typeof val === 'number', username: (val) => val === undefined || typeof val === 'string', authoredBy: (val) => val === undefined || typeof val === 'string' }); } /** * Type guard to validate Reaction objects. * * Validates reaction structure including emoji, count, and recent users array. * Also performs deep validation of the recent users array structure. * * @param obj - The value to validate * @returns True if the value is a valid Reaction * * @example * ```typescript * if (isReaction(data)) { * console.log(`${data.emoji}: ${data.count} reactions`) * } * ``` */ function isReaction(obj) { if (!hasRequiredProperties(obj, { emoji: (val) => typeof val === 'string', documentURL: (val) => true, // ExportedFile can be anything count: (val) => typeof val === 'number' && val >= 0 })) { return false; } const reaction = obj; // Validate recent array structure return Array.isArray(reaction.recent) && reaction.recent.every(item => typeof item === 'object' && item !== null && isActor(item.sender) && item.date instanceof Date); } /** * Type guard to validate Conversation objects. * * Ensures the conversation has a valid ID and type from the known conversation types. * * @param obj - The value to validate * @returns True if the value is a valid Conversation * * @example * ```typescript * if (isConversation(data)) { * console.log(`Chat: ${data.name} (${data.type})`) * } * ``` */ function isConversation(obj) { return hasRequiredProperties(obj, { id: (val) => typeof val === 'number', type: (val) => isOneOf(val, exports.CONVERSATION_TYPES), name: (val) => val === undefined || typeof val === 'string' }); } /** * Type guard to validate InlineButton objects. * * Validates button type and text, with optional validation of type-specific * properties like URLs and callback data. * * @param obj - The value to validate * @returns True if the value is a valid InlineButton * * @example * ```typescript * if (isInlineButton(data)) { * if (data.type === 'url' && data.url) { * console.log(`URL Button: ${data.text} -> ${data.url}`) * } * } * ``` */ function isInlineButton(obj) { return hasRequiredProperties(obj, { type: (val) => isOneOf(val, exports.INLINE_BUTTON_TYPES), text: (val) => typeof val === 'string' && val.length > 0 }) && // Optional properties don't need validation beyond type checking typeof obj === 'object' && obj !== null; } /** * Type guard to validate LocationInfo objects. * * Ensures latitude and longitude are valid numbers within expected ranges. * * @param obj - The value to validate * @returns True if the value is valid LocationInfo * * @example * ```typescript * if (isLocationInfo(data)) { * console.log(`Location: ${data.latitude}, ${data.longitude}`) * } * ``` */ function isLocationInfo(obj) { return hasRequiredProperties(obj, { latitude: (val) => typeof val === 'number' && val >= -90 && val <= 90, longitude: (val) => typeof val === 'number' && val >= -180 && val <= 180 }); } /** * Type guard to validate ContactInfo objects. * * Ensures all name and phone number fields are present and non-empty strings. * * @param obj - The value to validate * @returns True if the value is valid ContactInfo * * @example * ```typescript * if (isContactInfo(data)) { * const fullName = `${data.first_name} ${data.last_name}`.trim() * console.log(`Contact: ${fullName} (${data.phone_number})`) * } * ``` */ function isContactInfo(obj) { return hasRequiredProperties(obj, { first_name: (val) => typeof val === 'string' && val.length > 0, last_name: (val) => typeof val === 'string' && val.length > 0, phone_number: (val) => typeof val === 'string' && val.length > 0 }); } /** * Type guard to validate PollAnswer objects. * * Helper function used by isPoll to validate individual poll answers. * * @param obj - The value to validate * @returns True if the value is a valid PollAnswer */ function isPollAnswer(obj) { return hasRequiredProperties(obj, { text: (val) => typeof val === 'string' && val.length > 0, voters: (val) => typeof val === 'number' && val >= 0, chosen: (val) => typeof val === 'boolean' }); } /** * Type guard to validate Poll objects. * * Validates poll structure including question, state, and answers array. * Performs deep validation of all poll answers. * * @param obj - The value to validate * @returns True if the value is a valid Poll * * @example * ```typescript * if (isPoll(data)) { * const totalVotes = data.answers.reduce((sum, answer) => sum + answer.voters, 0) * console.log(`Poll: ${data.question} (${totalVotes} votes)`) * } * ``` */ function isPoll(obj) { if (!hasRequiredProperties(obj, { question: (val) => typeof val === 'string' && val.length > 0, closed: (val) => typeof val === 'boolean', total_voters: (val) => typeof val === 'number' && val >= 0 })) { return false; } const poll = obj; return isArrayOf(poll.answers, isPollAnswer); } /** * Type guard to validate InvoiceInfo objects. * * Validates payment invoice data including title, description, currency, and amount. * Ensures amount is a positive number representing the price. * * @param obj - The value to validate * @returns True if the value is valid InvoiceInfo * * @example * ```typescript * if (isInvoiceInfo(data)) { * console.log(`Invoice: ${data.title} - ${data.amount} ${data.currency}`) * } * ``` */ function isInvoiceInfo(obj) { return hasRequiredProperties(obj, { title: (val) => typeof val === 'string' && val.length > 0, description: (val) => typeof val === 'string', currency: (val) => typeof val === 'string' && val.length > 0, amount: (val) => typeof val === 'number' && val > 0 }); } /** * Type guard to validate TelegramChatExport objects. * * Validates the complete export structure including conversation data, * participants map, messages array, and metadata. * * @param obj - The value to validate * @returns True if the value is a valid TelegramChatExport * * @example * ```typescript * if (isTelegramChatExport(data)) { * console.log(`Export: ${data.totalMessages} messages from ${data.conversation.name}`) * } * ``` */ function isTelegramChatExport(obj) { if (!hasRequiredProperties(obj, { conversation: isConversation, totalMessages: (val) => typeof val === 'number' && val >= 0 })) { return false; } const exportData = obj; // Validate participants map if (!(exportData.participants instanceof Map)) { return false; } // Validate messages array (basic check - full message validation would be expensive) if (!Array.isArray(exportData.messages)) { return false; } // Validate date range return typeof exportData.dateRange === 'object' && exportData.dateRange !== null && exportData.dateRange.earliest instanceof Date && exportData.dateRange.latest instanceof Date; } //# sourceMappingURL=shared.js.map