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#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct Message {
16 pub message_id: i64,
18
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub message_thread_id: Option<i64>,
22
23 #[serde(skip_serializing_if = "Option::is_none")]
25 pub from: Option<User>,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
29 pub sender_chat: Option<Chat>,
30
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub sender_boost_count: Option<u32>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub sender_business_bot: Option<User>,
38
39 pub date: i64,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub business_connection_id: Option<String>,
45
46 pub chat: Chat,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
51 pub forward_origin: Option<MessageOrigin>,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub is_topic_message: Option<bool>,
56
57 #[serde(skip_serializing_if = "Option::is_none")]
60 pub is_automatic_forward: Option<bool>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub reply_to_message: Option<Box<Message>>,
65
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub external_reply: Option<ExternalReplyInfo>,
69
70 #[serde(skip_serializing_if = "Option::is_none")]
72 pub quote: Option<TextQuote>,
73
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub reply_to_story: Option<serde_json::Value>,
77
78 #[serde(skip_serializing_if = "Option::is_none")]
80 pub via_bot: Option<User>,
81
82 #[serde(skip_serializing_if = "Option::is_none")]
84 pub edit_date: Option<i64>,
85
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub has_protected_content: Option<bool>,
89
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub is_from_offline: Option<bool>,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub media_group_id: Option<String>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub author_signature: Option<String>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub text: Option<String>,
105
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub entities: Option<Vec<MessageEntity>>,
109
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub link_preview_options: Option<LinkPreviewOptions>,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub effect_id: Option<String>,
117
118 #[serde(skip_serializing_if = "Option::is_none")]
121 pub animation: Option<Animation>,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub audio: Option<Audio>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub document: Option<Document>,
128 #[serde(skip_serializing_if = "Option::is_none")]
130 pub paid_media: Option<serde_json::Value>,
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub photo: Option<Vec<PhotoSize>>,
134 #[serde(skip_serializing_if = "Option::is_none")]
136 pub sticker: Option<Sticker>,
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub story: Option<serde_json::Value>,
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub video: Option<Video>,
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub video_note: Option<VideoNote>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub voice: Option<Voice>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub caption: Option<String>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub caption_entities: Option<Vec<MessageEntity>>,
157
158 #[serde(skip_serializing_if = "Option::is_none")]
160 pub show_caption_above_media: Option<bool>,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub has_media_spoiler: Option<bool>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
169 pub contact: Option<Contact>,
170 #[serde(skip_serializing_if = "Option::is_none")]
172 pub dice: Option<Dice>,
173 #[serde(skip_serializing_if = "Option::is_none")]
175 pub game: Option<serde_json::Value>,
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub poll: Option<Poll>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub venue: Option<Venue>,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub location: Option<Location>,
185
186 #[serde(skip_serializing_if = "Option::is_none")]
189 pub new_chat_members: Option<Vec<User>>,
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub left_chat_member: Option<User>,
193 #[serde(skip_serializing_if = "Option::is_none")]
195 pub new_chat_title: Option<String>,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub new_chat_photo: Option<Vec<PhotoSize>>,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub delete_chat_photo: Option<bool>,
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub group_chat_created: Option<bool>,
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub supergroup_chat_created: Option<bool>,
208 #[serde(skip_serializing_if = "Option::is_none")]
210 pub channel_chat_created: Option<bool>,
211 #[serde(skip_serializing_if = "Option::is_none")]
213 pub message_auto_delete_timer_changed: Option<serde_json::Value>,
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub migrate_to_chat_id: Option<i64>,
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub migrate_from_chat_id: Option<i64>,
220 #[serde(skip_serializing_if = "Option::is_none")]
222 pub pinned_message: Option<Box<Message>>,
223
224 #[serde(skip_serializing_if = "Option::is_none")]
226 pub reply_markup: Option<InlineKeyboardMarkup>,
227
228 #[serde(skip_serializing_if = "Option::is_none")]
231 pub invoice: Option<serde_json::Value>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub successful_payment: Option<serde_json::Value>,
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub refunded_payment: Option<serde_json::Value>,
238
239 #[serde(skip_serializing_if = "Option::is_none")]
242 pub web_app_data: Option<WebAppData>,
243
244 #[serde(skip_serializing_if = "Option::is_none")]
247 pub forum_topic_created: Option<serde_json::Value>,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub forum_topic_edited: Option<serde_json::Value>,
251 #[serde(skip_serializing_if = "Option::is_none")]
253 pub forum_topic_closed: Option<serde_json::Value>,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub forum_topic_reopened: Option<serde_json::Value>,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 pub general_forum_topic_hidden: Option<serde_json::Value>,
260 #[serde(skip_serializing_if = "Option::is_none")]
262 pub general_forum_topic_unhidden: Option<serde_json::Value>,
263
264 #[serde(skip_serializing_if = "Option::is_none")]
267 pub direct_messages_topic_id: Option<i64>,
268 #[serde(skip_serializing_if = "Option::is_none")]
270 pub suggested_post: Option<serde_json::Value>,
271}
272
273impl Message {
274 #[must_use]
276 pub fn effective_text(&self) -> Option<&str> {
277 self.text.as_deref().or(self.caption.as_deref())
278 }
279
280 #[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 #[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 Some(
302 raw.find('@')
303 .map_or(raw, |i| &raw[..i])
304 .trim_start_matches('/'),
305 )
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
311#[serde(rename_all = "snake_case")]
312pub enum MessageEntityKind {
313 Mention,
315 Hashtag,
317 Cashtag,
319 BotCommand,
321 Url,
323 Email,
325 PhoneNumber,
327 Bold,
329 Italic,
331 Underline,
333 Strikethrough,
335 Spoiler,
337 Blockquote,
339 ExpandableBlockquote,
341 Code,
343 Pre,
345 TextLink,
347 TextMention,
349 CustomEmoji,
351 DateTime,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct MessageEntity {
358 #[serde(rename = "type")]
360 pub kind: MessageEntityKind,
361 pub offset: u32,
363 pub length: u32,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub url: Option<String>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub user: Option<User>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub language: Option<String>,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub custom_emoji_id: Option<String>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub unix_time: Option<i64>,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub date_time_format: Option<String>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(tag = "type", rename_all = "snake_case")]
388pub enum MessageOrigin {
389 User {
391 date: i64,
393 sender_user: User,
395 },
396 HiddenUser {
398 date: i64,
400 sender_user_name: String,
402 },
403 Chat {
405 date: i64,
407 sender_chat: Chat,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 author_signature: Option<String>,
412 },
413 Channel {
415 date: i64,
417 chat: Chat,
419 message_id: i64,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 author_signature: Option<String>,
424 },
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
429pub struct ReplyParameters {
430 pub message_id: i64,
432 #[serde(skip_serializing_if = "Option::is_none")]
434 pub chat_id: Option<crate::user::ChatId>,
435 #[serde(skip_serializing_if = "Option::is_none")]
437 pub allow_sending_without_reply: Option<bool>,
438 #[serde(skip_serializing_if = "Option::is_none")]
440 pub quote: Option<String>,
441 #[serde(skip_serializing_if = "Option::is_none")]
443 pub quote_parse_mode: Option<ParseMode>,
444 #[serde(skip_serializing_if = "Option::is_none")]
446 pub quote_entities: Option<Vec<MessageEntity>>,
447 #[serde(skip_serializing_if = "Option::is_none")]
449 pub quote_position: Option<u32>,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub poll_option_id: Option<String>,
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub checklist_task_id: Option<i64>,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
460pub enum ParseMode {
461 MarkdownV2,
463 HTML,
465 Markdown,
467}
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
471#[serde(tag = "type", rename_all = "snake_case")]
472pub enum ReactionType {
473 Emoji {
475 emoji: String,
477 },
478 CustomEmoji {
480 custom_emoji_id: String,
482 },
483 Paid,
485}
486
487#[derive(Debug, Clone, Default, Serialize, Deserialize)]
489pub struct LinkPreviewOptions {
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub is_disabled: Option<bool>,
493 #[serde(skip_serializing_if = "Option::is_none")]
495 pub url: Option<String>,
496 #[serde(skip_serializing_if = "Option::is_none")]
498 pub prefer_small_media: Option<bool>,
499 #[serde(skip_serializing_if = "Option::is_none")]
501 pub prefer_large_media: Option<bool>,
502 #[serde(skip_serializing_if = "Option::is_none")]
504 pub show_above_text: Option<bool>,
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct TextQuote {
510 pub text: String,
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub entities: Option<Vec<MessageEntity>>,
515 pub position: u32,
517 #[serde(skip_serializing_if = "Option::is_none")]
519 pub is_manual: Option<bool>,
520}
521
522#[derive(Debug, Clone, Serialize, Deserialize)]
524pub struct ExternalReplyInfo {
525 pub origin: MessageOrigin,
527 #[serde(skip_serializing_if = "Option::is_none")]
529 pub chat: Option<Chat>,
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub message_id: Option<i64>,
533 #[serde(skip_serializing_if = "Option::is_none")]
535 pub link_preview_options: Option<LinkPreviewOptions>,
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub animation: Option<Animation>,
539 #[serde(skip_serializing_if = "Option::is_none")]
541 pub audio: Option<Audio>,
542 #[serde(skip_serializing_if = "Option::is_none")]
544 pub document: Option<Document>,
545 #[serde(skip_serializing_if = "Option::is_none")]
547 pub photo: Option<Vec<PhotoSize>>,
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub sticker: Option<Sticker>,
551 #[serde(skip_serializing_if = "Option::is_none")]
553 pub video: Option<Video>,
554 #[serde(skip_serializing_if = "Option::is_none")]
556 pub video_note: Option<VideoNote>,
557 #[serde(skip_serializing_if = "Option::is_none")]
559 pub voice: Option<Voice>,
560 #[serde(skip_serializing_if = "Option::is_none")]
562 pub has_media_spoiler: Option<bool>,
563 #[serde(skip_serializing_if = "Option::is_none")]
565 pub contact: Option<Contact>,
566 #[serde(skip_serializing_if = "Option::is_none")]
568 pub dice: Option<Dice>,
569 #[serde(skip_serializing_if = "Option::is_none")]
571 pub location: Option<Location>,
572 #[serde(skip_serializing_if = "Option::is_none")]
574 pub venue: Option<Venue>,
575 #[serde(skip_serializing_if = "Option::is_none")]
577 pub poll: Option<Poll>,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct Contact {
583 pub phone_number: String,
585 pub first_name: String,
587 #[serde(skip_serializing_if = "Option::is_none")]
589 pub last_name: Option<String>,
590 #[serde(skip_serializing_if = "Option::is_none")]
592 pub user_id: Option<i64>,
593 #[serde(skip_serializing_if = "Option::is_none")]
595 pub vcard: Option<String>,
596}
597
598#[derive(Debug, Clone, Serialize, Deserialize)]
600pub struct Dice {
601 pub emoji: String,
603 pub value: u8,
605}
606
607#[derive(Debug, Clone, Serialize, Deserialize)]
609pub struct WebAppData {
610 pub data: String,
612 pub button_text: String,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
618pub struct WebAppInfo {
619 pub url: String,
621}
622
623#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct MessageId {
626 pub message_id: i64,
628}