Skip to main content

rustigram_types/
message.rs

1use serde::{Deserialize, Serialize};
2
3use crate::chat::{Chat, Location, Venue};
4use crate::file::{Animation, Audio, Document, PhotoSize, Video, VideoNote, Voice};
5use crate::keyboard::InlineKeyboardMarkup;
6use crate::poll::Poll;
7use crate::sticker::Sticker;
8use crate::user::User;
9
10/// A Telegram message.
11///
12/// Only fields that were actually sent will be `Some`. Consult the
13/// official Bot API documentation for field availability rules.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Message {
16    /// Unique message identifier inside this chat.
17    pub message_id: i64,
18
19    /// Optional — unique identifier of a message thread to which the message belongs.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub message_thread_id: Option<i64>,
22
23    /// Sender of the message, empty for messages sent to channels.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub from: Option<User>,
26
27    /// Sender of the message; empty for messages sent to channels or on behalf of a chat.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub sender_chat: Option<Chat>,
30
31    /// For supergroup messages — boost count of the sender.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub sender_boost_count: Option<u32>,
34
35    /// The bot that actually sent the message on behalf of the business account.
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub sender_business_bot: Option<User>,
38
39    /// Date the message was sent , as a Unix timestamp.
40    pub date: i64,
41
42    /// Unique identifier of the business connection from which the message was received.
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub business_connection_id: Option<String>,
45
46    /// Conversation the message belongs to.
47    pub chat: Chat,
48
49    /// Information about the original message for forwarded messages.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub forward_origin: Option<MessageOrigin>,
52
53    /// `true` if the message is sent to a forum topic.
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub is_topic_message: Option<bool>,
56
57    /// `true` if the message is a channel post automatically forwarded to the
58    /// connected discussion group.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub is_automatic_forward: Option<bool>,
61
62    /// For replies in the same chat and message thread, the original message.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub reply_to_message: Option<Box<Message>>,
65
66    /// Information about the message that is being replied to by the current message.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub external_reply: Option<ExternalReplyInfo>,
69
70    /// For replies that quote part of the original message, the quoted part.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub quote: Option<TextQuote>,
73
74    /// For replies to a story, the original story.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub reply_to_story: Option<serde_json::Value>,
77
78    /// Bot through which the message was sent.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub via_bot: Option<User>,
81
82    /// Date the message was last edited, as a Unix timestamp.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub edit_date: Option<i64>,
85
86    /// `true` if the message can't be forwarded.
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub has_protected_content: Option<bool>,
89
90    /// `true` if the message was sent by an implicit action.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub is_from_offline: Option<bool>,
93
94    /// The unique identifier of a media message group this message belongs to.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub media_group_id: Option<String>,
97
98    /// Signature of the post author for messages in channels.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub author_signature: Option<String>,
101
102    /// Actual UTF-8 text of the message (0–4096 characters).
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub text: Option<String>,
105
106    /// Special entities like usernames, URLs, bot commands, etc.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub entities: Option<Vec<MessageEntity>>,
109
110    /// Options used for link preview generation.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub link_preview_options: Option<LinkPreviewOptions>,
113
114    /// Unique identifier of the message effect added to the message.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub effect_id: Option<String>,
117
118    // Media fields
119    /// Animation attached to the message.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub animation: Option<Animation>,
122    /// Audio file attached to the message.
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub audio: Option<Audio>,
125    /// Document attached to the message.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub document: Option<Document>,
128    /// Paid media attached to the message.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub paid_media: Option<serde_json::Value>,
131    /// Photo attached to the message (array of sizes).
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub photo: Option<Vec<PhotoSize>>,
134    /// Sticker attached to the message.
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub sticker: Option<Sticker>,
137    /// Story attached to the message.
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub story: Option<serde_json::Value>,
140    /// Video attached to the message.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub video: Option<Video>,
143    /// Video note attached to the message.
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub video_note: Option<VideoNote>,
146    /// Voice note attached to the message.
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub voice: Option<Voice>,
149
150    /// Caption for the animation, audio, document, paid media, photo, video or voice.
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub caption: Option<String>,
153
154    /// Special entities in the caption.
155    #[serde(skip_serializing_if = "Option::is_none")]
156    pub caption_entities: Option<Vec<MessageEntity>>,
157
158    /// `true` if the caption must be shown above the message media.
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub show_caption_above_media: Option<bool>,
161
162    /// `true` if the message media is covered by a spoiler animation.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub has_media_spoiler: Option<bool>,
165
166    // Service message types
167    /// Contact shared in the message.
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub contact: Option<Contact>,
170    /// Dice result in the message.
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub dice: Option<Dice>,
173    /// Game in the message.
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub game: Option<serde_json::Value>,
176    /// Poll in the message.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub poll: Option<Poll>,
179    /// Venue in the message.
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub venue: Option<Venue>,
182    /// Location in the message.
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub location: Option<Location>,
185
186    // Group events
187    /// New members that joined the group.
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub new_chat_members: Option<Vec<User>>,
190    /// A member that left the group.
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub left_chat_member: Option<User>,
193    /// New chat title (service message).
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub new_chat_title: Option<String>,
196    /// New chat photo (service message).
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub new_chat_photo: Option<Vec<PhotoSize>>,
199    /// `true` if the chat photo was deleted (service message).
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub delete_chat_photo: Option<bool>,
202    /// `true` if the group was created (service message).
203    #[serde(skip_serializing_if = "Option::is_none")]
204    pub group_chat_created: Option<bool>,
205    /// `true` if the supergroup was created (service message).
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub supergroup_chat_created: Option<bool>,
208    /// `true` if the channel was created (service message).
209    #[serde(skip_serializing_if = "Option::is_none")]
210    pub channel_chat_created: Option<bool>,
211    /// Auto-delete timer changed (service message).
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub message_auto_delete_timer_changed: Option<serde_json::Value>,
214    /// The group has been migrated to a supergroup with this ID.
215    #[serde(skip_serializing_if = "Option::is_none")]
216    pub migrate_to_chat_id: Option<i64>,
217    /// The supergroup has been migrated from a group with this ID.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub migrate_from_chat_id: Option<i64>,
220    /// The pinned message (service message).
221    #[serde(skip_serializing_if = "Option::is_none")]
222    pub pinned_message: Option<Box<Message>>,
223
224    /// Inline keyboard attached to the message.
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub reply_markup: Option<InlineKeyboardMarkup>,
227
228    // Payment fields
229    /// Invoice for a payment (service message).
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub invoice: Option<serde_json::Value>,
232    /// Successful payment information (service message).
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub successful_payment: Option<serde_json::Value>,
235    /// Refunded payment information (service message).
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub refunded_payment: Option<serde_json::Value>,
238
239    // Web app
240    /// Data from the Web App.
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub web_app_data: Option<WebAppData>,
243
244    // Forum topic events
245    /// Forum topic created (service message).
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub forum_topic_created: Option<serde_json::Value>,
248    /// Forum topic edited (service message).
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub forum_topic_edited: Option<serde_json::Value>,
251    /// Forum topic closed (service message).
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub forum_topic_closed: Option<serde_json::Value>,
254    /// Forum topic reopened (service message).
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub forum_topic_reopened: Option<serde_json::Value>,
257    /// General forum topic hidden (service message).
258    #[serde(skip_serializing_if = "Option::is_none")]
259    pub general_forum_topic_hidden: Option<serde_json::Value>,
260    /// General forum topic unhidden (service message).
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub general_forum_topic_unhidden: Option<serde_json::Value>,
263
264    // Direct messages
265    /// Direct messages topic identifier.
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub direct_messages_topic_id: Option<i64>,
268    /// Suggested post information.
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub suggested_post: Option<serde_json::Value>,
271}
272
273impl Message {
274    /// Returns the effective text of the message — `text` or `caption`.
275    #[must_use]
276    pub fn effective_text(&self) -> Option<&str> {
277        self.text.as_deref().or(self.caption.as_deref())
278    }
279
280    /// Returns `true` if the message is a command (text starts with `/`).
281    #[must_use]
282    pub fn is_command(&self) -> bool {
283        self.entities
284            .as_deref()
285            .unwrap_or_default()
286            .iter()
287            .any(|e| e.kind == MessageEntityKind::BotCommand && e.offset == 0)
288    }
289
290    /// Extracts the command string (e.g. `"start"` from `/start@bot`), if present.
291    #[must_use]
292    pub fn command(&self) -> Option<&str> {
293        let text = self.text.as_deref()?;
294        let entity = self
295            .entities
296            .as_deref()?
297            .iter()
298            .find(|e| e.kind == MessageEntityKind::BotCommand && e.offset == 0)?;
299        let raw = &text[..entity.length as usize];
300        // Strip the `@BotUsername` suffix if present.
301        Some(
302            raw.find('@')
303                .map_or(raw, |i| &raw[..i])
304                .trim_start_matches('/'),
305        )
306    }
307}
308
309/// Type of a message entity.
310#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum MessageEntityKind {
313    /// @username mention.
314    Mention,
315    /// #hashtag.
316    Hashtag,
317    /// $cashtag.
318    Cashtag,
319    /// /bot_command.
320    BotCommand,
321    /// Plain URL.
322    Url,
323    /// Email address.
324    Email,
325    /// Phone number.
326    PhoneNumber,
327    /// **Bold** text.
328    Bold,
329    /// _Italic_ text.
330    Italic,
331    /// Underlined text.
332    Underline,
333    /// ~~Strikethrough~~ text.
334    Strikethrough,
335    /// ||Spoiler|| text.
336    Spoiler,
337    /// Block quotation.
338    Blockquote,
339    /// Expandable block quotation.
340    ExpandableBlockquote,
341    /// Inline `code`.
342    Code,
343    /// Pre-formatted code block.
344    Pre,
345    /// Clickable text link.
346    TextLink,
347    /// Mention of a user without a username.
348    TextMention,
349    /// Custom emoji.
350    CustomEmoji,
351    /// Date/time entity.
352    DateTime,
353}
354
355/// One special entity in a message text.
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct MessageEntity {
358    /// Type of the entity.
359    #[serde(rename = "type")]
360    pub kind: MessageEntityKind,
361    /// Offset in UTF-16 code units to the start of the entity.
362    pub offset: u32,
363    /// Length of the entity in UTF-16 code units.
364    pub length: u32,
365    /// For `TextLink` — URL.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub url: Option<String>,
368    /// For `TextMention` — the mentioned user.
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub user: Option<User>,
371    /// For `Pre` — the programming language.
372    #[serde(skip_serializing_if = "Option::is_none")]
373    pub language: Option<String>,
374    /// For `CustomEmoji` — identifier of the custom emoji.
375    #[serde(skip_serializing_if = "Option::is_none")]
376    pub custom_emoji_id: Option<String>,
377    /// For `DateTime` — Unix time.
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub unix_time: Option<i64>,
380    /// For `DateTime` — format string (`r|w?[dD]?[tT]?`).
381    #[serde(skip_serializing_if = "Option::is_none")]
382    pub date_time_format: Option<String>,
383}
384
385/// Origin of a forwarded message.
386#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(tag = "type", rename_all = "snake_case")]
388pub enum MessageOrigin {
389    /// Forwarded from a user with a visible profile.
390    User {
391        /// Date the original message was sent, as a Unix timestamp.
392        date: i64,
393        /// The user who sent the original message.
394        sender_user: User,
395    },
396    /// Forwarded from a user with a hidden profile.
397    HiddenUser {
398        /// Date the original message was sent, as a Unix timestamp.
399        date: i64,
400        /// Name of the user who sent the original message.
401        sender_user_name: String,
402    },
403    /// Forwarded from a chat on behalf of the chat itself.
404    Chat {
405        /// Date the original message was sent, as a Unix timestamp.
406        date: i64,
407        /// The chat that sent the original message.
408        sender_chat: Chat,
409        /// Signature of the original post author.
410        #[serde(skip_serializing_if = "Option::is_none")]
411        author_signature: Option<String>,
412    },
413    /// Forwarded from a channel.
414    Channel {
415        /// Date the original message was sent, as a Unix timestamp.
416        date: i64,
417        /// The channel that sent the original message.
418        chat: Chat,
419        /// Identifier of the original message in the channel.
420        message_id: i64,
421        /// Signature of the original post author.
422        #[serde(skip_serializing_if = "Option::is_none")]
423        author_signature: Option<String>,
424    },
425}
426
427/// Parameters for replying to a message.
428#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct ReplyParameters {
430    /// Identifier of the message that will be replied to.
431    pub message_id: i64,
432    /// Chat containing the message.
433    #[serde(skip_serializing_if = "Option::is_none")]
434    pub chat_id: Option<crate::user::ChatId>,
435    /// `true` if the message should be sent even if the specified message is not found.
436    #[serde(skip_serializing_if = "Option::is_none")]
437    pub allow_sending_without_reply: Option<bool>,
438    /// Quoted part of the message to be replied to.
439    #[serde(skip_serializing_if = "Option::is_none")]
440    pub quote: Option<String>,
441    /// Parse mode for the quote.
442    #[serde(skip_serializing_if = "Option::is_none")]
443    pub quote_parse_mode: Option<ParseMode>,
444    /// Special entities in the quote.
445    #[serde(skip_serializing_if = "Option::is_none")]
446    pub quote_entities: Option<Vec<MessageEntity>>,
447    /// Position of the quote in the original message (UTF-16 offset).
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub quote_position: Option<u32>,
450    /// Identifier of the poll option being replied to.
451    #[serde(skip_serializing_if = "Option::is_none")]
452    pub poll_option_id: Option<String>,
453    /// Identifier of the checklist task being replied to.
454    #[serde(skip_serializing_if = "Option::is_none")]
455    pub checklist_task_id: Option<i64>,
456}
457
458/// Text parse mode for message formatting.
459#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
460pub enum ParseMode {
461    /// Markdown v2 — recommended.
462    MarkdownV2,
463    /// HTML formatting.
464    HTML,
465    /// Legacy Markdown — limited.
466    Markdown,
467}
468
469/// Reaction type on a message.
470#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(tag = "type", rename_all = "snake_case")]
472pub enum ReactionType {
473    /// A standard emoji reaction.
474    Emoji {
475        /// The emoji character.
476        emoji: String,
477    },
478    /// A custom emoji reaction.
479    CustomEmoji {
480        /// Identifier of the custom emoji.
481        custom_emoji_id: String,
482    },
483    /// A paid star reaction.
484    Paid,
485}
486
487/// Options for controlling how links are previewed in messages.
488#[derive(Debug, Clone, Default, Serialize, Deserialize)]
489pub struct LinkPreviewOptions {
490    /// `true` if link preview is disabled.
491    #[serde(skip_serializing_if = "Option::is_none")]
492    pub is_disabled: Option<bool>,
493    /// URL to use for the link preview.
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub url: Option<String>,
496    /// `true` if the media in the link preview should be shrunk.
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub prefer_small_media: Option<bool>,
499    /// `true` if the media in the link preview should be enlarged.
500    #[serde(skip_serializing_if = "Option::is_none")]
501    pub prefer_large_media: Option<bool>,
502    /// `true` if the link preview should be shown above the message text.
503    #[serde(skip_serializing_if = "Option::is_none")]
504    pub show_above_text: Option<bool>,
505}
506
507/// Information about the quoted part of a message that is replied to.
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct TextQuote {
510    /// Text of the quoted part of the message.
511    pub text: String,
512    /// Special entities in the quote.
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub entities: Option<Vec<MessageEntity>>,
515    /// Approximate quote position in the original message (UTF-16 offset).
516    pub position: u32,
517    /// `true` if the quote was chosen manually by the message sender.
518    #[serde(skip_serializing_if = "Option::is_none")]
519    pub is_manual: Option<bool>,
520}
521
522/// Information about a message that is being replied to from outside the thread.
523#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ExternalReplyInfo {
525    /// Origin of the message being replied to.
526    pub origin: MessageOrigin,
527    /// Chat the original message belongs to (if not the current chat).
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub chat: Option<Chat>,
530    /// Identifier of the original message in the original chat.
531    #[serde(skip_serializing_if = "Option::is_none")]
532    pub message_id: Option<i64>,
533    /// Options for link preview.
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub link_preview_options: Option<LinkPreviewOptions>,
536    /// Animation in the original message.
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub animation: Option<Animation>,
539    /// Audio in the original message.
540    #[serde(skip_serializing_if = "Option::is_none")]
541    pub audio: Option<Audio>,
542    /// Document in the original message.
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub document: Option<Document>,
545    /// Photo in the original message.
546    #[serde(skip_serializing_if = "Option::is_none")]
547    pub photo: Option<Vec<PhotoSize>>,
548    /// Sticker in the original message.
549    #[serde(skip_serializing_if = "Option::is_none")]
550    pub sticker: Option<Sticker>,
551    /// Video in the original message.
552    #[serde(skip_serializing_if = "Option::is_none")]
553    pub video: Option<Video>,
554    /// Video note in the original message.
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub video_note: Option<VideoNote>,
557    /// Voice note in the original message.
558    #[serde(skip_serializing_if = "Option::is_none")]
559    pub voice: Option<Voice>,
560    /// `true` if the media is covered by a spoiler animation.
561    #[serde(skip_serializing_if = "Option::is_none")]
562    pub has_media_spoiler: Option<bool>,
563    /// Contact in the original message.
564    #[serde(skip_serializing_if = "Option::is_none")]
565    pub contact: Option<Contact>,
566    /// Dice in the original message.
567    #[serde(skip_serializing_if = "Option::is_none")]
568    pub dice: Option<Dice>,
569    /// Location in the original message.
570    #[serde(skip_serializing_if = "Option::is_none")]
571    pub location: Option<Location>,
572    /// Venue in the original message.
573    #[serde(skip_serializing_if = "Option::is_none")]
574    pub venue: Option<Venue>,
575    /// Poll in the original message.
576    #[serde(skip_serializing_if = "Option::is_none")]
577    pub poll: Option<Poll>,
578}
579
580/// Phone contact.
581#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct Contact {
583    /// Contact's phone number.
584    pub phone_number: String,
585    /// Contact's first name.
586    pub first_name: String,
587    /// Contact's last name.
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub last_name: Option<String>,
590    /// Contact's Telegram user ID.
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub user_id: Option<i64>,
593    /// Contact's vCard.
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub vcard: Option<String>,
596}
597
598/// Animated dice with a random value.
599#[derive(Debug, Clone, Serialize, Deserialize)]
600pub struct Dice {
601    /// Emoji on which the dice throw animation is based.
602    pub emoji: String,
603    /// Value of the dice (1–6 for 🎲🎯🎳; 1–5 for 🏀⚽; 1–64 for 🎰).
604    pub value: u8,
605}
606
607/// Data sent from a Web App.
608#[derive(Debug, Clone, Serialize, Deserialize)]
609pub struct WebAppData {
610    /// Data sent by the Web App.
611    pub data: String,
612    /// Text of the button that opened the Web App.
613    pub button_text: String,
614}
615
616/// Information about a Web App.
617#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct WebAppInfo {
619    /// HTTPS URL of the Web App.
620    pub url: String,
621}
622
623/// Lightweight message identifier returned by `copyMessage`.
624#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct MessageId {
626    /// Identifier of the message.
627    pub message_id: i64,
628}