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