From 13dd93cc63dca0f8fb0edddfd9a8019c14cf17fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 15 Jul 2024 21:47:21 +0300 Subject: [PATCH 01/51] Add multiple message actions methods of TBA 7.0 --- crates/teloxide-core/schema.ron | 123 +++++++++++++++++- crates/teloxide-core/src/adaptors/cache_me.rs | 3 + crates/teloxide-core/src/adaptors/erased.rs | 52 ++++++++ .../teloxide-core/src/adaptors/parse_mode.rs | 3 + .../src/adaptors/throttle/requester_impl.rs | 3 + crates/teloxide-core/src/adaptors/trace.rs | 3 + crates/teloxide-core/src/bot/api.rs | 45 +++++++ crates/teloxide-core/src/local_macros.rs | 29 +++++ crates/teloxide-core/src/payloads.rs | 6 + .../src/payloads/copy_messages.rs | 36 +++++ .../src/payloads/delete_messages.rs | 20 +++ .../src/payloads/forward_messages.rs | 32 +++++ crates/teloxide-core/src/payloads/setters.rs | 25 ++-- .../teloxide-core/src/requests/requester.rs | 39 ++++++ 14 files changed, 406 insertions(+), 13 deletions(-) create mode 100644 crates/teloxide-core/src/payloads/copy_messages.rs create mode 100644 crates/teloxide-core/src/payloads/delete_messages.rs create mode 100644 crates/teloxide-core/src/payloads/forward_messages.rs diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 75a3a639..a1f2b939 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -39,7 +39,7 @@ //! [github]: https://github.com/WaffleLapkin/tg-methods-schema Schema( - api_version: ApiVersion(ver: "6.9", date: "September 22, 2023"), + api_version: ApiVersion(ver: "7.0", date: "December 29, 2023"), methods: [ Method( names: ("getUpdates", "GetUpdates", "get_updates"), @@ -338,6 +338,51 @@ Schema( ), ], ), + Method( + names: ("forwardMessages", "ForwardMessages", "forward_messages"), + return_ty: ArrayOf(RawTy("MessageId")), + doc: Doc( + md: "Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of [MessageId] of the sent messages is returned.", + md_links: {"MessageId": "https://core.telegram.org/bots/api#messageid"}, + ), + tg_doc: "https://core.telegram.org/bots/api#forwardmessages", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)"), + ), + Param( + name: "message_thread_id", + ty: Option(RawTy("ThreadId")), + descr: Doc(md: "Unique identifier for the target message thread (topic) of the forum; for forum supergroups only"), + ), + Param( + name: "from_chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`)"), + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc(md: "A JSON-serialized list of 1-100 identifiers of messages in the chat _from\\_chat\\_id_ to forward. The identifiers must be specified in a strictly increasing order.") + ), + Param( + name: "disable_notification", + ty: Option(bool), + descr: Doc( + md: "Sends the message [silently]. Users will receive a notification with no sound.", + md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} + ) + ), + Param( + name: "protect_content", + ty: Option(bool), + descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), + ), + ], + ), Method( names: ("copyMessage", "CopyMessage", "copy_message"), return_ty: RawTy("MessageId"), @@ -422,6 +467,60 @@ Schema( ), ], ), + Method( + names: ("copyMessages", "CopyMessages", "copy_messages"), + return_ty: ArrayOf(RawTy("MessageId")), + doc: Doc( + md: "Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz [poll] can be copied only if the value of the field _correct\\_option\\_id_ is known to the bot. The method is analogous to the method [forwardMessages], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [MessageId] of the sent messages is returned.", + md_links: { + "poll": "https://core.telegram.org/bots/api#poll", + "forwardMessages": "https://core.telegram.org/bots/api#forwardmessages", + "MessageId": "https://core.telegram.org/bots/api#messageid" + }, + ), + tg_doc: "https://core.telegram.org/bots/api#copymessages", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`)"), + ), + Param( + name: "message_thread_id", + ty: Option(RawTy("ThreadId")), + descr: Doc(md: "Unique identifier for the target message thread (topic) of the forum; for forum supergroups only"), + ), + Param( + name: "from_chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`)"), + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc(md: "Identifiers of 1-100 messages in the chat _from\\_chat\\_id_ to copy. The identifiers must be specified in a strictly increasing order.") + ), + Param( + name: "disable_notification", + ty: Option(bool), + descr: Doc( + md: "Sends the message [silently]. Users will receive a notification with no sound.", + md_links: {"silently": "https://telegram.org/blog/channels-2-0#silent-messages"} + ) + ), + Param( + name: "protect_content", + ty: Option(bool), + descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), + ), + Param( + name: "remove_caption", + ty: Option(bool), + descr: Doc(md: "Pass _True_ to copy the messages without their captions") + ) + ], + ), Method( names: ("sendPhoto", "SendPhoto", "send_photo"), return_ty: RawTy("Message"), @@ -3529,6 +3628,28 @@ Schema( ), ], ), + Method( + names: ("deleteMessages", "DeleteMessages", "delete_messages"), + return_ty: True, + doc: Doc(md: "Use this method to delete multiple messages simultaneously. If some of the specified messages can't be found, they are skipped. Returns _True_ on success."), + tg_doc: "https://core.telegram.org/bots/api#delete_messages", + tg_category: "Updating messages", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format `@channelusername`).") + ), + Param( + name: "message_ids", + ty: ArrayOf(RawTy("MessageId")), + descr: Doc( + md: "Identifiers of 1-100 messages to delete. See [deleteMessage] for limitations on which messages can be deleted", + md_links: {"deleteMessage": "https://core.telegram.org/bots/api#delete_message"} + ) + ), + ], + ), Method( names: ("sendSticker", "SendSticker", "send_sticker"), return_ty: RawTy("Message"), diff --git a/crates/teloxide-core/src/adaptors/cache_me.rs b/crates/teloxide-core/src/adaptors/cache_me.rs index eecac8bc..caab3f87 100644 --- a/crates/teloxide-core/src/adaptors/cache_me.rs +++ b/crates/teloxide-core/src/adaptors/cache_me.rs @@ -94,7 +94,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -183,6 +185,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, diff --git a/crates/teloxide-core/src/adaptors/erased.rs b/crates/teloxide-core/src/adaptors/erased.rs index 7b56d648..1e3ca58b 100644 --- a/crates/teloxide-core/src/adaptors/erased.rs +++ b/crates/teloxide-core/src/adaptors/erased.rs @@ -170,6 +170,9 @@ macro_rules! fwd_erased { (@convert $m:ident, $arg:ident, emoji_list: $T:ty) => { $arg.into_iter().collect() }; + (@convert $m:ident, $arg:ident, message_ids: $T:ty) => { + $arg.into_iter().collect() + }; (@convert $m:ident, $arg:ident, $arg_:ident : $T:ty) => { $arg.into() }; @@ -190,7 +193,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -279,6 +284,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, @@ -341,6 +347,13 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, ForwardMessage, Self::Err>; + fn forward_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, ForwardMessages, Self::Err>; + fn copy_message( &self, chat_id: Recipient, @@ -348,6 +361,13 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, CopyMessage, Self::Err>; + fn copy_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, CopyMessages, Self::Err>; + fn send_photo( &self, chat_id: Recipient, @@ -816,6 +836,12 @@ trait ErasableRequester<'a> { message_id: MessageId, ) -> ErasedRequest<'a, DeleteMessage, Self::Err>; + fn delete_messages( + &self, + chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, DeleteMessages, Self::Err>; + fn send_sticker( &self, chat_id: Recipient, @@ -1019,6 +1045,15 @@ where Requester::forward_message(self, chat_id, from_chat_id, message_id).erase() } + fn forward_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, ForwardMessages, Self::Err> { + Requester::forward_messages(self, chat_id, from_chat_id, message_ids).erase() + } + fn copy_message( &self, chat_id: Recipient, @@ -1028,6 +1063,15 @@ where Requester::copy_message(self, chat_id, from_chat_id, message_id).erase() } + fn copy_messages( + &self, + chat_id: Recipient, + from_chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, CopyMessages, Self::Err> { + Requester::copy_messages(self, chat_id, from_chat_id, message_ids).erase() + } + fn send_photo( &self, chat_id: Recipient, @@ -1676,6 +1720,14 @@ where Requester::delete_message(self, chat_id, message_id).erase() } + fn delete_messages( + &self, + chat_id: Recipient, + message_ids: Vec, + ) -> ErasedRequest<'a, DeleteMessages, Self::Err> { + Requester::delete_messages(self, chat_id, message_ids).erase() + } + fn send_sticker( &self, chat_id: Recipient, diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 216893a4..f9458bca 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -185,6 +185,8 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, + copy_messages, send_video_note, send_location, edit_message_live_location, @@ -256,6 +258,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, diff --git a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs index 28d82051..eb9cbb41 100644 --- a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs +++ b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs @@ -92,6 +92,8 @@ where set_webhook, delete_webhook, get_webhook_info, + forward_messages, + copy_messages, edit_message_live_location, edit_message_live_location_inline, stop_message_live_location, @@ -166,6 +168,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, get_sticker_set, get_custom_emoji_stickers, upload_sticker_file, diff --git a/crates/teloxide-core/src/adaptors/trace.rs b/crates/teloxide-core/src/adaptors/trace.rs index 46882c6b..cef5fa7e 100644 --- a/crates/teloxide-core/src/adaptors/trace.rs +++ b/crates/teloxide-core/src/adaptors/trace.rs @@ -123,7 +123,9 @@ where delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -212,6 +214,7 @@ where edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, diff --git a/crates/teloxide-core/src/bot/api.rs b/crates/teloxide-core/src/bot/api.rs index 380d41a4..7da6ee98 100644 --- a/crates/teloxide-core/src/bot/api.rs +++ b/crates/teloxide-core/src/bot/api.rs @@ -72,6 +72,24 @@ impl Requester for Bot { ) } + type ForwardMessages = JsonRequest; + fn forward_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::ForwardMessages + where + C: Into, + F: Into, + M: IntoIterator, + { + Self::ForwardMessages::new( + self.clone(), + payloads::ForwardMessages::new(chat_id, from_chat_id, message_ids), + ) + } + type SendPhoto = MultipartRequest; fn send_photo(&self, chat_id: C, photo: InputFile) -> Self::SendPhoto @@ -1086,6 +1104,15 @@ impl Requester for Bot { Self::DeleteMessage::new(self.clone(), payloads::DeleteMessage::new(chat_id, message_id)) } + type DeleteMessages = JsonRequest; + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages + where + C: Into, + M: IntoIterator, + { + Self::DeleteMessages::new(self.clone(), payloads::DeleteMessages::new(chat_id, message_ids)) + } + type SendSticker = MultipartRequest; fn send_sticker(&self, chat_id: C, sticker: InputFile) -> Self::SendSticker @@ -1465,6 +1492,24 @@ impl Requester for Bot { ) } + type CopyMessages = JsonRequest; + fn copy_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::CopyMessages + where + C: Into, + F: Into, + M: IntoIterator, + { + Self::CopyMessages::new( + self.clone(), + payloads::CopyMessages::new(chat_id, from_chat_id, message_ids), + ) + } + type UnpinAllChatMessages = JsonRequest; fn unpin_all_chat_messages(&self, chat_id: C) -> Self::UnpinAllChatMessages diff --git a/crates/teloxide-core/src/local_macros.rs b/crates/teloxide-core/src/local_macros.rs index 93108978..41ae27a6 100644 --- a/crates/teloxide-core/src/local_macros.rs +++ b/crates/teloxide-core/src/local_macros.rs @@ -487,6 +487,16 @@ macro_rules! requester_forward { $body!(forward_message this (chat_id: C, from_chat_id: F, message_id: MessageId)) } }; + (@method forward_messages $body:ident $ty:ident) => { + type ForwardMessages = $ty![ForwardMessages]; + + fn forward_messages(&self, chat_id: C, from_chat_id: F, message_ids: M) -> Self::ForwardMessages where C: Into, + F: Into, + M: IntoIterator { + let this = self; + $body!(forward_messages this (chat_id: C, from_chat_id: F, message_ids: M)) + } + }; (@method copy_message $body:ident $ty:ident) => { type CopyMessage = $ty![CopyMessage]; @@ -496,6 +506,16 @@ macro_rules! requester_forward { $body!(copy_message this (chat_id: C, from_chat_id: F, message_id: MessageId)) } }; + (@method copy_messages $body:ident $ty:ident) => { + type CopyMessages = $ty![CopyMessages]; + + fn copy_messages(&self, chat_id: C, from_chat_id: F, message_ids: M) -> Self::CopyMessages where C: Into, + F: Into, + M: IntoIterator { + let this = self; + $body!(copy_messages this (chat_id: C, from_chat_id: F, message_ids: M)) + } + }; (@method send_photo $body:ident $ty:ident) => { type SendPhoto = $ty![SendPhoto]; @@ -1228,6 +1248,15 @@ macro_rules! requester_forward { $body!(delete_message this (chat_id: C, message_id: MessageId)) } }; + (@method delete_messages $body:ident $ty:ident) => { + type DeleteMessages = $ty![DeleteMessages]; + + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages where C: Into, + M: IntoIterator { + let this = self; + $body!(delete_messages this (chat_id: C, message_ids: M)) + } + }; (@method send_sticker $body:ident $ty:ident) => { type SendSticker = $ty![SendSticker]; diff --git a/crates/teloxide-core/src/payloads.rs b/crates/teloxide-core/src/payloads.rs index 7f3cf308..ee3f845e 100644 --- a/crates/teloxide-core/src/payloads.rs +++ b/crates/teloxide-core/src/payloads.rs @@ -28,6 +28,7 @@ mod close; mod close_forum_topic; mod close_general_forum_topic; mod copy_message; +mod copy_messages; mod create_chat_invite_link; mod create_forum_topic; mod create_invoice_link; @@ -37,6 +38,7 @@ mod delete_chat_photo; mod delete_chat_sticker_set; mod delete_forum_topic; mod delete_message; +mod delete_messages; mod delete_my_commands; mod delete_sticker_from_set; mod delete_sticker_set; @@ -56,6 +58,7 @@ mod edit_message_text; mod edit_message_text_inline; mod export_chat_invite_link; mod forward_message; +mod forward_messages; mod get_chat; mod get_chat_administrators; mod get_chat_member; @@ -152,6 +155,7 @@ pub use close::{Close, CloseSetters}; pub use close_forum_topic::{CloseForumTopic, CloseForumTopicSetters}; pub use close_general_forum_topic::{CloseGeneralForumTopic, CloseGeneralForumTopicSetters}; pub use copy_message::{CopyMessage, CopyMessageSetters}; +pub use copy_messages::{CopyMessages, CopyMessagesSetters}; pub use create_chat_invite_link::{CreateChatInviteLink, CreateChatInviteLinkSetters}; pub use create_forum_topic::{CreateForumTopic, CreateForumTopicSetters}; pub use create_invoice_link::{CreateInvoiceLink, CreateInvoiceLinkSetters}; @@ -161,6 +165,7 @@ pub use delete_chat_photo::{DeleteChatPhoto, DeleteChatPhotoSetters}; pub use delete_chat_sticker_set::{DeleteChatStickerSet, DeleteChatStickerSetSetters}; pub use delete_forum_topic::{DeleteForumTopic, DeleteForumTopicSetters}; pub use delete_message::{DeleteMessage, DeleteMessageSetters}; +pub use delete_messages::{DeleteMessages, DeleteMessagesSetters}; pub use delete_my_commands::{DeleteMyCommands, DeleteMyCommandsSetters}; pub use delete_sticker_from_set::{DeleteStickerFromSet, DeleteStickerFromSetSetters}; pub use delete_sticker_set::{DeleteStickerSet, DeleteStickerSetSetters}; @@ -184,6 +189,7 @@ pub use edit_message_text::{EditMessageText, EditMessageTextSetters}; pub use edit_message_text_inline::{EditMessageTextInline, EditMessageTextInlineSetters}; pub use export_chat_invite_link::{ExportChatInviteLink, ExportChatInviteLinkSetters}; pub use forward_message::{ForwardMessage, ForwardMessageSetters}; +pub use forward_messages::{ForwardMessages, ForwardMessagesSetters}; pub use get_chat::{GetChat, GetChatSetters}; pub use get_chat_administrators::{GetChatAdministrators, GetChatAdministratorsSetters}; pub use get_chat_member::{GetChatMember, GetChatMemberSetters}; diff --git a/crates/teloxide-core/src/payloads/copy_messages.rs b/crates/teloxide-core/src/payloads/copy_messages.rs new file mode 100644 index 00000000..033a5c64 --- /dev/null +++ b/crates/teloxide-core/src/payloads/copy_messages.rs @@ -0,0 +1,36 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, ThreadId}; + +impl_payload! { + /// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz [`Poll`] can be copied only if the value of the field _correct\_option\_id_ is known to the bot. The method is analogous to the method [`ForwardMessages`], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [`MessageId`] of the sent messages is returned. + /// + /// [`MessageId`]: crate::types::MessageId + /// [`Poll`]: crate::payloads::Poll + /// [`ForwardMessages`]: crate::payloads::ForwardMessages + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub CopyMessages (CopyMessagesSetters) => Vec { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: Recipient [into], + /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) + pub from_chat_id: Recipient [into], + /// Identifiers of 1-100 messages in the chat _from\_chat\_id_ to copy. The identifiers must be specified in a strictly increasing order. + pub message_ids: Vec [collect], + } + optional { + /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only + pub message_thread_id: ThreadId, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// Protects the contents of sent messages from forwarding and saving + pub protect_content: bool, + /// Pass _True_ to copy the messages without their captions + pub remove_caption: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/delete_messages.rs b/crates/teloxide-core/src/payloads/delete_messages.rs new file mode 100644 index 00000000..f8306c03 --- /dev/null +++ b/crates/teloxide-core/src/payloads/delete_messages.rs @@ -0,0 +1,20 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, True}; + +impl_payload! { + /// Use this method to delete multiple messages simultaneously. If some of the specified messages can't be found, they are skipped. Returns _True_ on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub DeleteMessages (DeleteMessagesSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`). + pub chat_id: Recipient [into], + /// Identifiers of 1-100 messages to delete. See [`DeleteMessage`] for limitations on which messages can be deleted + /// + /// [`DeleteMessage`]: crate::payloads::DeleteMessage + pub message_ids: Vec [collect], + } + } +} diff --git a/crates/teloxide-core/src/payloads/forward_messages.rs b/crates/teloxide-core/src/payloads/forward_messages.rs new file mode 100644 index 00000000..e191725b --- /dev/null +++ b/crates/teloxide-core/src/payloads/forward_messages.rs @@ -0,0 +1,32 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, Recipient, ThreadId}; + +impl_payload! { + /// Use this method to forward multiple messages of any kind. If some of the specified messages can't be found or forwarded, they are skipped. Service messages and messages with protected content can't be forwarded. Album grouping is kept for forwarded messages. On success, an array of [`MessageId`] of the sent messages is returned. + /// + /// [`MessageId`]: crate::types::MessageId + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub ForwardMessages (ForwardMessagesSetters) => Vec { + required { + /// Unique identifier for the target chat or username of the target channel (in the format `@channelusername`) + pub chat_id: Recipient [into], + /// Unique identifier for the chat where the original message was sent (or channel username in the format `@channelusername`) + pub from_chat_id: Recipient [into], + /// A JSON-serialized list of 1-100 identifiers of messages in the chat _from\_chat\_id_ to forward. The identifiers must be specified in a strictly increasing order. + pub message_ids: Vec [collect], + } + optional { + /// Unique identifier for the target message thread (topic) of the forum; for forum supergroups only + pub message_thread_id: ThreadId, + /// Sends the message [silently]. Users will receive a notification with no sound. + /// + /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages + pub disable_notification: bool, + /// Protects the contents of sent messages from forwarding and saving + pub protect_content: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/setters.rs b/crates/teloxide-core/src/payloads/setters.rs index 9771e38b..96ba6712 100644 --- a/crates/teloxide-core/src/payloads/setters.rs +++ b/crates/teloxide-core/src/payloads/setters.rs @@ -6,18 +6,19 @@ pub use crate::payloads::{ AnswerPreCheckoutQuerySetters as _, AnswerShippingQuerySetters as _, AnswerWebAppQuerySetters as _, ApproveChatJoinRequestSetters as _, BanChatMemberSetters as _, BanChatSenderChatSetters as _, CloseForumTopicSetters as _, CloseGeneralForumTopicSetters as _, - CloseSetters as _, CopyMessageSetters as _, CreateChatInviteLinkSetters as _, - CreateForumTopicSetters as _, CreateInvoiceLinkSetters as _, CreateNewStickerSetSetters as _, - DeclineChatJoinRequestSetters as _, DeleteChatPhotoSetters as _, - DeleteChatStickerSetSetters as _, DeleteForumTopicSetters as _, DeleteMessageSetters as _, - DeleteMyCommandsSetters as _, DeleteStickerFromSetSetters as _, DeleteStickerSetSetters as _, - DeleteWebhookSetters as _, EditChatInviteLinkSetters as _, EditForumTopicSetters as _, - EditGeneralForumTopicSetters as _, EditMessageCaptionInlineSetters as _, - EditMessageCaptionSetters as _, EditMessageLiveLocationInlineSetters as _, - EditMessageLiveLocationSetters as _, EditMessageMediaInlineSetters as _, - EditMessageMediaSetters as _, EditMessageReplyMarkupInlineSetters as _, - EditMessageReplyMarkupSetters as _, EditMessageTextInlineSetters as _, - EditMessageTextSetters as _, ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, + CloseSetters as _, CopyMessageSetters as _, CopyMessagesSetters as _, + CreateChatInviteLinkSetters as _, CreateForumTopicSetters as _, CreateInvoiceLinkSetters as _, + CreateNewStickerSetSetters as _, DeclineChatJoinRequestSetters as _, + DeleteChatPhotoSetters as _, DeleteChatStickerSetSetters as _, DeleteForumTopicSetters as _, + DeleteMessageSetters as _, DeleteMessagesSetters as _, DeleteMyCommandsSetters as _, + DeleteStickerFromSetSetters as _, DeleteStickerSetSetters as _, DeleteWebhookSetters as _, + EditChatInviteLinkSetters as _, EditForumTopicSetters as _, EditGeneralForumTopicSetters as _, + EditMessageCaptionInlineSetters as _, EditMessageCaptionSetters as _, + EditMessageLiveLocationInlineSetters as _, EditMessageLiveLocationSetters as _, + EditMessageMediaInlineSetters as _, EditMessageMediaSetters as _, + EditMessageReplyMarkupInlineSetters as _, EditMessageReplyMarkupSetters as _, + EditMessageTextInlineSetters as _, EditMessageTextSetters as _, + ExportChatInviteLinkSetters as _, ForwardMessageSetters as _, ForwardMessagesSetters as _, GetChatAdministratorsSetters as _, GetChatMemberCountSetters as _, GetChatMemberSetters as _, GetChatMembersCountSetters as _, GetChatMenuButtonSetters as _, GetChatSetters as _, GetCustomEmojiStickersSetters as _, GetFileSetters as _, GetForumTopicIconStickersSetters as _, diff --git a/crates/teloxide-core/src/requests/requester.rs b/crates/teloxide-core/src/requests/requester.rs index a67b3a97..0b97abf9 100644 --- a/crates/teloxide-core/src/requests/requester.rs +++ b/crates/teloxide-core/src/requests/requester.rs @@ -203,6 +203,20 @@ pub trait Requester { C: Into, F: Into; + type ForwardMessages: Request; + + /// For Telegram documentation see [`ForwardMessages`]. + fn forward_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::ForwardMessages + where + C: Into, + F: Into, + M: IntoIterator; + type CopyMessage: Request; /// For Telegram documentation see [`CopyMessage`]. @@ -216,6 +230,20 @@ pub trait Requester { C: Into, F: Into; + type CopyMessages: Request; + + /// For Telegram documentation see [`CopyMessages`]. + fn copy_messages( + &self, + chat_id: C, + from_chat_id: F, + message_ids: M, + ) -> Self::CopyMessages + where + C: Into, + F: Into, + M: IntoIterator; + type SendPhoto: Request; /// For Telegram documentation see [`SendPhoto`]. @@ -977,6 +1005,14 @@ pub trait Requester { where C: Into; + type DeleteMessages: Request; + + /// For Telegram documentation see [`DeleteMessages`]. + fn delete_messages(&self, chat_id: C, message_ids: M) -> Self::DeleteMessages + where + C: Into, + M: IntoIterator; + type SendSticker: Request; /// For Telegram documentation see [`SendSticker`]. @@ -1249,7 +1285,9 @@ macro_rules! forward_all { delete_webhook, get_webhook_info, forward_message, + forward_messages, copy_message, + copy_messages, send_message, send_photo, send_audio, @@ -1338,6 +1376,7 @@ macro_rules! forward_all { edit_message_reply_markup_inline, stop_poll, delete_message, + delete_messages, send_sticker, get_sticker_set, get_custom_emoji_stickers, From fcf3a1451046d1bb279528baa59cb6620a60321a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Mon, 15 Jul 2024 22:44:21 +0300 Subject: [PATCH 02/51] Add blockqoute message entity --- crates/teloxide-core/src/types/message_entity.rs | 13 +++++++++++++ crates/teloxide-core/src/types/parse_mode.rs | 7 +++++++ crates/teloxide-core/src/util.rs | 1 + crates/teloxide/src/utils/html.rs | 10 ++++++++++ crates/teloxide/src/utils/markdown.rs | 10 ++++++++++ 5 files changed, 41 insertions(+) diff --git a/crates/teloxide-core/src/types/message_entity.rs b/crates/teloxide-core/src/types/message_entity.rs index c6cb7c8e..df4c4d0d 100644 --- a/crates/teloxide-core/src/types/message_entity.rs +++ b/crates/teloxide-core/src/types/message_entity.rs @@ -249,6 +249,7 @@ pub enum MessageEntityKind { Email, PhoneNumber, Bold, + Blockquote, Italic, Underline, Strikethrough, @@ -285,6 +286,18 @@ mod tests { ); } + // https://github.com/teloxide/teloxide/issues/1062 + #[test] + fn blockquote() { + use serde_json::from_str; + + assert_eq!( + MessageEntity { kind: MessageEntityKind::Blockquote, offset: 32, length: 92 }, + from_str::(r#"{"type": "blockquote", "offset": 32, "length": 92}"#) + .unwrap() + ); + } + #[test] fn pre() { use serde_json::from_str; diff --git a/crates/teloxide-core/src/types/parse_mode.rs b/crates/teloxide-core/src/types/parse_mode.rs index 0edc69eb..0ac230f8 100644 --- a/crates/teloxide-core/src/types/parse_mode.rs +++ b/crates/teloxide-core/src/types/parse_mode.rs @@ -55,6 +55,11 @@ use serde::{Deserialize, Serialize}; /// ```rust #[doc = "pre-formatted fixed-width code block written in the Rust programming language"] /// ``` +/// >Block quotation started +/// >Block quotation continued +/// >Block quotation continued +/// >Block quotation continued +/// >The last line of the block quotation /// ```` /// /// Please note: @@ -98,6 +103,8 @@ use serde::{Deserialize, Serialize}; ///
pre-formatted fixed-width code block
#[doc = "
pre-formatted fixed-width code block written in the \
          Rust programming language
"] +///
Block quotation started\nBlock quotation continued\nThe last +/// line of the block quotation
/// ```` /// /// Please note: diff --git a/crates/teloxide-core/src/util.rs b/crates/teloxide-core/src/util.rs index a917428b..7fd8d533 100644 --- a/crates/teloxide-core/src/util.rs +++ b/crates/teloxide-core/src/util.rs @@ -44,6 +44,7 @@ pub(crate) fn mentioned_users_from_entities( | Email | PhoneNumber | Bold + | Blockquote | Italic | Underline | Strikethrough diff --git a/crates/teloxide/src/utils/html.rs b/crates/teloxide/src/utils/html.rs index 5fa2ee91..823ecda1 100644 --- a/crates/teloxide/src/utils/html.rs +++ b/crates/teloxide/src/utils/html.rs @@ -14,6 +14,16 @@ pub fn bold(s: &str) -> String { format!("{s}") } +/// Applies the block quotation style to the string. +/// +/// Passed string will not be automatically escaped because it can contain +/// nested markup. +#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \ + without using its output does nothing useful"] +pub fn blockquote(s: &str) -> String { + format!("
{s}
") +} + /// Applies the italic font style to the string. /// /// Passed string will not be automatically escaped because it can contain diff --git a/crates/teloxide/src/utils/markdown.rs b/crates/teloxide/src/utils/markdown.rs index ad6eb26c..ec007c29 100644 --- a/crates/teloxide/src/utils/markdown.rs +++ b/crates/teloxide/src/utils/markdown.rs @@ -14,6 +14,16 @@ pub fn bold(s: &str) -> String { format!("*{s}*") } +/// Applies the block quotation style to the string. +/// +/// Passed string will not be automatically escaped because it can contain +/// nested markup. +#[must_use = "This function returns a new string, rather than mutating the argument, so calling it \ + without using its output does nothing useful"] +pub fn blockquote(s: &str) -> String { + format!(">{s}") +} + /// Applies the italic font style to the string. /// /// Can be safely used with `utils::markdown::underline()`. From 7dc7047ea16733328e7675ebc9fb011fc448ca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Tue, 16 Jul 2024 14:14:05 +0300 Subject: [PATCH 03/51] Add request for multiple users --- crates/teloxide-core/src/types.rs | 4 +-- .../src/types/keyboard_button.rs | 32 ++++++++--------- .../src/types/keyboard_button_request_user.rs | 34 ++++++++++++++----- crates/teloxide-core/src/types/message.rs | 16 ++++----- crates/teloxide-core/src/types/user_shared.rs | 15 -------- .../teloxide-core/src/types/users_shared.rs | 15 ++++++++ 6 files changed, 67 insertions(+), 49 deletions(-) delete mode 100644 crates/teloxide-core/src/types/user_shared.rs create mode 100644 crates/teloxide-core/src/types/users_shared.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index f142825d..50f7c9f6 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -116,7 +116,7 @@ pub use unit_true::*; pub use update::*; pub use user::*; pub use user_profile_photos::*; -pub use user_shared::*; +pub use users_shared::*; pub use venue::*; pub use video::*; pub use video_chat_ended::*; @@ -219,7 +219,7 @@ mod unit_true; mod update; mod user; mod user_profile_photos; -mod user_shared; +mod users_shared; mod venue; mod video; mod video_chat_ended; diff --git a/crates/teloxide-core/src/types/keyboard_button.rs b/crates/teloxide-core/src/types/keyboard_button.rs index 2139b97e..60470977 100644 --- a/crates/teloxide-core/src/types/keyboard_button.rs +++ b/crates/teloxide-core/src/types/keyboard_button.rs @@ -1,7 +1,7 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use crate::types::{ - KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUser, True, WebAppInfo, + KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUsers, True, WebAppInfo, }; /// This object represents one button of the reply keyboard. @@ -68,10 +68,10 @@ pub enum ButtonRequest { /// [`chat_shared`]: crate::types::MessageKind::ChatShared RequestChat(KeyboardButtonRequestChat), - /// If this variant is used, pressing the button will open a list of - /// suitable users. Tapping on any user will send their identifier to the - /// bot in a “user_shared” service message. - RequestUser(KeyboardButtonRequestUser), + /// If specified, pressing the button will open a list of suitable users. + /// Identifiers of selected users will be sent to the bot in a + /// “users_shared” service message. Available in private chats only. + RequestUsers(KeyboardButtonRequestUsers), /// If this variant is used, the user will be asked to create a poll and /// send it to the bot when the button is pressed. @@ -110,10 +110,10 @@ struct RawRequest { chat: Option, /// If specified, pressing the button will open a list of suitable users. - /// Tapping on any user will send their identifier to the bot in a - /// “user_shared” service message. Available in private chats only. - #[serde(rename = "request_user")] - user: Option, + /// Identifiers of selected users will be sent to the bot in a + /// “users_shared” service message. Available in private chats only. + #[serde(rename = "request_users")] + users: Option, /// If specified, the user will be asked to create a poll and /// send it to the bot when the button is pressed. Available in private @@ -134,11 +134,11 @@ impl<'de> Deserialize<'de> for ButtonRequest { { let raw = RawRequest::deserialize(deserializer)?; match raw { - RawRequest { contact, location, chat, user, poll, web_app } + RawRequest { contact, location, chat, users, poll, web_app } if 1 < (contact.is_some() as u8 + location.is_some() as u8 + chat.is_some() as u8 - + user.is_some() as u8 + + users.is_some() as u8 + poll.is_some() as u8 + web_app.is_some() as u8) => { @@ -150,7 +150,7 @@ impl<'de> Deserialize<'de> for ButtonRequest { RawRequest { contact: Some(True), .. } => Ok(Self::Contact), RawRequest { location: Some(True), .. } => Ok(Self::Location), RawRequest { chat: Some(request_chat), .. } => Ok(Self::RequestChat(request_chat)), - RawRequest { user: Some(request_user), .. } => Ok(Self::RequestUser(request_user)), + RawRequest { users: Some(request_users), .. } => Ok(Self::RequestUsers(request_users)), RawRequest { poll: Some(poll_type), .. } => Ok(Self::Poll(poll_type)), RawRequest { web_app: Some(web_app), .. } => Ok(Self::WebApp(web_app)), @@ -158,11 +158,11 @@ impl<'de> Deserialize<'de> for ButtonRequest { contact: None, location: None, chat: None, - user: None, + users: None, poll: None, web_app: None, } => Err(D::Error::custom( - "Either one of `request_contact`, `request_chat`, `request_user`, \ + "Either one of `request_contact`, `request_chat`, `request_users`, \ `request_location`, `request_poll` and `web_app` fields is required", )), } @@ -178,7 +178,7 @@ impl Serialize for ButtonRequest { contact: None, location: None, chat: None, - user: None, + users: None, poll: None, web_app: None, }; @@ -187,7 +187,7 @@ impl Serialize for ButtonRequest { Self::Contact => raw.contact = Some(True), Self::Location => raw.location = Some(True), Self::RequestChat(request_chat) => raw.chat = Some(request_chat.clone()), - Self::RequestUser(request_user) => raw.user = Some(request_user.clone()), + Self::RequestUsers(request_users) => raw.users = Some(request_users.clone()), Self::Poll(poll_type) => raw.poll = Some(poll_type.clone()), Self::WebApp(web_app) => raw.web_app = Some(web_app.clone()), }; diff --git a/crates/teloxide-core/src/types/keyboard_button_request_user.rs b/crates/teloxide-core/src/types/keyboard_button_request_user.rs index 843b86d1..8df5cc4a 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_user.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_user.rs @@ -1,15 +1,17 @@ use serde::{Deserialize, Serialize}; -/// This object defines the criteria used to request a suitable user. The -/// identifier of the selected user will be shared with the bot when the +/// This object defines the criteria used to request a suitable users. The +/// identifiers of the selected users will be shared with the bot when the /// corresponding button is pressed. More about requesting users » +/// +/// [More about requesting users »]: https://core.telegram.org/bots/features#chat-and-user-selection #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub struct KeyboardButtonRequestUser { - /// identifier of the request, which will be received back in the - /// [`UserShared`] object. Must be unique within the message. +pub struct KeyboardButtonRequestUsers { + /// Identifier of the request, which will be received back in the + /// [`UsersShared`] object. Must be unique within the message. /// - /// [`UserShared`]: crate::types::UserShared + /// [`UsersShared`]: crate::types::UsersShared pub request_id: i32, /// Pass `true` to request a bot, pass `false` to request a regular user. If @@ -22,12 +24,16 @@ pub struct KeyboardButtonRequestUser { /// applied. #[serde(default, skip_serializing_if = "Option::is_none")] pub user_is_premium: Option, + + /// The maximum number of users to be selected; 1-10. Defaults to 1. + #[serde(default = "de_max_quantity_default")] + pub max_quantity: u8, } -impl KeyboardButtonRequestUser { +impl KeyboardButtonRequestUsers { /// Creates a new [`KeyboardButtonRequestUser`]. pub fn new(request_id: i32) -> Self { - Self { request_id, user_is_bot: None, user_is_premium: None } + Self { request_id, user_is_bot: None, user_is_premium: None, max_quantity: 1 } } /// Setter for `user_is_bot` field @@ -41,4 +47,16 @@ impl KeyboardButtonRequestUser { self.user_is_premium = Some(value); self } + + /// Setter for `max_quantity` field, the value must be in the range 1..=10 + pub fn max_quantity(mut self, value: u8) -> Self { + assert!((1..=10).contains(&value)); + + self.max_quantity = value; + self + } +} + +fn de_max_quantity_default() -> u8 { + 1 } diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 2990b1aa..b2696028 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -10,7 +10,7 @@ use crate::types::{ GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, ThreadId, True, - User, UserShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, + User, UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, }; @@ -61,7 +61,7 @@ pub enum MessageKind { MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged), Pinned(MessagePinned), ChatShared(MessageChatShared), - UserShared(MessageUserShared), + UsersShared(MessageUsersShared), Invoice(MessageInvoice), SuccessfulPayment(MessageSuccessfulPayment), ConnectedWebsite(MessageConnectedWebsite), @@ -256,9 +256,9 @@ pub struct MessageChatShared { } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct MessageUserShared { - /// A chat was shared with the bot. - pub user_shared: UserShared, +pub struct MessageUsersShared { + /// Users were shared with the bot + pub users_shared: UsersShared, } #[serde_with::skip_serializing_none] @@ -682,7 +682,7 @@ mod getters { MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, - MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUserShared, + MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUsersShared, MessageVideoChatParticipantsInvited, PhotoSize, User, }; @@ -1306,9 +1306,9 @@ mod getters { } #[must_use] - pub fn shared_user(&self) -> Option<&types::UserShared> { + pub fn shared_users(&self) -> Option<&types::UsersShared> { match &self.kind { - UserShared(MessageUserShared { user_shared }) => Some(user_shared), + UsersShared(MessageUsersShared { users_shared }) => Some(users_shared), _ => None, } } diff --git a/crates/teloxide-core/src/types/user_shared.rs b/crates/teloxide-core/src/types/user_shared.rs deleted file mode 100644 index 1fd0cefe..00000000 --- a/crates/teloxide-core/src/types/user_shared.rs +++ /dev/null @@ -1,15 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::types::UserId; - -/// Information about the chat whose identifier was shared with the bot using a -/// [`KeyboardButtonRequestUser`] button. -/// -/// [`KeyboardButtonRequestUser`]: crate::types::KeyboardButtonRequestUser -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct UserShared { - /// Identifier of the request. - pub request_id: i32, - /// Identifier of the shared user. - pub user_id: UserId, -} diff --git a/crates/teloxide-core/src/types/users_shared.rs b/crates/teloxide-core/src/types/users_shared.rs new file mode 100644 index 00000000..46b20679 --- /dev/null +++ b/crates/teloxide-core/src/types/users_shared.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::UserId; + +/// This object contains information about the users whose identifiers were +/// shared with the bot using a [KeyboardButtonRequestUsers] button. +/// +/// [KeyboardButtonRequestUsers]: crate::types::KeyboardButtonRequestUsers +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct UsersShared { + /// Identifier of the request + pub request_id: i32, + /// Identifiers of the shared users + pub user_ids: Vec, +} From 83d9e6c6b76019c0182b762a1270354d3c413f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Tue, 16 Jul 2024 15:08:12 +0300 Subject: [PATCH 04/51] Add new fields of TBA7.0 to the ChatFullInfo --- crates/teloxide-core/src/types/chat.rs | 20 ++------ .../teloxide-core/src/types/chat_full_info.rs | 47 +++++++++++++++---- crates/teloxide-core/src/types/message.rs | 5 +- crates/teloxide-core/src/types/update.rs | 3 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/crates/teloxide-core/src/types/chat.rs b/crates/teloxide-core/src/types/chat.rs index 3f6fbd52..2b0fd386 100644 --- a/crates/teloxide-core/src/types/chat.rs +++ b/crates/teloxide-core/src/types/chat.rs @@ -108,13 +108,6 @@ pub struct ChatPrivate { /// A last name of the other party in a private chat. pub last_name: Option, - /// Custom emoji identifier of emoji status of the other party in a private - /// chat. Returned only in [`GetChat`]. - /// - /// [`GetChat`]: crate::payloads::GetChat - // FIXME: CustomEmojiId - pub emoji_status_custom_emoji_id: Option, - /// Bio of the other party in a private chat. Returned only in [`GetChat`]. /// /// [`GetChat`]: crate::payloads::GetChat @@ -539,7 +532,6 @@ mod serde_helper { bio: Option, has_private_forwards: Option, has_restricted_voice_and_video_messages: Option, - emoji_status_custom_emoji_id: Option, } impl From for super::ChatPrivate { @@ -552,7 +544,6 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, }: ChatPrivate, ) -> Self { Self { @@ -562,7 +553,6 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, } } } @@ -576,7 +566,6 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, }: super::ChatPrivate, ) -> Self { Self { @@ -587,7 +576,6 @@ mod serde_helper { bio, has_private_forwards, has_restricted_voice_and_video_messages, - emoji_status_custom_emoji_id, } } } @@ -618,7 +606,7 @@ mod tests { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; let actual = from_str(r#"{"id":-1,"type":"channel","username":"channel_name"}"#).unwrap(); assert_eq!(expected, actual); @@ -636,14 +624,13 @@ mod tests { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None }), photo: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, from_str(r#"{"id":0,"type":"private","username":"username","first_name":"Anon"}"#) .unwrap() @@ -661,14 +648,13 @@ mod tests { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None, }), photo: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; let json = to_string(&chat).unwrap(); diff --git a/crates/teloxide-core/src/types/chat_full_info.rs b/crates/teloxide-core/src/types/chat_full_info.rs index 69d547a7..f48f3d07 100644 --- a/crates/teloxide-core/src/types/chat_full_info.rs +++ b/crates/teloxide-core/src/types/chat_full_info.rs @@ -4,12 +4,43 @@ use serde::{Deserialize, Serialize}; // TODO: in the TBA7.3 the Chat will be splitted into Chat and ChatInfo // Currently it's just a container for the some fields of the Chat struct #[serde_with::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ChatFullInfo { + /// Identifier of the accent color for the chat name and backgrounds of the + /// chat photo, reply header, and link preview. See [accent colors] for more + /// details. + /// + /// [accent colors]: https://core.telegram.org/bots/api#accent-colors + pub accent_color_id: Option, + /// Custom emoji identifier of the emoji chosen by the chat for the reply + /// header and link preview background + // FIXME: CustomEmojiId + pub background_custom_emoji_id: Option, + /// Identifier of the accent color for the chat's profile background. See + /// [profile accent colors] for more details. + /// + /// [profile accent colors]: https://core.telegram.org/bots/api#profile-accent-colors + pub profile_accent_color_id: Option, + /// Custom emoji identifier of the emoji chosen by the chat for its profile + /// background + // FIXME: CustomEmojiId + pub profile_background_custom_emoji_id: Option, + /// Custom emoji identifier of emoji status of the other party in a private + /// chat. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + // FIXME: CustomEmojiId + pub emoji_status_custom_emoji_id: Option, /// Expiration date of the emoji status of the chat or the other party in a /// private chat, in Unix time, if any #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub emoji_status_expiration_date: Option>, + /// True, if new chat members will have access to old messages; available + /// only to chat administrators. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + #[serde(default)] + pub has_visible_history: bool, } #[cfg(test)] @@ -18,18 +49,18 @@ mod tests { #[test] fn test_chat_full_info_de() { - assert_eq!( - serde_json::from_str::("{}").unwrap(), - ChatFullInfo { emoji_status_expiration_date: None } - ); + assert_eq!(serde_json::from_str::("{}").unwrap(), ChatFullInfo::default()); assert_eq!( serde_json::from_str::( r#"{ - "emoji_status_expiration_date": 1720708004 - }"# + "emoji_status_expiration_date": 1720708004 + }"# ) .unwrap(), - ChatFullInfo { emoji_status_expiration_date: DateTime::from_timestamp(1720708004, 0) } + ChatFullInfo { + emoji_status_expiration_date: DateTime::from_timestamp(1720708004, 0), + ..ChatFullInfo::default() + } ); } } diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index b2696028..eddd623e 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1775,7 +1775,6 @@ mod tests { last_name: Some("Власов".to_string()), username: Some("aka_dude".to_string()), bio: None, - emoji_status_custom_emoji_id: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None }), @@ -1784,7 +1783,7 @@ mod tests { pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, kind: MessageKind::ChatShared(MessageChatShared { chat_shared: ChatShared { request_id: 348349, chat_id: ChatId(384939) } @@ -2017,7 +2016,7 @@ mod tests { pinned_message: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }; assert!(message.from().unwrap().is_anonymous()); diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index cd08a326..a1d504ee 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -435,14 +435,13 @@ mod test { bio: None, has_private_forwards: None, has_restricted_voice_and_video_messages: None, - emoji_status_custom_emoji_id: None, }), photo: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, kind: MessageKind::Common(MessageCommon { from: Some(User { From 6c967231eff35726c8b7a9088bfa676849697726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 17 Jul 2024 11:49:34 +0300 Subject: [PATCH 05/51] Add MessageOrigin field to MessageCommon --- crates/teloxide-core/src/types.rs | 3 + crates/teloxide-core/src/types/message.rs | 135 +++++++----------- .../teloxide-core/src/types/message_origin.rs | 57 ++++++++ crates/teloxide-core/src/types/update.rs | 2 +- crates/teloxide/src/dispatching/filter_ext.rs | 1 - 5 files changed, 112 insertions(+), 86 deletions(-) create mode 100644 crates/teloxide-core/src/types/message_origin.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 50f7c9f6..3359badc 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -85,6 +85,7 @@ pub use message::*; pub use message_auto_delete_timer_changed::*; pub use message_entity::*; pub use message_id::*; +pub use message_origin::*; pub use order_info::*; pub use parse_mode::*; pub use passport_data::*; @@ -191,6 +192,7 @@ mod message; mod message_auto_delete_timer_changed; mod message_entity; mod message_id; +mod message_origin; mod order_info; mod parse_mode; mod photo_size; @@ -403,6 +405,7 @@ pub(crate) mod option_url_from_string { } } +#[allow(dead_code)] pub(crate) mod option_msg_id_as_int { use crate::types::MessageId; diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index eddd623e..575d2a82 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -8,9 +8,9 @@ use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, - MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData, - PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, ThreadId, True, - User, UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, + MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, + PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, + ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, }; @@ -101,9 +101,8 @@ pub struct MessageCommon { /// title of an anonymous group administrator. pub author_signature: Option, - /// For forwarded messages, information about the forward - #[serde(flatten)] - pub forward: Option, + /// Information about the original message for forwarded messages + pub forward_origin: Option, /// For replies, the original message. Note that the Message object in this /// field will not contain further `reply_to_message` fields even if it @@ -299,52 +298,6 @@ pub struct MessagePassportData { pub passport_data: PassportData, } -/// Information about forwarded message. -#[serde_with::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Forward { - /// Date the original message was sent in Unix time. - #[serde(rename = "forward_date")] - #[serde(with = "crate::types::serde_date_from_unix_timestamp")] - pub date: DateTime, - - /// The entity that sent the original message. - #[serde(flatten)] - pub from: ForwardedFrom, - - /// For messages forwarded from channels, signature of the post author if - /// present. For messages forwarded from anonymous admins, authors title, if - /// present. - #[serde(rename = "forward_signature")] - pub signature: Option, - - /// For messages forwarded from channels, identifier of the original message - /// in the channel - #[serde( - rename = "forward_from_message_id", - with = "crate::types::option_msg_id_as_int", - default, - skip_serializing_if = "Option::is_none" - )] - pub message_id: Option, -} - -/// The entity that sent the original message that later was forwarded. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum ForwardedFrom { - /// The message was sent by a user. - #[serde(rename = "forward_from")] - User(User), - /// The message was sent by an anonymous user on behalf of a group or - /// channel. - #[serde(rename = "forward_from_chat")] - Chat(Chat), - /// The message was sent by a user who disallow adding a link to their - /// account in forwarded messages. - #[serde(rename = "forward_sender_name")] - SenderName(String), -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(untagged)] pub enum MediaKind { @@ -674,13 +627,13 @@ mod getters { use std::ops::Deref; use crate::types::{ - self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom, - MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, - MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, MediaText, MediaVenue, - MediaVideo, MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, - MessageChatShared, MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, - MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, - MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, + self, message::MessageKind::*, Chat, ChatId, ChatMigration, MediaAnimation, MediaAudio, + MediaContact, MediaDocument, MediaGame, MediaKind, MediaLocation, MediaPhoto, MediaPoll, + MediaSticker, MediaStory, MediaText, MediaVenue, MediaVideo, MediaVideoNote, MediaVoice, + Message, MessageChannelChatCreated, MessageChatShared, MessageCommon, + MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity, + MessageGroupChatCreated, MessageId, MessageInvoice, MessageLeftChatMember, + MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessageOrigin, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUsersShared, MessageVideoChatParticipantsInvited, PhotoSize, User, @@ -725,52 +678,63 @@ mod getters { } #[must_use] - pub fn forward(&self) -> Option<&Forward> { - self.common().and_then(|m| m.forward.as_ref()) + pub fn forward_origin(&self) -> Option<&MessageOrigin> { + match &self.kind { + Common(MessageCommon { forward_origin, .. }) => forward_origin.as_ref(), + _ => None, + } } #[must_use] pub fn forward_date(&self) -> Option> { - self.forward().map(|f| f.date) - } - - #[must_use] - pub fn forward_from(&self) -> Option<&ForwardedFrom> { - self.forward().map(|f| &f.from) + self.forward_origin().map(|f| f.date()) } #[must_use] pub fn forward_from_user(&self) -> Option<&User> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::User(user) => Some(user), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::User { sender_user, .. } => Some(sender_user), _ => None, }) } #[must_use] pub fn forward_from_chat(&self) -> Option<&Chat> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::Chat(chat) => Some(chat), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Chat { sender_chat, .. } => Some(sender_chat), _ => None, }) } #[must_use] pub fn forward_from_sender_name(&self) -> Option<&str> { - self.forward_from().and_then(|from| match from { - ForwardedFrom::SenderName(sender_name) => Some(&**sender_name), + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::HiddenUser { sender_user_name, .. } => { + Some(sender_user_name.as_str()) + } _ => None, }) } #[must_use] pub fn forward_from_message_id(&self) -> Option { - self.forward().and_then(|f| f.message_id) + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Channel { message_id, .. } => Some(*message_id), + _ => None, + }) } #[must_use] - pub fn forward_signature(&self) -> Option<&str> { - self.forward().and_then(|f| f.signature.as_deref()) + pub fn forward_author_signature(&self) -> Option<&str> { + self.forward_origin().and_then(|origin| match origin { + MessageOrigin::Chat { author_signature, .. } => { + author_signature.as_ref().map(|a| a.as_str()) + } + MessageOrigin::Channel { author_signature, .. } => { + author_signature.as_ref().map(|a| a.as_str()) + } + _ => None, + }) } #[must_use] @@ -1974,14 +1938,17 @@ mod tests { "title": "a", "type": "supergroup" }, - "date": 1640359576, - "forward_from_chat": { - "id": -1001160242915, - "title": "a", - "type": "supergroup" + "forward_origin": { + "type": "chat", + "date": 1640359544, + "sender_chat": { + "id": -1001160242915, + "title": "a", + "type": "supergroup" + }, + "author_signature": "TITLE" }, - "forward_signature": "TITLE", - "forward_date": 1640359544, + "date": 1640359576, "text": "text" }"#; @@ -2024,7 +1991,7 @@ mod tests { assert_eq!(message.sender_chat().unwrap(), &group); assert_eq!(&message.chat, &group); assert_eq!(message.forward_from_chat().unwrap(), &group); - assert_eq!(message.forward_signature().unwrap(), "TITLE"); + assert_eq!(message.forward_author_signature().unwrap(), "TITLE"); assert!(message.forward_date().is_some()); assert_eq!(message.text().unwrap(), "text"); } diff --git a/crates/teloxide-core/src/types/message_origin.rs b/crates/teloxide-core/src/types/message_origin.rs new file mode 100644 index 00000000..240539dd --- /dev/null +++ b/crates/teloxide-core/src/types/message_origin.rs @@ -0,0 +1,57 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, User}; + +/// This object describes the origin of a message +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "type")] +pub enum MessageOrigin { + User { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// User that sent the message originally + sender_user: User, + }, + HiddenUser { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Name of the user that sent the message originally + sender_user_name: String, + }, + Chat { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Chat that sent the message originally + sender_chat: Chat, + /// For messages originally sent by an anonymous chat administrator, + /// original message author signature + author_signature: Option, + }, + Channel { + /// Date the message was sent originally in Unix time + #[serde(default, with = "crate::types::serde_date_from_unix_timestamp")] + date: DateTime, + /// Channel chat to which the message was originally sent + chat: Chat, + /// Unique message identifier inside the chat + message_id: MessageId, + /// Signature of the original post author + author_signature: Option, + }, +} + +impl MessageOrigin { + pub fn date(&self) -> DateTime { + *match self { + Self::User { date, .. } => date, + Self::HiddenUser { date, .. } => date, + Self::Chat { date, .. } => date, + Self::Channel { date, .. } => date, + } + } +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index a1d504ee..ed1b2cac 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -455,7 +455,7 @@ mod test { added_to_attachment_menu: false, }), reply_to_message: None, - forward: None, + forward_origin: None, edit_date: None, media_kind: MediaKind::Text(MediaText { text: String::from("hello there"), diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index 532f614f..08d0fba6 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -92,7 +92,6 @@ define_message_ext! { (filter_migration_from, Message::migrate_from_chat_id), (filter_migration_to, Message::migrate_to_chat_id), (filter_reply_to_message, Message::reply_to_message), - (filter_forward_from, Message::forward_from), // Rest variants of a MessageKind (filter_new_chat_members, Message::new_chat_members), (filter_left_chat_member, Message::left_chat_member), From c8f7bd745cac5cbe7130ea953285c39024cb2c3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 17 Jul 2024 15:05:46 +0300 Subject: [PATCH 06/51] Add `MaybeInaccessibleMessage` to the `Message` and `CallbackQuery` --- crates/teloxide-core/src/types.rs | 4 + .../teloxide-core/src/types/callback_query.rs | 17 ++-- .../src/types/inaccessible_message.rs | 13 +++ .../src/types/maybe_inaccessible_message.rs | 98 +++++++++++++++++++ crates/teloxide-core/src/types/message.rs | 29 +++--- crates/teloxide-core/src/types/update.rs | 2 +- crates/teloxide/examples/buttons.rs | 3 +- .../src/dispatching/dialogue/get_chat_id.rs | 2 +- 8 files changed, 145 insertions(+), 23 deletions(-) create mode 100644 crates/teloxide-core/src/types/inaccessible_message.rs create mode 100644 crates/teloxide-core/src/types/maybe_inaccessible_message.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 3359badc..591e0ca3 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -41,6 +41,7 @@ pub use game::*; pub use game_high_score::*; pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; +pub use inaccessible_message::*; pub use inline_keyboard_button::*; pub use inline_keyboard_markup::*; pub use inline_query::*; @@ -79,6 +80,7 @@ pub use label_price::*; pub use location::*; pub use login_url::*; pub use mask_position::*; +pub use maybe_inaccessible_message::*; pub use me::*; pub use menu_button::*; pub use message::*; @@ -170,6 +172,7 @@ mod game; mod game_high_score; mod general_forum_topic_hidden; mod general_forum_topic_unhidden; +mod inaccessible_message; mod inline_keyboard_button; mod inline_keyboard_markup; mod inline_query_results_button; @@ -186,6 +189,7 @@ mod label_price; mod location; mod login_url; mod mask_position; +mod maybe_inaccessible_message; mod me; mod menu_button; mod message; diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index bb765aa0..72fbe80f 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{Message, User}; +use crate::types::{MaybeInaccessibleMessage, Message, User}; /// This object represents an incoming callback query from a callback button in /// an [inline keyboard]. @@ -24,10 +24,9 @@ pub struct CallbackQuery { /// A sender. pub from: User, - /// A message with the callback button that originated the query. Note that - /// message content and message date will not be available if the message - /// is too old. - pub message: Option, + /// Message sent by the bot with the callback button that originated the + /// query + pub message: Option, /// An identifier of the message sent via the bot in inline mode, that /// originated the query. @@ -58,7 +57,13 @@ impl CallbackQuery { use crate::util::flatten; use std::iter::once; - once(&self.from).chain(flatten(self.message.as_ref().map(Message::mentioned_users))) + once(&self.from).chain(flatten( + self.message + .as_ref() + // If we can access the message + .and_then(|maybe| maybe.message()) + .map(Message::mentioned_users), + )) } } diff --git a/crates/teloxide-core/src/types/inaccessible_message.rs b/crates/teloxide-core/src/types/inaccessible_message.rs new file mode 100644 index 00000000..8e1cd6d2 --- /dev/null +++ b/crates/teloxide-core/src/types/inaccessible_message.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId}; + +/// This object describes a message that was deleted or is otherwise +/// inaccessible to the bot. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct InaccessibleMessage { + /// Chat the message belonged to + pub chat: Chat, + /// Unique message identifier inside the chat + pub message_id: MessageId, +} diff --git a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs new file mode 100644 index 00000000..ff0d09ed --- /dev/null +++ b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs @@ -0,0 +1,98 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, InaccessibleMessage, Message, MessageId}; + +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] +pub enum MaybeInaccessibleMessage { + Inaccessible(InaccessibleMessage), + Regular(Message), +} + +impl MaybeInaccessibleMessage { + pub fn id(&self) -> MessageId { + match self { + Self::Inaccessible(i_message) => i_message.message_id, + Self::Regular(message) => message.id, + } + } + + pub fn message(&self) -> Option<&Message> { + match self { + Self::Regular(message) => Some(message), + Self::Inaccessible(_) => None, + } + } + + pub fn chat_and_id(&self) -> (&Chat, MessageId) { + (self.chat(), self.id()) + } + + pub fn chat(&self) -> &Chat { + match self { + Self::Regular(message) => &message.chat, + Self::Inaccessible(i_message) => &i_message.chat, + } + } +} + +impl<'de> Deserialize<'de> for MaybeInaccessibleMessage { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let message: Message = Message::deserialize(deserializer)?; + + // Thank you, TBA 7.0 authors! + if message.date.timestamp() == 0 { + return Ok(MaybeInaccessibleMessage::Inaccessible(InaccessibleMessage { + chat: message.chat, + message_id: message.id, + })); + } + Ok(MaybeInaccessibleMessage::Regular(message)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_inaccessible_message() { + let json = r#"{ + "chat": { + "id": 42, + "first_name": "Вадим Игоревич", + "last_name": "Сырцев", + "username": "syrtcevvi", + "type": "private" + }, + "message_id": 4, + "date": 0 + }"#; + + let inaccessible_message = serde_json::from_str::(json); + assert!(inaccessible_message.is_ok()); + assert!(matches!(inaccessible_message.unwrap(), MaybeInaccessibleMessage::Inaccessible(_))); + } + + #[test] + fn test_regular_message() { + let json = r#"{ + "chat": { + "id": 42, + "first_name": "Вадим Игоревич", + "last_name": "Сырцев", + "username": "syrtcevvi", + "type": "private" + }, + "message_id": 4, + "date": 1 + }"#; + + let regular_message = serde_json::from_str::(json); + assert!(regular_message.is_ok()); + assert!(matches!(regular_message.unwrap(), MaybeInaccessibleMessage::Regular(_))); + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 575d2a82..48cde3c6 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -8,10 +8,11 @@ use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, - MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, - PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, - ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, - VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, + MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, + MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, + Story, SuccessfulPayment, ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, + VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, + WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -245,7 +246,7 @@ pub struct MessagePinned { /// field will not contain further `reply_to_message` fields even if it /// is itself a reply. #[serde(rename = "pinned_message")] - pub pinned: Box, + pub pinned: Box, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -627,14 +628,14 @@ mod getters { use std::ops::Deref; use crate::types::{ - self, message::MessageKind::*, Chat, ChatId, ChatMigration, MediaAnimation, MediaAudio, - MediaContact, MediaDocument, MediaGame, MediaKind, MediaLocation, MediaPhoto, MediaPoll, - MediaSticker, MediaStory, MediaText, MediaVenue, MediaVideo, MediaVideoNote, MediaVoice, - Message, MessageChannelChatCreated, MessageChatShared, MessageCommon, - MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity, - MessageGroupChatCreated, MessageId, MessageInvoice, MessageLeftChatMember, - MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessageOrigin, - MessagePassportData, MessagePinned, MessageProximityAlertTriggered, + self, message::MessageKind::*, Chat, ChatId, ChatMigration, MaybeInaccessibleMessage, + MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, + MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, MediaText, MediaVenue, + MediaVideo, MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, + MessageChatShared, MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, + MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, + MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, + MessageOrigin, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUsersShared, MessageVideoChatParticipantsInvited, PhotoSize, User, }; @@ -1208,7 +1209,7 @@ mod getters { } #[must_use] - pub fn pinned_message(&self) -> Option<&Message> { + pub fn pinned_message(&self) -> Option<&MaybeInaccessibleMessage> { match &self.kind { Pinned(MessagePinned { pinned }) => Some(pinned), _ => None, diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index ed1b2cac..66906ead 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -220,7 +220,7 @@ impl Update { let chat = match &self.kind { Message(m) | EditedMessage(m) | ChannelPost(m) | EditedChannelPost(m) => &m.chat, - CallbackQuery(q) => &q.message.as_ref()?.chat, + CallbackQuery(q) => q.message.as_ref()?.chat(), ChatMember(m) => &m.chat, MyChatMember(m) => &m.chat, ChatJoinRequest(c) => &c.chat, diff --git a/crates/teloxide/examples/buttons.rs b/crates/teloxide/examples/buttons.rs index c640d708..8a2269f7 100644 --- a/crates/teloxide/examples/buttons.rs +++ b/crates/teloxide/examples/buttons.rs @@ -116,7 +116,8 @@ async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box Option { - self.message.as_ref().map(|mes| mes.chat.id) + self.message.as_ref().map(|mes| mes.chat().id) } } From 8f9b6d13cae68d2198ae7bd397e9f12a53a904c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 18 Jul 2024 18:27:23 +0300 Subject: [PATCH 07/51] Fix #1058 --- crates/teloxide-core/schema.ron | 2 +- crates/teloxide-core/src/payloads/send_poll.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index a1f2b939..e4206f77 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -1712,7 +1712,7 @@ Schema( names: ("sendPoll", "SendPoll", "send_poll"), return_ty: RawTy("Message"), doc: Doc( - md: "Use this method to send phone contacts. On success, the sent [Message] is returned.", + md: "Use this method to send a native poll. On success, the sent [Message] is returned.", md_links: {"Message": "https://core.telegram.org/bots/api#message"}, ), tg_doc: "https://core.telegram.org/bots/api#sendpoll", diff --git a/crates/teloxide-core/src/payloads/send_poll.rs b/crates/teloxide-core/src/payloads/send_poll.rs index 080b63d2..7e521263 100644 --- a/crates/teloxide-core/src/payloads/send_poll.rs +++ b/crates/teloxide-core/src/payloads/send_poll.rs @@ -8,7 +8,7 @@ use crate::types::{ }; impl_payload! { - /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. + /// Use this method to send a native poll. On success, the sent [`Message`] is returned. /// /// [`Message`]: crate::types::Message #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] From 7680d7e9789d37a208b0873558efc3f8570ba498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 18 Jul 2024 18:37:34 +0300 Subject: [PATCH 08/51] Add the `ReplyParameters` struct --- crates/teloxide-core/schema.ron | 201 +++++------------- .../src/payloads/copy_message.rs | 11 +- .../src/payloads/copy_messages.rs | 3 +- .../src/payloads/send_animation.rs | 9 +- .../teloxide-core/src/payloads/send_audio.rs | 9 +- .../src/payloads/send_contact.rs | 9 +- .../teloxide-core/src/payloads/send_dice.rs | 9 +- .../src/payloads/send_document.rs | 9 +- .../teloxide-core/src/payloads/send_game.rs | 9 +- .../src/payloads/send_invoice.rs | 11 +- .../src/payloads/send_location.rs | 9 +- .../src/payloads/send_media_group.rs | 9 +- .../src/payloads/send_message.rs | 9 +- .../teloxide-core/src/payloads/send_photo.rs | 9 +- .../teloxide-core/src/payloads/send_poll.rs | 9 +- .../src/payloads/send_sticker.rs | 9 +- .../teloxide-core/src/payloads/send_venue.rs | 9 +- .../teloxide-core/src/payloads/send_video.rs | 9 +- .../src/payloads/send_video_note.rs | 9 +- .../teloxide-core/src/payloads/send_voice.rs | 9 +- .../teloxide-core/src/serde_multipart/mod.rs | 3 +- crates/teloxide-core/src/types.rs | 14 +- crates/teloxide-core/src/types/message_id.rs | 13 +- .../src/types/reply_parameters.rs | 50 +++++ .../teloxide/examples/dispatching_features.rs | 8 +- 25 files changed, 185 insertions(+), 273 deletions(-) create mode 100644 crates/teloxide-core/src/types/reply_parameters.rs diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index e4206f77..7d6ff142 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -271,14 +271,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -445,14 +440,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -471,9 +461,8 @@ Schema( names: ("copyMessages", "CopyMessages", "copy_messages"), return_ty: ArrayOf(RawTy("MessageId")), doc: Doc( - md: "Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz [poll] can be copied only if the value of the field _correct\\_option\\_id_ is known to the bot. The method is analogous to the method [forwardMessages], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [MessageId] of the sent messages is returned.", + md: "Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field _correct\\_option\\_id_ is known to the bot. The method is analogous to the method [forwardMessages], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [MessageId] of the sent messages is returned.", md_links: { - "poll": "https://core.telegram.org/bots/api#poll", "forwardMessages": "https://core.telegram.org/bots/api#forwardmessages", "MessageId": "https://core.telegram.org/bots/api#messageid" }, @@ -586,14 +575,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -694,14 +678,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -789,14 +768,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -907,14 +881,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1018,14 +987,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1104,14 +1068,9 @@ Schema( ) ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1189,14 +1148,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1251,14 +1205,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), ], ), @@ -1329,14 +1278,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1607,14 +1551,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1686,14 +1625,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1805,14 +1739,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -1866,14 +1795,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -3697,14 +3621,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -4168,14 +4087,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", @@ -4419,14 +4333,9 @@ Schema( descr: Doc(md: "Protects the contents of sent messages from forwarding and saving"), ), Param( - name: "reply_to_message_id", - ty: Option(RawTy("MessageId")), - descr: Doc(md: "If the message is a reply, ID of the original message") - ), - Param( - name: "allow_sending_without_reply", - ty: Option(bool), - descr: Doc(md: "Pass _True_, if the message should be sent even if the specified replied-to message is not found") + name: "reply_parameters", + ty: Option(RawTy("ReplyParameters")), + descr: Doc(md: "Description of the message to reply to"), ), Param( name: "reply_markup", diff --git a/crates/teloxide-core/src/payloads/copy_message.rs b/crates/teloxide-core/src/payloads/copy_message.rs index 479cbae4..daa67928 100644 --- a/crates/teloxide-core/src/payloads/copy_message.rs +++ b/crates/teloxide-core/src/payloads/copy_message.rs @@ -2,7 +2,9 @@ use serde::Serialize; -use crate::types::{MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{ + MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to copy messages of any kind. The method is analogous to the method forwardMessage, but the copied message doesn't have a link to the original message. Returns the [`MessageId`] of the sent message on success. @@ -36,11 +38,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/copy_messages.rs b/crates/teloxide-core/src/payloads/copy_messages.rs index 033a5c64..90a4cd90 100644 --- a/crates/teloxide-core/src/payloads/copy_messages.rs +++ b/crates/teloxide-core/src/payloads/copy_messages.rs @@ -5,10 +5,9 @@ use serde::Serialize; use crate::types::{MessageId, Recipient, ThreadId}; impl_payload! { - /// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz [`Poll`] can be copied only if the value of the field _correct\_option\_id_ is known to the bot. The method is analogous to the method [`ForwardMessages`], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [`MessageId`] of the sent messages is returned. + /// Use this method to copy messages of any kind. If some of the specified messages can't be found or copied, they are skipped. Service messages, giveaway messages, giveaway winners messages, and invoice messages can't be copied. A quiz poll can be copied only if the value of the field _correct\_option\_id_ is known to the bot. The method is analogous to the method [`ForwardMessages`], but the copied messages don't have a link to the original message. Album grouping is kept for copied messages. On success, an array of [`MessageId`] of the sent messages is returned. /// /// [`MessageId`]: crate::types::MessageId - /// [`Poll`]: crate::payloads::Poll /// [`ForwardMessages`]: crate::payloads::ForwardMessages #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] pub CopyMessages (CopyMessagesSetters) => Vec { diff --git a/crates/teloxide-core/src/payloads/send_animation.rs b/crates/teloxide-core/src/payloads/send_animation.rs index 500a66a5..ba2348ee 100644 --- a/crates/teloxide-core/src/payloads/send_animation.rs +++ b/crates/teloxide-core/src/payloads/send_animation.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -50,11 +50,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_audio.rs b/crates/teloxide-core/src/payloads/send_audio.rs index fcb12f4d..2c579ec8 100644 --- a/crates/teloxide-core/src/payloads/send_audio.rs +++ b/crates/teloxide-core/src/payloads/send_audio.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -51,11 +51,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_contact.rs b/crates/teloxide-core/src/payloads/send_contact.rs index 7179cbcc..b350b1b5 100644 --- a/crates/teloxide-core/src/payloads/send_contact.rs +++ b/crates/teloxide-core/src/payloads/send_contact.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send phone contacts. On success, the sent [`Message`] is returned. @@ -33,11 +33,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_dice.rs b/crates/teloxide-core/src/payloads/send_dice.rs index 2a00cbd8..2444c39b 100644 --- a/crates/teloxide-core/src/payloads/send_dice.rs +++ b/crates/teloxide-core/src/payloads/send_dice.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{DiceEmoji, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{DiceEmoji, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send an animated emoji that will display a random value. On success, the sent [`Message`] is returned. @@ -25,11 +25,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_document.rs b/crates/teloxide-core/src/payloads/send_document.rs index cfe41cc4..7f570058 100644 --- a/crates/teloxide-core/src/payloads/send_document.rs +++ b/crates/teloxide-core/src/payloads/send_document.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -44,11 +44,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_game.rs b/crates/teloxide-core/src/payloads/send_game.rs index bb2b02c2..0b355e1f 100644 --- a/crates/teloxide-core/src/payloads/send_game.rs +++ b/crates/teloxide-core/src/payloads/send_game.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{ChatId, Message, MessageId, ReplyMarkup, ThreadId}; +use crate::types::{ChatId, Message, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send a game. On success, the sent [`Message`] is returned. @@ -25,11 +25,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Play game_title' button will be shown. If not empty, the first button must launch the game. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_invoice.rs b/crates/teloxide-core/src/payloads/send_invoice.rs index 278c7dcf..3c199157 100644 --- a/crates/teloxide-core/src/payloads/send_invoice.rs +++ b/crates/teloxide-core/src/payloads/send_invoice.rs @@ -3,7 +3,9 @@ use serde::Serialize; use url::Url; -use crate::types::{InlineKeyboardMarkup, LabeledPrice, Message, MessageId, Recipient, ThreadId}; +use crate::types::{ + InlineKeyboardMarkup, LabeledPrice, Message, Recipient, ReplyParameters, ThreadId, +}; impl_payload! { /// Use this method to send invoices. On success, the sent [`Message`] is returned. @@ -70,11 +72,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// A JSON-serialized object for an [inline keyboard]. If empty, one 'Pay `total price`' button will be shown. If not empty, the first button must be a Pay button. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_location.rs b/crates/teloxide-core/src/payloads/send_location.rs index 5d61cbe1..f1e0f736 100644 --- a/crates/teloxide-core/src/payloads/send_location.rs +++ b/crates/teloxide-core/src/payloads/send_location.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send point on the map. On success, the sent [`Message`] is returned. @@ -37,11 +37,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_media_group.rs b/crates/teloxide-core/src/payloads/send_media_group.rs index f5300c84..0098ead4 100644 --- a/crates/teloxide-core/src/payloads/send_media_group.rs +++ b/crates/teloxide-core/src/payloads/send_media_group.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{InputMedia, Message, MessageId, Recipient, ThreadId}; +use crate::types::{InputMedia, Message, Recipient, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send a group of photos, videos, documents or audios as an album. Documents and audio files can be only grouped in an album with messages of the same type. On success, an array of [`Message`]s that were sent is returned. @@ -34,11 +34,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, } } } diff --git a/crates/teloxide-core/src/payloads/send_message.rs b/crates/teloxide-core/src/payloads/send_message.rs index b243aec3..129c860a 100644 --- a/crates/teloxide-core/src/payloads/send_message.rs +++ b/crates/teloxide-core/src/payloads/send_message.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -35,11 +35,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_photo.rs b/crates/teloxide-core/src/payloads/send_photo.rs index 0c89cc90..452b16e7 100644 --- a/crates/teloxide-core/src/payloads/send_photo.rs +++ b/crates/teloxide-core/src/payloads/send_photo.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -40,11 +40,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_poll.rs b/crates/teloxide-core/src/payloads/send_poll.rs index 7e521263..aaa69580 100644 --- a/crates/teloxide-core/src/payloads/send_poll.rs +++ b/crates/teloxide-core/src/payloads/send_poll.rs @@ -4,7 +4,7 @@ use chrono::{DateTime, Utc}; use serde::Serialize; use crate::types::{ - Message, MessageEntity, MessageId, ParseMode, PollType, Recipient, ReplyMarkup, ThreadId, + Message, MessageEntity, ParseMode, PollType, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -54,11 +54,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_sticker.rs b/crates/teloxide-core/src/payloads/send_sticker.rs index 72dd9e2f..13678400 100644 --- a/crates/teloxide-core/src/payloads/send_sticker.rs +++ b/crates/teloxide-core/src/payloads/send_sticker.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{InputFile, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{InputFile, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { @[multipart = sticker] @@ -30,11 +30,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_venue.rs b/crates/teloxide-core/src/payloads/send_venue.rs index 59fcdcbb..3ccca409 100644 --- a/crates/teloxide-core/src/payloads/send_venue.rs +++ b/crates/teloxide-core/src/payloads/send_venue.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { /// Use this method to send information about a venue. On success, the sent [`Message`] is returned. @@ -41,11 +41,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_video.rs b/crates/teloxide-core/src/payloads/send_video.rs index 4ecfc6bf..be52eb1a 100644 --- a/crates/teloxide-core/src/payloads/send_video.rs +++ b/crates/teloxide-core/src/payloads/send_video.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -53,11 +53,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_video_note.rs b/crates/teloxide-core/src/payloads/send_video_note.rs index 5b7afd9a..f4387c94 100644 --- a/crates/teloxide-core/src/payloads/send_video_note.rs +++ b/crates/teloxide-core/src/payloads/send_video_note.rs @@ -2,7 +2,7 @@ use serde::Serialize; -use crate::types::{InputFile, Message, MessageId, Recipient, ReplyMarkup, ThreadId}; +use crate::types::{InputFile, Message, Recipient, ReplyMarkup, ReplyParameters, ThreadId}; impl_payload! { @[multipart = video_note, thumbnail] @@ -37,11 +37,8 @@ impl_payload! { pub disable_notification: bool, /// Protects the contents of sent messages from forwarding and saving pub protect_content: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_voice.rs b/crates/teloxide-core/src/payloads/send_voice.rs index 5e5293ed..a7d3b139 100644 --- a/crates/teloxide-core/src/payloads/send_voice.rs +++ b/crates/teloxide-core/src/payloads/send_voice.rs @@ -3,7 +3,7 @@ use serde::Serialize; use crate::types::{ - InputFile, Message, MessageEntity, MessageId, ParseMode, Recipient, ReplyMarkup, ThreadId, + InputFile, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, }; impl_payload! { @@ -40,11 +40,8 @@ impl_payload! { /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages pub disable_notification: bool, - /// If the message is a reply, ID of the original message - #[serde(serialize_with = "crate::types::serialize_reply_to_message_id")] - pub reply_to_message_id: MessageId, - /// Pass _True_, if the message should be sent even if the specified replied-to message is not found - pub allow_sending_without_reply: bool, + /// Description of the message to reply to + pub reply_parameters: ReplyParameters, /// Additional interface options. A JSON-serialized object for an [inline keyboard], [custom reply keyboard], instructions to remove reply keyboard or to force a reply from the user. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/serde_multipart/mod.rs b/crates/teloxide-core/src/serde_multipart/mod.rs index 0c2966e7..a83af2b3 100644 --- a/crates/teloxide-core/src/serde_multipart/mod.rs +++ b/crates/teloxide-core/src/serde_multipart/mod.rs @@ -173,8 +173,7 @@ mod tests { .caption_entities(entities()) .thumbnail(InputFile::read( File::open("../../media/teloxide-core-logo.png").await.unwrap(), - )) - .allow_sending_without_reply(true), + )), ) .unwrap() .await; diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 591e0ca3..827fbc3e 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -102,6 +102,7 @@ pub use proximity_alert_triggered::*; pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; +pub use reply_parameters::*; pub use response_parameters::*; pub use sent_web_app_message::*; pub use shipping_address::*; @@ -208,6 +209,7 @@ mod proximity_alert_triggered; mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; +mod reply_parameters; mod response_parameters; mod sent_web_app_message; mod shipping_address; @@ -286,8 +288,6 @@ pub use recipient::*; pub use seconds::*; pub use user_id::*; -use serde::Serialize; - /// Converts an `i64` timestamp to a `choro::DateTime`, producing serde error /// for invalid timestamps pub(crate) fn serde_timestamp( @@ -446,16 +446,6 @@ pub(crate) mod option_msg_id_as_int { } } -pub(crate) fn serialize_reply_to_message_id( - this: &Option, - serializer: S, -) -> Result -where - S: serde::Serializer, -{ - this.map(|MessageId(id)| id).serialize(serializer) -} - pub(crate) mod serde_rgb { use serde::{de::Visitor, Deserializer, Serializer}; diff --git a/crates/teloxide-core/src/types/message_id.rs b/crates/teloxide-core/src/types/message_id.rs index d0f1212b..8d2e573a 100644 --- a/crates/teloxide-core/src/types/message_id.rs +++ b/crates/teloxide-core/src/types/message_id.rs @@ -1,7 +1,18 @@ use serde::{Deserialize, Serialize}; /// A unique message identifier. -#[derive(Clone, Copy, Debug, derive_more::Display, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive( + Default, + Clone, + Copy, + Debug, + derive_more::Display, + PartialEq, + Eq, + Hash, + Serialize, + Deserialize +)] #[serde(from = "MessageIdRaw", into = "MessageIdRaw")] pub struct MessageId(pub i32); diff --git a/crates/teloxide-core/src/types/reply_parameters.rs b/crates/teloxide-core/src/types/reply_parameters.rs new file mode 100644 index 00000000..c39abfcf --- /dev/null +++ b/crates/teloxide-core/src/types/reply_parameters.rs @@ -0,0 +1,50 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MessageId, Recipient}; + +/// Describes reply parameters for the message that is being sent. +#[serde_with::skip_serializing_none] +#[derive(Default, Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ReplyParameters { + /// Identifier of the message that will be replied to in the current chat, + /// or in the chat _chat\_id_ if it is specified + pub message_id: MessageId, + /// If the message to be replied to is from a different chat, unique + /// identifier for the chat or username of the channel (in the format + /// `@channelusername`) + pub chat_id: Option, + /// Pass _true_ if the message should be sent even if the specified message + /// to be replied to is not found; can be used only for replies in the + /// same chat and forum topic. + pub allow_sending_without_reply: Option, + /// Quoted part of the message to be replied to; 0-1024 characters after + /// entities parsing. The quote must be an exact substring of the message to + /// be replied to, including _bold_, _italic_, _underline_, _strikethrough_, + /// _spoiler_, and _custom_emoji_ entities. The message will fail to send if + /// the quote isn't found in the original message. + pub quote: Option, +} + +impl ReplyParameters { + pub fn new(message_id: MessageId) -> Self { + Self { message_id, ..Self::default() } + } + + /// Setter for the `chat_id` field + pub fn chat_id(mut self, chat_id: Recipient) -> Self { + self.chat_id = Some(chat_id); + self + } + + /// Sets the `allow_sending_without_reply_field` to _true_ + pub fn allow_sending_without_reply(mut self) -> Self { + self.allow_sending_without_reply = Some(true); + self + } + + /// Setter for the `quote` field + pub fn quote(mut self, quote: String) -> Self { + self.quote = Some(quote); + self + } +} diff --git a/crates/teloxide/examples/dispatching_features.rs b/crates/teloxide/examples/dispatching_features.rs index 1082c29f..e5739a96 100644 --- a/crates/teloxide/examples/dispatching_features.rs +++ b/crates/teloxide/examples/dispatching_features.rs @@ -3,7 +3,11 @@ use rand::Rng; -use teloxide::{prelude::*, types::Dice, utils::command::BotCommands}; +use teloxide::{ + prelude::*, + types::{Dice, ReplyParameters}, + utils::command::BotCommands, +}; #[tokio::main] async fn main() { @@ -60,7 +64,7 @@ async fn main() { // filter only messages with dices. Message::filter_dice().endpoint(|bot: Bot, msg: Message, dice: Dice| async move { bot.send_message(msg.chat.id, format!("Dice value: {}", dice.value)) - .reply_to_message_id(msg.id) + .reply_parameters(ReplyParameters::new(msg.id)) .await?; Ok(()) }), From 298df305f09b72beb5a5ac639d74afbbc7e753ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 18 Jul 2024 18:47:33 +0300 Subject: [PATCH 09/51] Add the quote field of type`TextQuote` to the `Message` struct --- crates/teloxide-core/src/types.rs | 2 ++ crates/teloxide-core/src/types/message.rs | 20 ++++++++++++---- crates/teloxide-core/src/types/text_quote.rs | 24 ++++++++++++++++++++ crates/teloxide-core/src/types/update.rs | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 crates/teloxide-core/src/types/text_quote.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 827fbc3e..707eb90f 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -114,6 +114,7 @@ pub use story::*; pub use successful_payment::*; pub use switch_inline_query_chosen_chat::*; pub use target_message::*; +pub use text_quote::*; pub use thread_id::*; pub use unit_false::*; pub use unit_true::*; @@ -221,6 +222,7 @@ mod story; mod successful_payment; mod switch_inline_query_chosen_chat; mod target_message; +mod text_quote; mod thread_id; mod unit_false; mod unit_true; diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 48cde3c6..ccbacbe5 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -10,9 +10,9 @@ use crate::types::{ GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, - Story, SuccessfulPayment, ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, - VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, - WebAppData, WriteAccessAllowed, + Story, SuccessfulPayment, TextQuote, ThreadId, True, User, UsersShared, Venue, Video, + VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, + Voice, WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -110,6 +110,10 @@ pub struct MessageCommon { /// itself is a reply. pub reply_to_message: Option>, + /// For replies that quote part of the original message, the quoted part of + /// the message + pub quote: Option, + /// Date the message was last edited in Unix time. #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub edit_date: Option>, @@ -637,7 +641,7 @@ mod getters { MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessageOrigin, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUsersShared, - MessageVideoChatParticipantsInvited, PhotoSize, User, + MessageVideoChatParticipantsInvited, PhotoSize, TextQuote, User, }; use super::{ @@ -686,6 +690,14 @@ mod getters { } } + #[must_use] + pub fn quote(&self) -> Option<&TextQuote> { + match &self.kind { + Common(MessageCommon { quote, .. }) => quote.as_ref(), + _ => None, + } + } + #[must_use] pub fn forward_date(&self) -> Option> { self.forward_origin().map(|f| f.date()) diff --git a/crates/teloxide-core/src/types/text_quote.rs b/crates/teloxide-core/src/types/text_quote.rs new file mode 100644 index 00000000..eda8811e --- /dev/null +++ b/crates/teloxide-core/src/types/text_quote.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::MessageEntity; + +/// This object contains information about the quoted part of a message that is +/// replied to by the given message. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct TextQuote { + /// Text of the quoted part of a message that is replied to by the given + /// message + pub text: String, + /// Special entities that appear in the quote. Currently, only _bold_, + /// _italic_, _underline_, _strikethrough_, _spoiler_, and + /// _custom_emoji_ entities are kept in quotes. + #[serde(default)] + pub entities: Vec, + /// Approximate quote position in the original message in UTF-16 code units + /// as specified by the sender + pub position: u32, + /// True, if the quote was chosen manually by the message sender. Otherwise, + /// the quote was added automatically by the server. + #[serde(default)] + pub is_manual: bool, +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 66906ead..e3e3e14c 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -456,6 +456,7 @@ mod test { }), reply_to_message: None, forward_origin: None, + quote: None, edit_date: None, media_kind: MediaKind::Text(MediaText { text: String::from("hello there"), From c0f97fae0ec1666a12ae0de6aff98cdcd6b6bf36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Thu, 18 Jul 2024 18:47:51 +0300 Subject: [PATCH 10/51] Fix docs for KeyboardButtonRequestUsers --- crates/teloxide-core/src/types/keyboard_button_request_user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/keyboard_button_request_user.rs b/crates/teloxide-core/src/types/keyboard_button_request_user.rs index 8df5cc4a..9636cf16 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_user.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_user.rs @@ -31,7 +31,7 @@ pub struct KeyboardButtonRequestUsers { } impl KeyboardButtonRequestUsers { - /// Creates a new [`KeyboardButtonRequestUser`]. + /// Creates a new [`KeyboardButtonRequestUsers`]. pub fn new(request_id: i32) -> Self { Self { request_id, user_is_bot: None, user_is_premium: None, max_quantity: 1 } } From 62da0027e34fe83926764672613a8676367dc805 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 16 Jul 2024 16:48:23 +0400 Subject: [PATCH 11/51] Add `Giveaway` struct --- crates/teloxide-core/src/types.rs | 2 + crates/teloxide-core/src/types/giveaway.rs | 45 ++++++++++++++++++++++ crates/teloxide-core/src/types/message.rs | 23 ++++++++--- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 crates/teloxide-core/src/types/giveaway.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 707eb90f..6fc26cab 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -41,6 +41,7 @@ pub use game::*; pub use game_high_score::*; pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; +pub use giveaway::*; pub use inaccessible_message::*; pub use inline_keyboard_button::*; pub use inline_keyboard_markup::*; @@ -174,6 +175,7 @@ mod game; mod game_high_score; mod general_forum_topic_hidden; mod general_forum_topic_unhidden; +mod giveaway; mod inaccessible_message; mod inline_keyboard_button; mod inline_keyboard_markup; diff --git a/crates/teloxide-core/src/types/giveaway.rs b/crates/teloxide-core/src/types/giveaway.rs new file mode 100644 index 00000000..da0e6ffd --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway.rs @@ -0,0 +1,45 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::Chat; + +/// This object represents a message about a scheduled giveaway. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Giveaway { + /// The list of chats which the user must join to participate in the + /// giveaway. + pub chats: Vec, + + /// Point in time (Unix timestamp) when winners of the giveaway will be + /// selected + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub winners_selection_date: DateTime, + + /// The number of users which are supposed to be selected as winners of the + /// giveaway + pub winner_count: u32, + + /// `true`, if only users who join the chats after the giveaway started + /// should be eligible to win + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub only_new_members: bool, + + /// `true`, if the list of giveaway winners will be visible to everyone + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub has_public_winners: bool, + + /// Description of additional giveaway prize + pub prize_description: Option, + + /// A list of two-letter [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) country codes indicating the + /// countries from which eligible users for the giveaway must come. If + /// empty, then all users can participate in the giveaway. Users with a + /// phone number that was bought on Fragment can always participate in + /// giveaways. + pub country_codes: Option>, + + /// The number of months the Telegram Premium subscription won from the + /// giveaway will be active for + pub premium_subscription_month_count: Option, +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index ccbacbe5..3c097084 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -7,12 +7,12 @@ use url::Url; use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, - MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, - MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, - Story, SuccessfulPayment, TextQuote, ThreadId, True, User, UsersShared, Venue, Video, - VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, - Voice, WebAppData, WriteAccessAllowed, + GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, InlineKeyboardMarkup, Invoice, + Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, + MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, + ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, TextQuote, ThreadId, True, User, + UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, + VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -76,6 +76,7 @@ pub enum MessageKind { ForumTopicReopened(MessageForumTopicReopened), GeneralForumTopicHidden(MessageGeneralForumTopicHidden), GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden), + Giveaway(MessageGiveaway), VideoChatScheduled(MessageVideoChatScheduled), VideoChatStarted(MessageVideoChatStarted), VideoChatEnded(MessageVideoChatEnded), @@ -592,6 +593,16 @@ pub struct MessageGeneralForumTopicUnhidden { pub general_forum_topic_unhidden: GeneralForumTopicUnhidden, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveaway { + /// Message is giveaway, information about a scheduled giveaway. [More about + /// giveaways »] + /// + /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway: Giveaway, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageVideoChatScheduled { From fa1b793c7f45861fb23a8f734d663d358274020c Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 16 Jul 2024 16:53:13 +0400 Subject: [PATCH 12/51] Add `GiveawayCreated` struct --- crates/teloxide-core/src/types.rs | 2 ++ .../src/types/giveaway_created.rs | 7 ++++++ crates/teloxide-core/src/types/message.rs | 24 ++++++++++++++----- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 crates/teloxide-core/src/types/giveaway_created.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 6fc26cab..a55d9dad 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -42,6 +42,7 @@ pub use game_high_score::*; pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; pub use giveaway::*; +pub use giveaway_created::*; pub use inaccessible_message::*; pub use inline_keyboard_button::*; pub use inline_keyboard_markup::*; @@ -176,6 +177,7 @@ mod game_high_score; mod general_forum_topic_hidden; mod general_forum_topic_unhidden; mod giveaway; +mod giveaway_created; mod inaccessible_message; mod inline_keyboard_button; mod inline_keyboard_markup; diff --git a/crates/teloxide-core/src/types/giveaway_created.rs b/crates/teloxide-core/src/types/giveaway_created.rs new file mode 100644 index 00000000..eeee1d74 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_created.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +/// This object represents a service message about the creation of a scheduled +/// giveaway. Currently holds no information. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct GiveawayCreated {} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 3c097084..ee85e240 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -7,12 +7,13 @@ use url::Url; use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, InlineKeyboardMarkup, Invoice, - Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, - MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, - ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, TextQuote, ThreadId, True, User, - UsersShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, - VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, + GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCreated, + InlineKeyboardMarkup, Invoice, Location, MaybeInaccessibleMessage, + MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, + PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, + TextQuote, ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, + VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, + WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -77,6 +78,7 @@ pub enum MessageKind { GeneralForumTopicHidden(MessageGeneralForumTopicHidden), GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden), Giveaway(MessageGiveaway), + GiveawayCreated(MessageGiveawayCreated), VideoChatScheduled(MessageVideoChatScheduled), VideoChatStarted(MessageVideoChatStarted), VideoChatEnded(MessageVideoChatEnded), @@ -603,6 +605,16 @@ pub struct MessageGiveaway { pub giveaway: Giveaway, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayCreated { + /// Service message: a scheduled 'Giveaway' created. [More about giveaways + /// »] + /// + /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_created: GiveawayCreated, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageVideoChatScheduled { From 9d803693fbc20d69626719612f47a34de4dcbfc9 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 16 Jul 2024 17:12:13 +0400 Subject: [PATCH 13/51] Add `GiveawayWinners` struct --- crates/teloxide-core/src/types.rs | 2 + .../src/types/giveaway_winners.rs | 55 +++++++++++++++++++ crates/teloxide-core/src/types/message.rs | 13 ++++- 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 crates/teloxide-core/src/types/giveaway_winners.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index a55d9dad..ff89de7e 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -43,6 +43,7 @@ pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; pub use giveaway::*; pub use giveaway_created::*; +pub use giveaway_winners::*; pub use inaccessible_message::*; pub use inline_keyboard_button::*; pub use inline_keyboard_markup::*; @@ -178,6 +179,7 @@ mod general_forum_topic_hidden; mod general_forum_topic_unhidden; mod giveaway; mod giveaway_created; +mod giveaway_winners; mod inaccessible_message; mod inline_keyboard_button; mod inline_keyboard_markup; diff --git a/crates/teloxide-core/src/types/giveaway_winners.rs b/crates/teloxide-core/src/types/giveaway_winners.rs new file mode 100644 index 00000000..9ef4dee4 --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_winners.rs @@ -0,0 +1,55 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use serde_with::with_prefix; + +use crate::types::{Chat, MessageId, User}; + +with_prefix!(prefix_giveaway "giveaway_"); + +/// This object represents a message about the completion of a giveaway with +/// public winners. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GiveawayWinners { + /// The chat that created the giveaway + pub chat: Chat, + + /// Identifier of the messsage with the giveaway in the chat + #[serde(flatten, with = "prefix_giveaway")] + pub giveaway_message_id: MessageId, + + /// Point in time (Unix timestamp) when winners of the giveaway were + /// selected + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub winners_selection_date: DateTime, + + /// Total number of winners in the giveaway + pub winner_count: u32, + + /// List of up to 100 winners of the giveaway + pub winners: Vec, + + /// The number of other chats the user had to join in order to be eligible + /// for the giveaway + pub additional_chat_count: Option, + + /// The number of months the Telegram Premium subscription won from the + /// giveaway will be active for + pub premium_subscription_month_count: Option, + + /// Number of undistributed prizes + pub unclaimed_prize_count: Option, + + /// `true`, if only users who had joined the chats after the giveaway + /// started were eligible to win + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub only_new_members: bool, + + /// `true`, if the giveaway was canceled because the payment for it was + /// refunded + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub was_refunded: bool, + + /// Description of additional giveaway prize + pub prize_description: Option, +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index ee85e240..24aafe08 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -7,7 +7,7 @@ use url::Url; use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCreated, + GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, @@ -79,6 +79,7 @@ pub enum MessageKind { GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden), Giveaway(MessageGiveaway), GiveawayCreated(MessageGiveawayCreated), + GiveawayWinners(MessageGiveawayWinners), VideoChatScheduled(MessageVideoChatScheduled), VideoChatStarted(MessageVideoChatStarted), VideoChatEnded(MessageVideoChatEnded), @@ -615,6 +616,16 @@ pub struct MessageGiveawayCreated { pub giveaway_created: GiveawayCreated, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayWinners { + /// Message is giveaway winners, information about the completion of a + /// giveaway with public winners. [More about giveaways »] + /// + /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_winners: GiveawayWinners, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageVideoChatScheduled { From d9587ea562df99bdb8fdd457ac5bb8d2cab86e25 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 16 Jul 2024 17:33:48 +0400 Subject: [PATCH 14/51] Add `GiveawayCompleted` struct --- crates/teloxide-core/src/types.rs | 2 ++ .../src/types/giveaway_completed.rs | 18 +++++++++++++ crates/teloxide-core/src/types/message.rs | 25 +++++++++++++------ 3 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 crates/teloxide-core/src/types/giveaway_completed.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index ff89de7e..deb64378 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -42,6 +42,7 @@ pub use game_high_score::*; pub use general_forum_topic_hidden::*; pub use general_forum_topic_unhidden::*; pub use giveaway::*; +pub use giveaway_completed::*; pub use giveaway_created::*; pub use giveaway_winners::*; pub use inaccessible_message::*; @@ -178,6 +179,7 @@ mod game_high_score; mod general_forum_topic_hidden; mod general_forum_topic_unhidden; mod giveaway; +mod giveaway_completed; mod giveaway_created; mod giveaway_winners; mod inaccessible_message; diff --git a/crates/teloxide-core/src/types/giveaway_completed.rs b/crates/teloxide-core/src/types/giveaway_completed.rs new file mode 100644 index 00000000..face804d --- /dev/null +++ b/crates/teloxide-core/src/types/giveaway_completed.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::Message; + +/// This object represents a service message about the completion of a giveaway +/// without public winners. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct GiveawayCompleted { + /// Number of winners in the giveaway + pub winner_count: u32, + + /// Number of undistributed prizes + pub unclaimed_prize_count: Option, + + /// Message with the giveaway that was completed, if it wasn't deleted + pub giveaway_message: Option>, +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 24aafe08..e03fe262 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -7,13 +7,13 @@ use url::Url; use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCreated, GiveawayWinners, - InlineKeyboardMarkup, Invoice, Location, MaybeInaccessibleMessage, - MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, - PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, - TextQuote, ThreadId, True, User, UsersShared, Venue, Video, VideoChatEnded, - VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, - WebAppData, WriteAccessAllowed, + GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCompleted, + GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, Location, + MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, + MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, + Story, SuccessfulPayment, TextQuote, ThreadId, True, User, UsersShared, Venue, Video, + VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, + Voice, WebAppData, WriteAccessAllowed, }; /// This object represents a message. @@ -78,6 +78,7 @@ pub enum MessageKind { GeneralForumTopicHidden(MessageGeneralForumTopicHidden), GeneralForumTopicUnhidden(MessageGeneralForumTopicUnhidden), Giveaway(MessageGiveaway), + GiveawayCompleted(MessageGiveawayCompleted), GiveawayCreated(MessageGiveawayCreated), GiveawayWinners(MessageGiveawayWinners), VideoChatScheduled(MessageVideoChatScheduled), @@ -606,6 +607,16 @@ pub struct MessageGiveaway { pub giveaway: Giveaway, } +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageGiveawayCompleted { + /// Service message: a 'Giveaway' completed. [More about giveaways + /// »] + /// + /// [More about giveaways »]: https://core.telegram.org/api#giveaways-amp-gifts + pub giveaway_completed: GiveawayCompleted, +} + #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageGiveawayCreated { From fda1890425e7ee9065ae2f59d17163db05101beb Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 00:37:10 +0400 Subject: [PATCH 15/51] Add `giveaway*` getters to `Message` --- crates/teloxide-core/src/types/message.rs | 41 ++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index e03fe262..23bf79ed 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -692,7 +692,8 @@ mod getters { use super::{ MessageForumTopicClosed, MessageForumTopicCreated, MessageForumTopicEdited, MessageForumTopicReopened, MessageGeneralForumTopicHidden, - MessageGeneralForumTopicUnhidden, MessageMessageAutoDeleteTimerChanged, + MessageGeneralForumTopicUnhidden, MessageGiveaway, MessageGiveawayCompleted, + MessageGiveawayCreated, MessageGiveawayWinners, MessageMessageAutoDeleteTimerChanged, MessageVideoChatEnded, MessageVideoChatScheduled, MessageVideoChatStarted, MessageWebAppData, MessageWriteAccessAllowed, }; @@ -1413,6 +1414,44 @@ mod getters { } } + #[must_use] + pub fn giveaway(&self) -> Option<&types::Giveaway> { + match &self.kind { + Giveaway(MessageGiveaway { giveaway }) => Some(giveaway), + _ => None, + } + } + + #[must_use] + pub fn giveaway_completed(&self) -> Option<&types::GiveawayCompleted> { + match &self.kind { + GiveawayCompleted(MessageGiveawayCompleted { giveaway_completed }) => { + Some(giveaway_completed) + } + _ => None, + } + } + + #[must_use] + pub fn giveaway_created(&self) -> Option<&types::GiveawayCreated> { + match &self.kind { + GiveawayCreated(MessageGiveawayCreated { giveaway_created }) => { + Some(giveaway_created) + } + _ => None, + } + } + + #[must_use] + pub fn giveaway_winners(&self) -> Option<&types::GiveawayWinners> { + match &self.kind { + GiveawayWinners(MessageGiveawayWinners { giveaway_winners }) => { + Some(giveaway_winners) + } + _ => None, + } + } + #[must_use] pub fn video_chat_scheduled(&self) -> Option<&types::VideoChatScheduled> { match &self.kind { From dc3660b30898cfd88ea3beb64f3ba5559a3acf88 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 19:15:49 +0400 Subject: [PATCH 16/51] Add giveaway* tests --- crates/teloxide-core/src/types/giveaway.rs | 25 ++ .../src/types/giveaway_completed.rs | 43 +++ .../src/types/giveaway_created.rs | 11 + .../src/types/giveaway_winners.rs | 31 ++ crates/teloxide-core/src/types/message.rs | 306 ++++++++++++++++++ 5 files changed, 416 insertions(+) diff --git a/crates/teloxide-core/src/types/giveaway.rs b/crates/teloxide-core/src/types/giveaway.rs index da0e6ffd..6865bf2a 100644 --- a/crates/teloxide-core/src/types/giveaway.rs +++ b/crates/teloxide-core/src/types/giveaway.rs @@ -43,3 +43,28 @@ pub struct Giveaway { /// giveaway will be active for pub premium_subscription_month_count: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_completed.rs b/crates/teloxide-core/src/types/giveaway_completed.rs index face804d..888395e4 100644 --- a/crates/teloxide-core/src/types/giveaway_completed.rs +++ b/crates/teloxide-core/src/types/giveaway_completed.rs @@ -16,3 +16,46 @@ pub struct GiveawayCompleted { /// Message with the giveaway that was completed, if it wasn't deleted pub giveaway_message: Option>, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "winner_count": 0, + "unclaimed_prize_count": 1, + "giveaway_message": { + "message_id": 24, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721161230, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_created.rs b/crates/teloxide-core/src/types/giveaway_created.rs index eeee1d74..de894c02 100644 --- a/crates/teloxide-core/src/types/giveaway_created.rs +++ b/crates/teloxide-core/src/types/giveaway_created.rs @@ -5,3 +5,14 @@ use serde::{Deserialize, Serialize}; #[serde_with::skip_serializing_none] #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct GiveawayCreated {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#"{}"#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/giveaway_winners.rs b/crates/teloxide-core/src/types/giveaway_winners.rs index 9ef4dee4..9dddc3e6 100644 --- a/crates/teloxide-core/src/types/giveaway_winners.rs +++ b/crates/teloxide-core/src/types/giveaway_winners.rs @@ -53,3 +53,34 @@ pub struct GiveawayWinners { /// Description of additional giveaway prize pub prize_description: Option, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "giveaway_message_id": 27, + "winners_selection_date": 1721162701, + "premium_subscription_month_count": 6, + "winner_count": 1, + "winners": [ + { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10" + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 23bf79ed..a862303b 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1758,6 +1758,7 @@ impl Message { #[cfg(test)] mod tests { + use chrono::DateTime; use cool_asserts::assert_matches; use serde_json::from_str; @@ -2320,4 +2321,309 @@ mod tests { let _: Message = serde_json::from_str(json).unwrap(); } + + #[test] + fn giveaway() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway().unwrap(), + &Giveaway { + chats: vec![Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + }], + winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), + winner_count: 1, + only_new_members: false, + has_public_winners: true, + prize_description: None, + country_codes: None, + premium_subscription_month_count: Some(6) + } + ) + } + + #[test] + fn giveaway_created() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway_created": {} + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!(message.giveaway_created().unwrap(), &GiveawayCreated {}) + } + + #[test] + fn giveaway_completed() { + let json = r#"{ + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway_completed": { + "winner_count": 0, + "unclaimed_prize_count": 1, + "giveaway_message": { + "message_id": 24, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721161230, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + } + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway_completed().unwrap(), + &GiveawayCompleted { + winner_count: 0, + unclaimed_prize_count: Some(1), + giveaway_message: Some(Box::new(Message { + id: MessageId(24), + thread_id: None, + date: DateTime::from_timestamp(1721161230, 0).unwrap(), + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + }, + via_bot: None, + kind: MessageKind::Giveaway(MessageGiveaway { + giveaway: Giveaway { + chats: vec![Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + }], + winners_selection_date: DateTime::from_timestamp(1721162701, 0) + .unwrap(), + winner_count: 1, + only_new_members: false, + has_public_winners: true, + prize_description: None, + country_codes: None, + premium_subscription_month_count: Some(6) + } + }) + })) + } + ) + } + + #[test] + fn giveaway_winners() { + let json = r#"{ + "message_id": 28, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162702, + "reply_to_message": { + "message_id": 27, + "sender_chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "date": 1721162577, + "giveaway": { + "chats": [ + { + "id": -1002236736395, + "title": "Test", + "type": "channel" + } + ], + "winners_selection_date": 1721162701, + "winner_count": 1, + "has_public_winners": true, + "premium_subscription_month_count": 6 + } + }, + "giveaway_winners": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "giveaway_message_id": 27, + "winners_selection_date": 1721162701, + "premium_subscription_month_count": 6, + "winner_count": 1, + "winners": [ + { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10" + } + ] + } + }"#; + let message: Message = from_str(json).unwrap(); + assert_eq!( + message.giveaway_winners().expect("Failed to get GiveawayWinners from Message!"), + &GiveawayWinners { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None + }), + description: None, + invite_link: None, + has_protected_content: None + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + }, + giveaway_message_id: MessageId(27), + winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), + winner_count: 1, + winners: vec![User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: None, + is_premium: false, + added_to_attachment_menu: false + }], + additional_chat_count: None, + premium_subscription_month_count: Some(6), + unclaimed_prize_count: None, + only_new_members: false, + was_refunded: false, + prize_description: None + } + ) + } } From cfa67610f62121744e4210d2b08858373b406475 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 20:30:27 +0400 Subject: [PATCH 17/51] Add `filter_giveaway`, `filter_giveaway_completed`, `filter_giveaway_created` and `filter_giveaway_winners` to MessageFilterExt --- crates/teloxide/src/dispatching/filter_ext.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index 08d0fba6..c4ea1161 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -116,6 +116,10 @@ define_message_ext! { (filter_forum_topic_reopened, Message::forum_topic_reopened), (filter_general_forum_topic_hidden, Message::general_forum_topic_hidden), (filter_general_forum_topic_unhidden, Message::general_forum_topic_unhidden), + (filter_giveaway, Message::giveaway), + (filter_giveaway_completed, Message::giveaway_completed), + (filter_giveaway_created, Message::giveaway_created), + (filter_giveaway_winners, Message::giveaway_winners), (filter_video_chat_scheduled, Message::video_chat_scheduled), (filter_video_chat_started, Message::video_chat_started), (filter_video_chat_ended, Message::video_chat_ended), From 77882d97f7665bc71e06b3b2a6684ffc0f710fb3 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 20:18:32 +0400 Subject: [PATCH 18/51] Add `ReactionType` struct --- crates/teloxide-core/src/types.rs | 2 + .../teloxide-core/src/types/reaction_type.rs | 52 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 crates/teloxide-core/src/types/reaction_type.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index deb64378..77f9cf43 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -103,6 +103,7 @@ pub use poll_answer::*; pub use poll_type::*; pub use pre_checkout_query::*; pub use proximity_alert_triggered::*; +pub use reaction_type::*; pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; @@ -215,6 +216,7 @@ mod poll_answer; mod poll_type; mod pre_checkout_query; mod proximity_alert_triggered; +mod reaction_type; mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; diff --git a/crates/teloxide-core/src/types/reaction_type.rs b/crates/teloxide-core/src/types/reaction_type.rs new file mode 100644 index 00000000..7a3250e4 --- /dev/null +++ b/crates/teloxide-core/src/types/reaction_type.rs @@ -0,0 +1,52 @@ +use serde::{Deserialize, Serialize}; + +/// The reaction type is based on an emoji. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ReactionType { + /// Kind of this reaction type - emoji or custom emoji. + #[serde(flatten)] + pub kind: ReactionTypeKind, +} + +/// Kind of a [`ReactionType`] - emoji or custom emoji. +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum ReactionTypeKind { + /// "emoji" or "custom_emoji" reaction + Emoji { + /// Reaction emoji. Currently, it can be one of "👍", "👎", "❤", "🔥", + /// "🥰", "👏", "😁", "🤔", "🤯", "😱", "🤬", "😢", "🎉", "🤩", + /// "🤮", "💩", "🙏", "👌", "🕊", "🤡", "🥱", "🥴", "😍", "🐳", + /// "❤‍🔥", "🌚", "🌭", "💯", "🤣", "⚡", "🍌", "🏆", "💔", "🤨", + /// "😐", "🍓", "🍾", "💋", "🖕", "😈", "😴", "😭", "🤓", "👻", + /// "👨‍💻", "👀", "🎃", "🙈", "😇", "😨", "🤝", "✍", "🤗", "🫡", + /// "🎅", "🎄", "☃", "💅", "🤪", "🗿", "🆒", "💘", "🙉", "🦄", "😘", + /// "💊", "🙊", "😎", "👾", "🤷‍♂", "🤷", "🤷‍♀", "😡" + emoji: String, + }, + /// Custom emoji sticker. + CustomEmoji { + /// Custom emoji identifier. + custom_emoji_id: String, + }, +} + +impl ReactionType { + #[must_use] + pub fn emoji(&self) -> Option<&String> { + match &self.kind { + ReactionTypeKind::Emoji { emoji } => Some(emoji), + _ => None, + } + } + + #[must_use] + pub fn custom_emoji_id(&self) -> Option<&String> { + match &self.kind { + ReactionTypeKind::CustomEmoji { custom_emoji_id } => Some(custom_emoji_id), + _ => None, + } + } +} From 50768a1af06873851e6bb781d72580f3f0cd4f0c Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 21:50:52 +0400 Subject: [PATCH 19/51] Add `MessageReactionUpdated` and `MessageReactionCountUpdated` structs --- crates/teloxide-core/src/types.rs | 4 + .../teloxide-core/src/types/allowed_update.rs | 2 + .../types/message_reaction_count_updated.rs | 73 ++++++ .../src/types/message_reaction_updated.rs | 82 ++++++ crates/teloxide-core/src/types/update.rs | 240 ++++++++++++++++-- crates/teloxide/src/dispatching/filter_ext.rs | 2 + .../src/dispatching/handler_description.rs | 2 + 7 files changed, 390 insertions(+), 15 deletions(-) create mode 100644 crates/teloxide-core/src/types/message_reaction_count_updated.rs create mode 100644 crates/teloxide-core/src/types/message_reaction_updated.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 77f9cf43..cd7cf0a8 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -92,6 +92,8 @@ pub use message_auto_delete_timer_changed::*; pub use message_entity::*; pub use message_id::*; pub use message_origin::*; +pub use message_reaction_count_updated::*; +pub use message_reaction_updated::*; pub use order_info::*; pub use parse_mode::*; pub use passport_data::*; @@ -208,6 +210,8 @@ mod message_auto_delete_timer_changed; mod message_entity; mod message_id; mod message_origin; +mod message_reaction_count_updated; +mod message_reaction_updated; mod order_info; mod parse_mode; mod photo_size; diff --git a/crates/teloxide-core/src/types/allowed_update.rs b/crates/teloxide-core/src/types/allowed_update.rs index 7820ad8f..a77040b3 100644 --- a/crates/teloxide-core/src/types/allowed_update.rs +++ b/crates/teloxide-core/src/types/allowed_update.rs @@ -7,6 +7,8 @@ pub enum AllowedUpdate { EditedMessage, ChannelPost, EditedChannelPost, + MessageReaction, + MessageReactionCount, InlineQuery, ChosenInlineResult, CallbackQuery, diff --git a/crates/teloxide-core/src/types/message_reaction_count_updated.rs b/crates/teloxide-core/src/types/message_reaction_count_updated.rs new file mode 100644 index 00000000..0d9f1a0e --- /dev/null +++ b/crates/teloxide-core/src/types/message_reaction_count_updated.rs @@ -0,0 +1,73 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, ReactionType}; + +/// This object represents reaction changes on a message with anonymous +/// reactions. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageReactionCountUpdated { + /// The chat containing the message + pub chat: Chat, + + /// Unique message identifier inside the chat + #[serde(flatten)] + pub message_id: MessageId, + + /// Date of the change in Unix time + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, + + /// List of reactions that are present on the message + pub reactions: Vec, +} + +/// Represents a reaction added to a message along with the number of times it +/// was added. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ReactionCount { + /// Type of the reaction + pub r#type: ReactionType, + + /// Number of times the reaction was added + pub total_count: u64, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "message_id": 36, + "date": 1721306391, + "reactions": [ + { + "type": { + "type": "emoji", + "emoji": "🗿" + }, + "total_count": 2 + }, + { + "type": { + "type": "emoji", + "emoji": "🌭" + }, + "total_count": 1 + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/message_reaction_updated.rs b/crates/teloxide-core/src/types/message_reaction_updated.rs new file mode 100644 index 00000000..8aeae9e5 --- /dev/null +++ b/crates/teloxide-core/src/types/message_reaction_updated.rs @@ -0,0 +1,82 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, MessageId, ReactionType, User}; + +/// This object represents a change of a reaction on a message performed by a +/// user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageReactionUpdated { + /// The chat containing the message the user reacted to + pub chat: Chat, + + /// Unique identifier of the message inside the chat + #[serde(flatten)] + pub message_id: MessageId, + + /// The user that changed the reaction, if the user isn't anonymous + pub user: Option, + + /// The chat on behalf of which the reaction was changed, if the user is + /// anonymous + pub actor_chat: Option, + + /// Date of the change in Unix time + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub date: DateTime, + + /// Previous list of reaction types that were set by the user + pub old_reaction: Vec, + + /// New list of reaction types that have been set by the user + pub new_reaction: Vec, +} + +impl MessageReactionUpdated { + #[must_use] + pub fn actor_chat(&self) -> Option<&Chat> { + self.actor_chat.as_ref() + } + + #[must_use] + pub fn user(&self) -> Option<&User> { + self.user.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002184233434, + "title": "Test", + "type": "supergroup" + }, + "message_id": 35, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + }, + "date": 1721306082, + "old_reaction": [], + "new_reaction": [ + { + "type": "emoji", + "emoji": "🌭" + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index e3e3e14c..cd1b91be 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -4,7 +4,8 @@ use serde_json::Value; use crate::types::{ CallbackQuery, Chat, ChatJoinRequest, ChatMemberUpdated, ChosenInlineResult, InlineQuery, - Message, Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, User, + Message, MessageReactionCountUpdated, MessageReactionUpdated, Poll, PollAnswer, + PreCheckoutQuery, ShippingQuery, User, }; /// This [object] represents an incoming update. @@ -59,6 +60,24 @@ pub enum UpdateKind { /// New version of a channel post that is known to the bot and was edited. EditedChannelPost(Message), + /// A reaction to a message was changed by a user. The bot must be an + /// administrator in the chat and must explicitly specify + /// [`AllowedUpdate::MessageReaction`] in the list of `allowed_updates` + /// to receive these updates. The update isn't received for reactions + /// set by bots. + /// + /// [`AllowedUpdate::MessageReaction`]: crate::types::AllowedUpdate::MessageReaction + MessageReaction(MessageReactionUpdated), + + /// Reactions to a message with anonymous reactions were changed. The bot + /// must be an administrator in the chat and must explicitly specify + /// [`AllowedUpdate::MessageReactionCount`] in the list of `allowed_updates` + /// to receive these updates. The updates are grouped and can be sent + /// with delay up to a few minutes. + /// + /// [`AllowedUpdate::MessageReactionCount`]: crate::types::AllowedUpdate::MessageReactionCount + MessageReactionCount(MessageReactionCountUpdated), + /// New incoming [inline] query. /// /// [inline]: https://core.telegram.org/bots/api#inline-mode @@ -133,6 +152,7 @@ impl Update { CallbackQuery(query) => &query.from, ChosenInlineResult(chosen) => &chosen.from, + MessageReaction(reaction) => return reaction.user(), InlineQuery(query) => &query.from, ShippingQuery(query) => &query.from, PreCheckoutQuery(query) => &query.from, @@ -141,7 +161,7 @@ impl Update { MyChatMember(m) | ChatMember(m) => &m.from, ChatJoinRequest(r) => &r.from, - Poll(_) | Error(_) => return None, + MessageReactionCount(_) | Poll(_) | Error(_) => return None, }; Some(from) @@ -191,6 +211,13 @@ impl Update { | UpdateKind::ChannelPost(message) | UpdateKind::EditedChannelPost(message) => i0(message.mentioned_users()), + UpdateKind::MessageReaction(answer) => { + if let Some(user) = answer.user() { + return i1(once(user)); + } + i6(empty()) + } + UpdateKind::InlineQuery(query) => i1(once(&query.from)), UpdateKind::ChosenInlineResult(query) => i1(once(&query.from)), UpdateKind::CallbackQuery(query) => i2(query.mentioned_users()), @@ -209,7 +236,7 @@ impl Update { i4(member.mentioned_users()) } UpdateKind::ChatJoinRequest(request) => i5(request.mentioned_users()), - UpdateKind::Error(_) => i6(empty()), + UpdateKind::MessageReactionCount(_) | UpdateKind::Error(_) => i6(empty()), } } @@ -224,6 +251,8 @@ impl Update { ChatMember(m) => &m.chat, MyChatMember(m) => &m.chat, ChatJoinRequest(c) => &c.chat, + MessageReaction(r) => &r.chat, + MessageReactionCount(r) => &r.chat, InlineQuery(_) | ChosenInlineResult(_) @@ -293,6 +322,14 @@ impl<'de> Deserialize<'de> for UpdateKind { "edited_channel_post" => { map.next_value::().ok().map(UpdateKind::EditedChannelPost) } + "message_reaction" => map + .next_value::() + .ok() + .map(UpdateKind::MessageReaction), + "message_reaction_count" => map + .next_value::() + .ok() + .map(UpdateKind::MessageReactionCount), "inline_query" => { map.next_value::().ok().map(UpdateKind::InlineQuery) } @@ -351,27 +388,33 @@ impl Serialize for UpdateKind { UpdateKind::EditedChannelPost(v) => { s.serialize_newtype_variant(name, 3, "edited_channel_post", v) } - UpdateKind::InlineQuery(v) => s.serialize_newtype_variant(name, 4, "inline_query", v), + UpdateKind::MessageReaction(v) => { + s.serialize_newtype_variant(name, 4, "message_reaction", v) + } + UpdateKind::MessageReactionCount(v) => { + s.serialize_newtype_variant(name, 5, "message_reaction_count", v) + } + UpdateKind::InlineQuery(v) => s.serialize_newtype_variant(name, 6, "inline_query", v), UpdateKind::ChosenInlineResult(v) => { - s.serialize_newtype_variant(name, 5, "chosen_inline_result", v) + s.serialize_newtype_variant(name, 7, "chosen_inline_result", v) } UpdateKind::CallbackQuery(v) => { - s.serialize_newtype_variant(name, 6, "callback_query", v) + s.serialize_newtype_variant(name, 8, "callback_query", v) } UpdateKind::ShippingQuery(v) => { - s.serialize_newtype_variant(name, 7, "shipping_query", v) + s.serialize_newtype_variant(name, 9, "shipping_query", v) } UpdateKind::PreCheckoutQuery(v) => { - s.serialize_newtype_variant(name, 8, "pre_checkout_query", v) + s.serialize_newtype_variant(name, 10, "pre_checkout_query", v) } - UpdateKind::Poll(v) => s.serialize_newtype_variant(name, 9, "poll", v), - UpdateKind::PollAnswer(v) => s.serialize_newtype_variant(name, 10, "poll_answer", v), + UpdateKind::Poll(v) => s.serialize_newtype_variant(name, 11, "poll", v), + UpdateKind::PollAnswer(v) => s.serialize_newtype_variant(name, 12, "poll_answer", v), UpdateKind::MyChatMember(v) => { - s.serialize_newtype_variant(name, 11, "my_chat_member", v) + s.serialize_newtype_variant(name, 13, "my_chat_member", v) } - UpdateKind::ChatMember(v) => s.serialize_newtype_variant(name, 12, "chat_member", v), + UpdateKind::ChatMember(v) => s.serialize_newtype_variant(name, 14, "chat_member", v), UpdateKind::ChatJoinRequest(v) => { - s.serialize_newtype_variant(name, 13, "chat_join_request", v) + s.serialize_newtype_variant(name, 15, "chat_join_request", v) } UpdateKind::Error(v) => v.serialize(s), } @@ -385,8 +428,10 @@ fn empty_error() -> UpdateKind { #[cfg(test)] mod test { use crate::types::{ - Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, MediaKind, MediaText, Message, - MessageCommon, MessageId, MessageKind, Update, UpdateId, UpdateKind, User, UserId, + Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, MediaKind, MediaText, + Message, MessageCommon, MessageId, MessageKind, MessageReactionCountUpdated, + MessageReactionUpdated, PublicChatChannel, PublicChatKind, PublicChatSupergroup, + ReactionCount, ReactionType, ReactionTypeKind, Update, UpdateId, UpdateKind, User, UserId, }; use chrono::DateTime; @@ -726,4 +771,169 @@ mod test { _ => panic!("Expected `MyChatMember`"), } } + + #[test] + fn message_reaction_updated() { + let json = r#" + { + "update_id": 71651249, + "message_reaction": { + "chat": { + "id": -1002184233434, + "title": "Test", + "type": "supergroup" + }, + "message_id": 35, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + }, + "date": 1721306082, + "old_reaction": [], + "new_reaction": [ + { + "type": "emoji", + "emoji": "🌭" + } + ] + } + } + "#; + + let expected = Update { + id: UpdateId(71651249), + kind: UpdateKind::MessageReaction(MessageReactionUpdated { + chat: Chat { + id: ChatId(-1002184233434), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Supergroup(PublicChatSupergroup { + username: None, + active_usernames: None, + is_forum: false, + sticker_set_name: None, + can_set_sticker_set: None, + permissions: None, + slow_mode_delay: None, + linked_chat_id: None, + location: None, + join_to_send_messages: None, + join_by_request: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + }, + message_id: MessageId(35), + user: Some(User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }), + actor_chat: None, + date: DateTime::from_timestamp(1721306082, 0).unwrap(), + old_reaction: vec![], + new_reaction: vec![ReactionType { + kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, + }], + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn message_reaction_count_updated() { + let json = r#" + { + "update_id": 71651251, + "message_reaction_count": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "message_id": 36, + "date": 1721306391, + "reactions": [ + { + "type": { + "type": "emoji", + "emoji": "🗿" + }, + "total_count": 2 + }, + { + "type": { + "type": "emoji", + "emoji": "🌭" + }, + "total_count": 1 + } + ] + } + } + "#; + + let expected = Update { + id: UpdateId(71651251), + kind: UpdateKind::MessageReactionCount(MessageReactionCountUpdated { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + }), + photo: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + }, + message_id: MessageId(36), + date: DateTime::from_timestamp(1721306391, 0).unwrap(), + reactions: vec![ + ReactionCount { + r#type: ReactionType { + kind: ReactionTypeKind::Emoji { emoji: "🗿".to_owned() }, + }, + total_count: 2, + }, + ReactionCount { + r#type: ReactionType { + kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, + }, + total_count: 1, + }, + ], + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } } diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index c4ea1161..d0f1d744 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -149,6 +149,8 @@ define_update_ext! { (filter_edited_message, UpdateKind::EditedMessage, EditedMessage), (filter_channel_post, UpdateKind::ChannelPost, ChannelPost), (filter_edited_channel_post, UpdateKind::EditedChannelPost, EditedChannelPost), + (filter_message_reaction_updated, UpdateKind::MessageReaction, MessageReaction), + (filter_message_reaction_count_updated, UpdateKind::MessageReactionCount, MessageReactionCount), (filter_inline_query, UpdateKind::InlineQuery, InlineQuery), (filter_chosen_inline_result, UpdateKind::ChosenInlineResult, ChosenInlineResult), (filter_callback_query, UpdateKind::CallbackQuery, CallbackQuery), diff --git a/crates/teloxide/src/dispatching/handler_description.rs b/crates/teloxide/src/dispatching/handler_description.rs index eb49dda0..2c1aefab 100644 --- a/crates/teloxide/src/dispatching/handler_description.rs +++ b/crates/teloxide/src/dispatching/handler_description.rs @@ -65,6 +65,8 @@ impl EventKind for Kind { EditedMessage, ChannelPost, EditedChannelPost, + MessageReaction, + MessageReactionCount, InlineQuery, ChosenInlineResult, CallbackQuery, From 409c3d54037db40674afca72f876397be7b0b8e9 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Wed, 17 Jul 2024 22:36:58 +0400 Subject: [PATCH 20/51] Add `setMessageReaction` TBA method --- crates/teloxide-core/schema.ron | 31 +++++++++++++++++++ crates/teloxide-core/src/adaptors/cache_me.rs | 1 + crates/teloxide-core/src/adaptors/erased.rs | 15 +++++++++ .../teloxide-core/src/adaptors/parse_mode.rs | 1 + .../src/adaptors/throttle/requester_impl.rs | 1 + crates/teloxide-core/src/adaptors/trace.rs | 1 + crates/teloxide-core/src/bot/api.rs | 12 +++++++ crates/teloxide-core/src/local_macros.rs | 8 +++++ crates/teloxide-core/src/payloads.rs | 2 ++ .../src/payloads/set_message_reaction.rs | 25 +++++++++++++++ crates/teloxide-core/src/payloads/setters.rs | 2 +- .../teloxide-core/src/requests/requester.rs | 12 +++++++ 12 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 crates/teloxide-core/src/payloads/set_message_reaction.rs diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 7d6ff142..5b382979 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -1851,6 +1851,37 @@ Schema( ), ], ), + Method( + names: ("setMessageReaction", "SetMessageReaction", "set_message_reaction"), + return_ty: True, + doc: Doc( + md: "Use this method to change the chosen reactions on a message. Service messages can't be reacted to. Automatically forwarded messages from a channel to its discussion group have the same available reactions as messages in the channel. Returns True on success." + ), + tg_doc: "https://core.telegram.org/bots/api#setmessagereaction", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the target chat or username of the target channel (in the format @channelusername)") + ), + Param( + name: "message_id", + ty: RawTy("MessageId"), + descr: Doc(md: "Identifier of the target message. If the message belongs to a media group, the reaction is set to the first non-deleted message in the group instead.") + ), + Param( + name: "reaction", + ty: Option(ArrayOf(RawTy("ReactionType"))), + descr: Doc(md: "New list of reaction types to set on the message. Currently, as non-premium users, bots can set up to one reaction per message. A custom emoji reaction can be used if it is either already present on the message or explicitly allowed by chat administrators.") + ), + Param( + name: "is_big", + ty: Option(bool), + descr: Doc(md: "Pass True to set the reaction with a big animation") + ), + ], + ), Method( names: ("getUserProfilePhotos", "GetUserProfilePhotos", "get_user_profile_photos"), return_ty: RawTy("UserProfilePhotos"), diff --git a/crates/teloxide-core/src/adaptors/cache_me.rs b/crates/teloxide-core/src/adaptors/cache_me.rs index caab3f87..4ab96541 100644 --- a/crates/teloxide-core/src/adaptors/cache_me.rs +++ b/crates/teloxide-core/src/adaptors/cache_me.rs @@ -116,6 +116,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, diff --git a/crates/teloxide-core/src/adaptors/erased.rs b/crates/teloxide-core/src/adaptors/erased.rs index 1e3ca58b..0c2a907d 100644 --- a/crates/teloxide-core/src/adaptors/erased.rs +++ b/crates/teloxide-core/src/adaptors/erased.rs @@ -215,6 +215,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, @@ -480,6 +481,12 @@ trait ErasableRequester<'a> { action: ChatAction, ) -> ErasedRequest<'a, SendChatAction, Self::Err>; + fn set_message_reaction( + &self, + chat_id: Recipient, + message_id: MessageId, + ) -> ErasedRequest<'a, SetMessageReaction, Self::Err>; + fn get_user_profile_photos( &self, user_id: UserId, @@ -1222,6 +1229,14 @@ where Requester::send_chat_action(self, chat_id, action).erase() } + fn set_message_reaction( + &self, + chat_id: Recipient, + message_id: MessageId, + ) -> ErasedRequest<'a, SetMessageReaction, Self::Err> { + Requester::set_message_reaction(self, chat_id, message_id).erase() + } + fn get_user_profile_photos( &self, user_id: UserId, diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index f9458bca..1b4fba80 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -197,6 +197,7 @@ where send_contact, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, diff --git a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs index eb9cbb41..0fbe3a5f 100644 --- a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs +++ b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs @@ -99,6 +99,7 @@ where stop_message_live_location, stop_message_live_location_inline, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, diff --git a/crates/teloxide-core/src/adaptors/trace.rs b/crates/teloxide-core/src/adaptors/trace.rs index cef5fa7e..7c6ebfa8 100644 --- a/crates/teloxide-core/src/adaptors/trace.rs +++ b/crates/teloxide-core/src/adaptors/trace.rs @@ -145,6 +145,7 @@ where send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, diff --git a/crates/teloxide-core/src/bot/api.rs b/crates/teloxide-core/src/bot/api.rs index 7da6ee98..27912b41 100644 --- a/crates/teloxide-core/src/bot/api.rs +++ b/crates/teloxide-core/src/bot/api.rs @@ -309,6 +309,18 @@ impl Requester for Bot { Self::SendChatAction::new(self.clone(), payloads::SendChatAction::new(chat_id, action)) } + type SetMessageReaction = JsonRequest; + + fn set_message_reaction(&self, chat_id: C, message_id: MessageId) -> Self::SetMessageReaction + where + C: Into, + { + Self::SetMessageReaction::new( + self.clone(), + payloads::SetMessageReaction::new(chat_id, message_id), + ) + } + type GetUserProfilePhotos = JsonRequest; fn get_user_profile_photos(&self, user_id: UserId) -> Self::GetUserProfilePhotos { diff --git a/crates/teloxide-core/src/local_macros.rs b/crates/teloxide-core/src/local_macros.rs index 41ae27a6..242f807b 100644 --- a/crates/teloxide-core/src/local_macros.rs +++ b/crates/teloxide-core/src/local_macros.rs @@ -667,6 +667,14 @@ macro_rules! requester_forward { $body!(send_chat_action this (chat_id: C, action: ChatAction)) } }; + (@method set_message_reaction $body:ident $ty:ident) => { + type SetMessageReaction = $ty![SetMessageReaction]; + + fn set_message_reaction(&self, chat_id: C, message_id: MessageId) -> Self::SetMessageReaction where C: Into { + let this = self; + $body!(set_message_reaction this (chat_id: C, message_id: MessageId)) + } + }; (@method get_user_profile_photos $body:ident $ty:ident) => { type GetUserProfilePhotos = $ty![GetUserProfilePhotos]; diff --git a/crates/teloxide-core/src/payloads.rs b/crates/teloxide-core/src/payloads.rs index ee3f845e..cdcd85f1 100644 --- a/crates/teloxide-core/src/payloads.rs +++ b/crates/teloxide-core/src/payloads.rs @@ -117,6 +117,7 @@ mod set_chat_title; mod set_custom_emoji_sticker_set_thumbnail; mod set_game_score; mod set_game_score_inline; +mod set_message_reaction; mod set_my_commands; mod set_my_default_administrator_rights; mod set_my_description; @@ -256,6 +257,7 @@ pub use set_custom_emoji_sticker_set_thumbnail::{ }; pub use set_game_score::{SetGameScore, SetGameScoreSetters}; pub use set_game_score_inline::{SetGameScoreInline, SetGameScoreInlineSetters}; +pub use set_message_reaction::{SetMessageReaction, SetMessageReactionSetters}; pub use set_my_commands::{SetMyCommands, SetMyCommandsSetters}; pub use set_my_default_administrator_rights::{ SetMyDefaultAdministratorRights, SetMyDefaultAdministratorRightsSetters, diff --git a/crates/teloxide-core/src/payloads/set_message_reaction.rs b/crates/teloxide-core/src/payloads/set_message_reaction.rs new file mode 100644 index 00000000..834336ba --- /dev/null +++ b/crates/teloxide-core/src/payloads/set_message_reaction.rs @@ -0,0 +1,25 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{MessageId, ReactionType, Recipient, True}; + +impl_payload! { + /// Use this method to change the chosen reactions on a message. Service messages can't be reacted to. Automatically forwarded messages from a channel to its discussion group have the same available reactions as messages in the channel. Returns True on success. + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub SetMessageReaction (SetMessageReactionSetters) => True { + required { + /// Unique identifier for the target chat or username of the target channel (in the format @channelusername) + pub chat_id: Recipient [into], + /// Identifier of the target message. If the message belongs to a media group, the reaction is set to the first non-deleted message in the group instead. + #[serde(flatten)] + pub message_id: MessageId, + } + optional { + /// New list of reaction types to set on the message. Currently, as non-premium users, bots can set up to one reaction per message. A custom emoji reaction can be used if it is either already present on the message or explicitly allowed by chat administrators. + pub reaction: Vec [collect], + /// Pass True to set the reaction with a big animation + pub is_big: bool, + } + } +} diff --git a/crates/teloxide-core/src/payloads/setters.rs b/crates/teloxide-core/src/payloads/setters.rs index 96ba6712..279fda1d 100644 --- a/crates/teloxide-core/src/payloads/setters.rs +++ b/crates/teloxide-core/src/payloads/setters.rs @@ -39,7 +39,7 @@ pub use crate::payloads::{ SetChatMenuButtonSetters as _, SetChatPermissionsSetters as _, SetChatPhotoSetters as _, SetChatStickerSetSetters as _, SetChatTitleSetters as _, SetCustomEmojiStickerSetThumbnailSetters as _, SetGameScoreInlineSetters as _, - SetGameScoreSetters as _, SetMyCommandsSetters as _, + SetGameScoreSetters as _, SetMessageReactionSetters as _, SetMyCommandsSetters as _, SetMyDefaultAdministratorRightsSetters as _, SetMyDescriptionSetters as _, SetMyNameSetters as _, SetMyShortDescriptionSetters as _, SetPassportDataErrorsSetters as _, SetStickerEmojiListSetters as _, SetStickerKeywordsSetters as _, diff --git a/crates/teloxide-core/src/requests/requester.rs b/crates/teloxide-core/src/requests/requester.rs index 0b97abf9..2936e8b2 100644 --- a/crates/teloxide-core/src/requests/requester.rs +++ b/crates/teloxide-core/src/requests/requester.rs @@ -413,6 +413,17 @@ pub trait Requester { where C: Into; + type SetMessageReaction: Request; + + /// For Telegram documentation see [`SetMessageReaction`]. + fn set_message_reaction( + &self, + chat_id: C, + message_id: MessageId, + ) -> Self::SetMessageReaction + where + C: Into; + type GetUserProfilePhotos: Request; /// For Telegram documentation see [`GetUserProfilePhotos`]. @@ -1307,6 +1318,7 @@ macro_rules! forward_all { send_poll, send_dice, send_chat_action, + set_message_reaction, get_user_profile_photos, get_file, kick_chat_member, From 1e25bf1c963acdf345cbec441ccb66a2b4918b4b Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Thu, 18 Jul 2024 00:12:05 +0400 Subject: [PATCH 21/51] Add `available_reactions` field to `Chat` --- crates/teloxide-core/src/types/chat.rs | 48 +++++++++++++++++++++-- crates/teloxide-core/src/types/message.rs | 6 +++ crates/teloxide-core/src/types/update.rs | 3 ++ 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/crates/teloxide-core/src/types/chat.rs b/crates/teloxide-core/src/types/chat.rs index 2b0fd386..eeef702f 100644 --- a/crates/teloxide-core/src/types/chat.rs +++ b/crates/teloxide-core/src/types/chat.rs @@ -1,7 +1,8 @@ use serde::{Deserialize, Serialize}; use crate::types::{ - ChatFullInfo, ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, Seconds, True, User, + ChatFullInfo, ChatId, ChatLocation, ChatPermissions, ChatPhoto, Message, ReactionType, Seconds, + True, User, }; /// This object represents a chat. @@ -21,6 +22,12 @@ pub struct Chat { /// [`GetChat`]: crate::payloads::GetChat pub photo: Option, + /// List of available reactions allowed in the chat. If omitted, then all + /// emoji reactions are allowed. Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub available_reactions: Option>, + /// The most recent pinned message (by sending date). Returned only in /// [`GetChat`]. /// @@ -602,13 +609,29 @@ mod tests { has_protected_content: None, }), photo: None, + available_reactions: Some(vec![ReactionType { + kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, + }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, chat_full_info: ChatFullInfo::default(), }; - let actual = from_str(r#"{"id":-1,"type":"channel","username":"channel_name"}"#).unwrap(); + let actual = from_str( + r#"{ + "id": -1, + "type": "channel", + "username": "channel_name", + "available_reactions": [ + { + "type": "emoji", + "emoji": "🌭" + } + ] + }"#, + ) + .unwrap(); assert_eq!(expected, actual); } @@ -626,14 +649,30 @@ mod tests { has_restricted_voice_and_video_messages: None, }), photo: None, + available_reactions: Some(vec![ReactionType { + kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, + }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, chat_full_info: ChatFullInfo::default() }, - from_str(r#"{"id":0,"type":"private","username":"username","first_name":"Anon"}"#) - .unwrap() + from_str( + r#"{ + "id": 0, + "type": "private", + "username": "username", + "first_name": "Anon", + "available_reactions": [ + { + "type": "emoji", + "emoji": "🌭" + } + ] + }"# + ) + .unwrap() ); } @@ -650,6 +689,7 @@ mod tests { has_restricted_voice_and_video_messages: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index a862303b..24eff7cb 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1841,6 +1841,7 @@ mod tests { has_restricted_voice_and_video_messages: None }), photo: None, + available_reactions: None, has_aggressive_anti_spam_enabled: false, pinned_message: None, message_auto_delete_time: None, @@ -2078,6 +2079,7 @@ mod tests { }), message_auto_delete_time: None, photo: None, + available_reactions: None, pinned_message: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, @@ -2368,6 +2370,7 @@ mod tests { has_protected_content: None }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -2476,6 +2479,7 @@ mod tests { has_protected_content: None }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -2498,6 +2502,7 @@ mod tests { has_protected_content: None }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -2598,6 +2603,7 @@ mod tests { has_protected_content: None }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index cd1b91be..18a37ad3 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -482,6 +482,7 @@ mod test { has_restricted_voice_and_video_messages: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -829,6 +830,7 @@ mod test { has_protected_content: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -908,6 +910,7 @@ mod test { has_protected_content: None, }), photo: None, + available_reactions: None, pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, From 599d8fac8a21cd6d800939f77284d8d5a71f7a79 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Thu, 18 Jul 2024 23:15:38 +0400 Subject: [PATCH 22/51] Remove `disable_web_page_preview` field from everywhere --- crates/teloxide-core/schema.ron | 10 ----- .../src/payloads/edit_message_text.rs | 2 - .../src/payloads/send_message.rs | 2 - .../src/types/inline_query_result.rs | 38 ++++++------------- .../src/types/input_message_content.rs | 17 +-------- 5 files changed, 12 insertions(+), 57 deletions(-) diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 5b382979..dd2bdad2 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -252,11 +252,6 @@ Schema( ty: Option(ArrayOf(RawTy("MessageEntity"))), descr: Doc(md: "List of special entities that appear in the message text, which can be specified instead of _parse\\_mode_"), ), - Param( - name: "disable_web_page_preview", - ty: Option(bool), - descr: Doc(md: "Disables link previews for links in this message") - ), Param( name: "disable_notification", ty: Option(bool), @@ -3273,11 +3268,6 @@ Schema( ty: Option(ArrayOf(RawTy("MessageEntity"))), descr: Doc(md: "List of special entities that appear in message text, which can be specified instead of _parse\\_mode_"), ), - Param( - name: "disable_web_page_preview", - ty: Option(bool), - descr: Doc(md: "Disables link previews for links in this message") - ), Param( name: "reply_markup", ty: Option(RawTy("InlineKeyboardMarkup")), diff --git a/crates/teloxide-core/src/payloads/edit_message_text.rs b/crates/teloxide-core/src/payloads/edit_message_text.rs index ba4329c5..9d0b9fbd 100644 --- a/crates/teloxide-core/src/payloads/edit_message_text.rs +++ b/crates/teloxide-core/src/payloads/edit_message_text.rs @@ -28,8 +28,6 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], - /// Disables link previews for links in this message - pub disable_web_page_preview: bool, /// A JSON-serialized object for an [inline keyboard]. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_message.rs b/crates/teloxide-core/src/payloads/send_message.rs index 129c860a..f168f5c5 100644 --- a/crates/teloxide-core/src/payloads/send_message.rs +++ b/crates/teloxide-core/src/payloads/send_message.rs @@ -27,8 +27,6 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in the message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], - /// Disables link previews for links in this message - pub disable_web_page_preview: bool, /// Sends the message [silently]. Users will receive a notification with no sound. /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages diff --git a/crates/teloxide-core/src/types/inline_query_result.rs b/crates/teloxide-core/src/types/inline_query_result.rs index 4a4c07b9..d8ad8df2 100644 --- a/crates/teloxide-core/src/types/inline_query_result.rs +++ b/crates/teloxide-core/src/types/inline_query_result.rs @@ -303,13 +303,12 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), caption_entities: None, }); - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -351,7 +350,6 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), caption_entities: None, @@ -359,7 +357,7 @@ mod tests { audio_duration: Some(Seconds::from_seconds(1)), }); - let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -400,13 +398,12 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), caption_entities: None, }); - let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -453,7 +450,6 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), thumbnail_url: Some(reqwest::Url::parse("http://thumb_url/").unwrap()), @@ -461,7 +457,7 @@ mod tests { thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -501,12 +497,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -556,12 +551,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -601,12 +595,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -656,12 +649,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -703,12 +695,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -756,12 +747,11 @@ mod tests { input_message_content: Some(InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), entities: None, })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -794,11 +784,10 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: Some(ParseMode::MarkdownV2), - disable_web_page_preview: Some(true), })), }); - let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","disable_web_page_preview":true}}"#; + let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1029,7 +1018,6 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, }), reply_markup: None, url: None, @@ -1056,7 +1044,6 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, }), reply_markup: Some(InlineKeyboardMarkup::default()), url: Some(Url::parse("http://url/").unwrap()), @@ -1109,7 +1096,6 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), @@ -1195,7 +1181,6 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), @@ -1252,7 +1237,6 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, - disable_web_page_preview: None, })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), diff --git a/crates/teloxide-core/src/types/input_message_content.rs b/crates/teloxide-core/src/types/input_message_content.rs index adeca069..3cb54fe7 100644 --- a/crates/teloxide-core/src/types/input_message_content.rs +++ b/crates/teloxide-core/src/types/input_message_content.rs @@ -35,9 +35,6 @@ pub struct InputMessageContentText { /// List of special entities that appear in message text, which can be /// specified instead of `parse_mode`. pub entities: Option>, - - /// Disables link previews for links in the sent message. - pub disable_web_page_preview: Option, } impl InputMessageContentText { @@ -45,12 +42,7 @@ impl InputMessageContentText { where S: Into, { - Self { - message_text: message_text.into(), - parse_mode: None, - disable_web_page_preview: None, - entities: None, - } + Self { message_text: message_text.into(), parse_mode: None, entities: None } } pub fn message_text(mut self, val: S) -> Self @@ -74,12 +66,6 @@ impl InputMessageContentText { self.entities = Some(val.into_iter().collect()); self } - - #[must_use] - pub fn disable_web_page_preview(mut self, val: bool) -> Self { - self.disable_web_page_preview = Some(val); - self - } } /// Represents the content of a location message to be sent as the result of an @@ -587,7 +573,6 @@ mod tests { let text_content = InputMessageContent::Text(InputMessageContentText { message_text: String::from("text"), parse_mode: None, - disable_web_page_preview: None, entities: None, }); From 3d74587c93e3f978ba79e90f679979fc64a8aaec Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 00:19:17 +0400 Subject: [PATCH 23/51] Add `LinkPreviewOptions` struct and `link_preview_options` field to `InputMessageContentText`, `Message`, `sendMessage` and `editMessageText` --- crates/teloxide-core/schema.ron | 10 ++ .../src/payloads/edit_message_text.rs | 7 +- .../src/payloads/send_message.rs | 5 +- crates/teloxide-core/src/types.rs | 2 + .../src/types/inline_query_result.rs | 146 ++++++++++++++++-- .../src/types/input_message_content.rs | 28 +++- .../src/types/link_preview_options.rs | 46 ++++++ crates/teloxide-core/src/types/message.rs | 6 +- crates/teloxide-core/src/types/update.rs | 19 ++- 9 files changed, 241 insertions(+), 28 deletions(-) create mode 100644 crates/teloxide-core/src/types/link_preview_options.rs diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index dd2bdad2..2169a8a7 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -252,6 +252,11 @@ Schema( ty: Option(ArrayOf(RawTy("MessageEntity"))), descr: Doc(md: "List of special entities that appear in the message text, which can be specified instead of _parse\\_mode_"), ), + Param( + name: "link_preview_options", + ty: Option(RawTy("LinkPreviewOptions")), + descr: Doc(md: "Link preview generation options for the message"), + ), Param( name: "disable_notification", ty: Option(bool), @@ -3268,6 +3273,11 @@ Schema( ty: Option(ArrayOf(RawTy("MessageEntity"))), descr: Doc(md: "List of special entities that appear in message text, which can be specified instead of _parse\\_mode_"), ), + Param( + name: "link_preview_options", + ty: Option(RawTy("LinkPreviewOptions")), + descr: Doc(md: "Link preview generation options for the message"), + ), Param( name: "reply_markup", ty: Option(RawTy("InlineKeyboardMarkup")), diff --git a/crates/teloxide-core/src/payloads/edit_message_text.rs b/crates/teloxide-core/src/payloads/edit_message_text.rs index 9d0b9fbd..833b3175 100644 --- a/crates/teloxide-core/src/payloads/edit_message_text.rs +++ b/crates/teloxide-core/src/payloads/edit_message_text.rs @@ -2,7 +2,10 @@ use serde::Serialize; -use crate::types::{InlineKeyboardMarkup, Message, MessageEntity, MessageId, ParseMode, Recipient}; +use crate::types::{ + InlineKeyboardMarkup, LinkPreviewOptions, Message, MessageEntity, MessageId, ParseMode, + Recipient, +}; impl_payload! { /// Use this method to edit text and [games] messages. On success, the edited Message is returned. @@ -28,6 +31,8 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], + /// Link preview generation options for the message + pub link_preview_options: LinkPreviewOptions, /// A JSON-serialized object for an [inline keyboard]. /// /// [inline keyboard]: https://core.telegram.org/bots#inline-keyboards-and-on-the-fly-updating diff --git a/crates/teloxide-core/src/payloads/send_message.rs b/crates/teloxide-core/src/payloads/send_message.rs index f168f5c5..d7006c4f 100644 --- a/crates/teloxide-core/src/payloads/send_message.rs +++ b/crates/teloxide-core/src/payloads/send_message.rs @@ -3,7 +3,8 @@ use serde::Serialize; use crate::types::{ - Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, ThreadId, + LinkPreviewOptions, Message, MessageEntity, ParseMode, Recipient, ReplyMarkup, ReplyParameters, + ThreadId, }; impl_payload! { @@ -27,6 +28,8 @@ impl_payload! { pub parse_mode: ParseMode, /// List of special entities that appear in the message text, which can be specified instead of _parse\_mode_ pub entities: Vec [collect], + /// Link preview generation options for the message + pub link_preview_options: LinkPreviewOptions, /// Sends the message [silently]. Users will receive a notification with no sound. /// /// [silently]: https://telegram.org/blog/channels-2-0#silent-messages diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index cd7cf0a8..643fe086 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -81,6 +81,7 @@ pub use keyboard_button_poll_type::*; pub use keyboard_button_request_chat::*; pub use keyboard_button_request_user::*; pub use label_price::*; +pub use link_preview_options::*; pub use location::*; pub use login_url::*; pub use mask_position::*; @@ -199,6 +200,7 @@ mod keyboard_button_poll_type; mod keyboard_button_request_chat; mod keyboard_button_request_user; mod label_price; +mod link_preview_options; mod location; mod login_url; mod mask_position; diff --git a/crates/teloxide-core/src/types/inline_query_result.rs b/crates/teloxide-core/src/types/inline_query_result.rs index d8ad8df2..1d58fd56 100644 --- a/crates/teloxide-core/src/types/inline_query_result.rs +++ b/crates/teloxide-core/src/types/inline_query_result.rs @@ -266,7 +266,7 @@ mod tests { InlineQueryResultGif, InlineQueryResultLocation, InlineQueryResultMpeg4Gif, InlineQueryResultPhoto, InlineQueryResultVenue, InlineQueryResultVideo, InlineQueryResultVoice, InputMessageContent, InputMessageContentLocation, - InputMessageContentText, Seconds, + InputMessageContentText, LinkPreviewOptions, Seconds, }; use mime::Mime; @@ -304,11 +304,18 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), caption_entities: None, }); - let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_file_id":"audio_file_id","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -351,13 +358,20 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), caption_entities: None, performer: Some(String::from("performer")), audio_duration: Some(Seconds::from_seconds(1)), }); - let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"audio","id":"id","audio_url":"http://audio_url/","title":"title","caption":"caption","parse_mode":"HTML","performer":"performer","audio_duration":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -399,11 +413,18 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), caption_entities: None, }); - let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","document_file_id":"document_file_id","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -451,13 +472,20 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), thumbnail_url: Some(reqwest::Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"document","id":"id","title":"title","caption":"caption","parse_mode":"HTML","document_url":"http://document_url/","mime_type":"application/pdf","description":"description","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -498,10 +526,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_file_id":"gif_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -552,10 +587,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"gif","id":"id","gif_url":"http://gif_url/","gif_width":1,"gif_height":1,"gif_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -596,10 +638,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_file_id":"mpeg4_file_id","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -650,10 +699,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"mpeg4_gif","id":"id","mpeg4_url":"http://mpeg4_url/","mpeg4_width":1,"mpeg4_height":1,"mpeg4_duration":1,"thumbnail_url":"http://thumb_url/","title":"title","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -696,10 +752,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_file_id":"photo_file_id","title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -748,10 +811,17 @@ mod tests { message_text: String::from("message_text"), parse_mode: Some(ParseMode::MarkdownV2), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"photo","id":"id","photo_url":"http://photo_url/","thumbnail_url":"http://thumb_url/","photo_width":1,"photo_height":1,"title":"title","description":"description","caption":"caption","parse_mode":"HTML","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -784,10 +854,17 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: Some(ParseMode::MarkdownV2), + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), }); - let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2"}}"#; + let expected_json = r#"{"type":"sticker","id":"id","sticker_file_id":"sticker_file_id","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","parse_mode":"MarkdownV2","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1017,6 +1094,13 @@ mod tests { input_message_content: InputMessageContent::Text(InputMessageContentText { message_text: String::from("message_text"), entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), parse_mode: None, }), reply_markup: None, @@ -1028,7 +1112,7 @@ mod tests { thumbnail_height: None, }); - let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text"}}"#; + let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}}}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1044,6 +1128,13 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), }), reply_markup: Some(InlineKeyboardMarkup::default()), url: Some(Url::parse("http://url/").unwrap()), @@ -1054,7 +1145,7 @@ mod tests { thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text"},"reply_markup":{"inline_keyboard":[]},"url":"http://url/","hide_url":true,"description":"description","thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"article","id":"id","title":"title","input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"reply_markup":{"inline_keyboard":[]},"url":"http://url/","hide_url":true,"description":"description","thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1096,13 +1187,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"contact","id":"id","phone_number":"phone_number","first_name":"first_name","last_name":"last_name","vcard":"vcard","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"contact","id":"id","phone_number":"phone_number","first_name":"first_name","last_name":"last_name","vcard":"vcard","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1181,13 +1279,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"location","id":"id","latitude":1.0,"longitude":1.0,"title":"title","horizontal_accuracy":1.0,"live_period":1,"heading":1,"proximity_alert_radius":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"location","id":"id","latitude":1.0,"longitude":1.0,"title":"title","horizontal_accuracy":1.0,"live_period":1,"heading":1,"proximity_alert_radius":1,"reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); @@ -1237,13 +1342,20 @@ mod tests { message_text: String::from("message_text"), entities: None, parse_mode: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), thumbnail_width: Some(1), thumbnail_height: Some(1), }); - let expected_json = r#"{"type":"venue","id":"id","latitude":1.0,"longitude":1.0,"title":"title","address":"address","foursquare_id":"foursquare_id","foursquare_type":"foursquare_type","google_place_id":"google_place_id","google_place_type":"google_place_type","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text"},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; + let expected_json = r#"{"type":"venue","id":"id","latitude":1.0,"longitude":1.0,"title":"title","address":"address","foursquare_id":"foursquare_id","foursquare_type":"foursquare_type","google_place_id":"google_place_id","google_place_type":"google_place_type","reply_markup":{"inline_keyboard":[]},"input_message_content":{"message_text":"message_text","link_preview_options":{"is_disabled":true}},"thumbnail_url":"http://thumb_url/","thumbnail_width":1,"thumbnail_height":1}"#; let actual_json = serde_json::to_string(&structure).unwrap(); assert_eq!(expected_json, actual_json); diff --git a/crates/teloxide-core/src/types/input_message_content.rs b/crates/teloxide-core/src/types/input_message_content.rs index 3cb54fe7..ed517b7a 100644 --- a/crates/teloxide-core/src/types/input_message_content.rs +++ b/crates/teloxide-core/src/types/input_message_content.rs @@ -1,7 +1,7 @@ use reqwest::Url; use serde::{Deserialize, Serialize}; -use crate::types::{Currency, LabeledPrice, MessageEntity, ParseMode}; +use crate::types::{Currency, LabeledPrice, LinkPreviewOptions, MessageEntity, ParseMode}; /// This object represents the content of a message to be sent as a result of an /// inline query. @@ -35,6 +35,9 @@ pub struct InputMessageContentText { /// List of special entities that appear in message text, which can be /// specified instead of `parse_mode`. pub entities: Option>, + + /// Link preview generation options for the message + pub link_preview_options: Option, } impl InputMessageContentText { @@ -42,7 +45,12 @@ impl InputMessageContentText { where S: Into, { - Self { message_text: message_text.into(), parse_mode: None, entities: None } + Self { + message_text: message_text.into(), + parse_mode: None, + entities: None, + link_preview_options: None, + } } pub fn message_text(mut self, val: S) -> Self @@ -66,6 +74,12 @@ impl InputMessageContentText { self.entities = Some(val.into_iter().collect()); self } + + #[must_use] + pub fn link_preview_options(mut self, val: LinkPreviewOptions) -> Self { + self.link_preview_options = Some(val); + self + } } /// Represents the content of a location message to be sent as the result of an @@ -569,11 +583,19 @@ mod tests { #[test] fn text_serialize() { - let expected_json = r#"{"message_text":"text"}"#; + let expected_json = + r#"{"message_text":"text","link_preview_options":{"is_disabled":true}}"#; let text_content = InputMessageContent::Text(InputMessageContentText { message_text: String::from("text"), parse_mode: None, entities: None, + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), }); let actual_json = serde_json::to_string(&text_content).unwrap(); diff --git a/crates/teloxide-core/src/types/link_preview_options.rs b/crates/teloxide-core/src/types/link_preview_options.rs new file mode 100644 index 00000000..4f56d8b0 --- /dev/null +++ b/crates/teloxide-core/src/types/link_preview_options.rs @@ -0,0 +1,46 @@ +use serde::{Deserialize, Serialize}; + +/// Describes the options used for link preview generation. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] +pub struct LinkPreviewOptions { + /// `true`, if the link preview is disabled + pub is_disabled: Option, + + /// URL to use for the link preview. If empty, then the first URL found in + /// the message text will be used + pub url: Option, + + /// `true`, if the media in the link preview is suppposed to be shrunk; + /// ignored if the URL isn't explicitly specified or media size change isn't + /// supported for the preview + pub prefer_small_media: Option, + + /// `true`, if the media in the link preview is suppposed to be enlarged; + /// ignored if the URL isn't explicitly specified or media size change isn't + /// supported for the preview + pub prefer_large_media: Option, + + /// `true`, if the link preview must be shown above the message text; + /// otherwise, the link preview will be shown below the message text + pub show_above_text: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "is_disabled": true, + "url": "https://kernel.org/", + "prefer_small_media": false, + "prefer_large_media": true, + "show_above_text": true + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 24eff7cb..a4b4372f 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -8,7 +8,7 @@ use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCompleted, - GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, Location, + GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, LinkPreviewOptions, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, Story, SuccessfulPayment, TextQuote, ThreadId, True, User, UsersShared, Venue, Video, @@ -476,6 +476,10 @@ pub struct MediaText { /// commands, etc. that appear in the text. #[serde(default, skip_serializing_if = "Vec::is_empty")] pub entities: Vec, + + /// Options used for link preview generation for the message, if it is a + /// text message and link preview options were changed + pub link_preview_options: Option, } #[serde_with::skip_serializing_none] diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 18a37ad3..2d7fa7b6 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -428,10 +428,11 @@ fn empty_error() -> UpdateKind { #[cfg(test)] mod test { use crate::types::{ - Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, MediaKind, MediaText, - Message, MessageCommon, MessageId, MessageKind, MessageReactionCountUpdated, - MessageReactionUpdated, PublicChatChannel, PublicChatKind, PublicChatSupergroup, - ReactionCount, ReactionType, ReactionTypeKind, Update, UpdateId, UpdateKind, User, UserId, + Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, LinkPreviewOptions, + MediaKind, MediaText, Message, MessageCommon, MessageId, MessageKind, + MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, PublicChatKind, + PublicChatSupergroup, ReactionCount, ReactionType, ReactionTypeKind, Update, UpdateId, + UpdateKind, User, UserId, }; use chrono::DateTime; @@ -460,7 +461,8 @@ mod test { "type":"private" }, "date":1569518342, - "text":"hello there" + "text":"hello there", + "link_preview_options":{"is_disabled":true} } }"#; @@ -507,6 +509,13 @@ mod test { media_kind: MediaKind::Text(MediaText { text: String::from("hello there"), entities: vec![], + link_preview_options: Some(LinkPreviewOptions { + is_disabled: Some(true), + url: None, + prefer_small_media: None, + prefer_large_media: None, + show_above_text: None, + }), }), reply_markup: None, sender_chat: None, From 1abf1fa519359fb1b4e2a8a94fbb91890f68d068 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 02:06:18 +0400 Subject: [PATCH 24/51] Add `link_preview_options` getter to `Message` --- crates/teloxide-core/src/types/message.rs | 31 +++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index a4b4372f..a375597e 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -681,16 +681,16 @@ mod getters { use std::ops::Deref; use crate::types::{ - self, message::MessageKind::*, Chat, ChatId, ChatMigration, MaybeInaccessibleMessage, - MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, - MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, MediaText, MediaVenue, - MediaVideo, MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, - MessageChatShared, MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, - MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, - MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, - MessageOrigin, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, - MessageSuccessfulPayment, MessageSupergroupChatCreated, MessageUsersShared, - MessageVideoChatParticipantsInvited, PhotoSize, TextQuote, User, + self, message::MessageKind::*, Chat, ChatId, ChatMigration, LinkPreviewOptions, + MaybeInaccessibleMessage, MediaAnimation, MediaAudio, MediaContact, MediaDocument, + MediaGame, MediaKind, MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaStory, + MediaText, MediaVenue, MediaVideo, MediaVideoNote, MediaVoice, Message, + MessageChannelChatCreated, MessageChatShared, MessageCommon, MessageConnectedWebsite, + MessageDeleteChatPhoto, MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, + MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, + MessageNewChatTitle, MessageOrigin, MessagePassportData, MessagePinned, + MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated, + MessageUsersShared, MessageVideoChatParticipantsInvited, PhotoSize, TextQuote, User, }; use super::{ @@ -870,6 +870,17 @@ mod getters { } } + #[must_use] + pub fn link_preview_options(&self) -> Option<&LinkPreviewOptions> { + match &self.kind { + Common(MessageCommon { + media_kind: MediaKind::Text(MediaText { link_preview_options, .. }), + .. + }) => link_preview_options.as_ref(), + _ => None, + } + } + /// Returns message entities that represent text formatting. /// /// **Note:** you probably want to use [`parse_caption_entities`] From e5edaceb22c487370edf706308f472766d79ee96 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 14:20:40 +0400 Subject: [PATCH 25/51] Move deserialization prefix for `giveaway_message_id` field to types module because it will be used in `ChatBoostSourceGiveaway` later --- crates/teloxide-core/src/types.rs | 5 +++++ crates/teloxide-core/src/types/giveaway_winners.rs | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 643fe086..97bcbf76 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -306,6 +306,11 @@ pub use recipient::*; pub use seconds::*; pub use user_id::*; +use serde_with::with_prefix; + +// Deserialization prefix for giveaway_message_id field used in GiveawayWinners +with_prefix!(prefix_giveaway_message_id "giveaway_"); + /// Converts an `i64` timestamp to a `choro::DateTime`, producing serde error /// for invalid timestamps pub(crate) fn serde_timestamp( diff --git a/crates/teloxide-core/src/types/giveaway_winners.rs b/crates/teloxide-core/src/types/giveaway_winners.rs index 9dddc3e6..b5043ad8 100644 --- a/crates/teloxide-core/src/types/giveaway_winners.rs +++ b/crates/teloxide-core/src/types/giveaway_winners.rs @@ -1,11 +1,8 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use serde_with::with_prefix; use crate::types::{Chat, MessageId, User}; -with_prefix!(prefix_giveaway "giveaway_"); - /// This object represents a message about the completion of a giveaway with /// public winners. #[serde_with::skip_serializing_none] @@ -15,7 +12,7 @@ pub struct GiveawayWinners { pub chat: Chat, /// Identifier of the messsage with the giveaway in the chat - #[serde(flatten, with = "prefix_giveaway")] + #[serde(flatten, with = "crate::types::prefix_giveaway_message_id")] pub giveaway_message_id: MessageId, /// Point in time (Unix timestamp) when winners of the giveaway were From 65b46972f905b90ea6287c9114fdda2f704a2b7b Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 14:37:12 +0400 Subject: [PATCH 26/51] Add `ChatBoost` and `ChatBoostSource` structs --- crates/teloxide-core/src/types.rs | 5 + crates/teloxide-core/src/types/chat_boost.rs | 52 +++++++ .../src/types/chat_boost_source.rs | 131 ++++++++++++++++++ 3 files changed, 188 insertions(+) create mode 100644 crates/teloxide-core/src/types/chat_boost.rs create mode 100644 crates/teloxide-core/src/types/chat_boost_source.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 97bcbf76..eea23ca5 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -13,6 +13,8 @@ pub use callback_query::*; pub use chat::*; pub use chat_action::*; pub use chat_administrator_rights::*; +pub use chat_boost::*; +pub use chat_boost_source::*; pub use chat_full_info::*; pub use chat_invite_link::*; pub use chat_join_request::*; @@ -156,6 +158,8 @@ mod callback_query; mod chat; mod chat_action; mod chat_administrator_rights; +mod chat_boost; +mod chat_boost_source; mod chat_full_info; mod chat_invite_link; mod chat_join_request; @@ -309,6 +313,7 @@ pub use user_id::*; use serde_with::with_prefix; // Deserialization prefix for giveaway_message_id field used in GiveawayWinners +// and ChatBoostSourceGiveaway with_prefix!(prefix_giveaway_message_id "giveaway_"); /// Converts an `i64` timestamp to a `choro::DateTime`, producing serde error diff --git a/crates/teloxide-core/src/types/chat_boost.rs b/crates/teloxide-core/src/types/chat_boost.rs new file mode 100644 index 00000000..06e10c07 --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost.rs @@ -0,0 +1,52 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::ChatBoostSource; + +/// This object contains information about a chat boost. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoost { + /// Unique identifier of the boost. + pub boost_id: String, + + /// Point in time (Unix timestamp) when the chat was boosted. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub add_date: DateTime, + + /// Point in time (Unix timestamp) when the boost will automatically expire, + /// unless the booster's Telegram Premium subscription is prolonged. + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub expiration_date: DateTime, + + /// Source of the added boost. + pub source: ChatBoostSource, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_source.rs b/crates/teloxide-core/src/types/chat_boost_source.rs new file mode 100644 index 00000000..3ffd7451 --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_source.rs @@ -0,0 +1,131 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{MessageId, User}; + +/// This object describes the source of a chat boost. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSource { + #[serde(flatten)] + pub kind: ChatBoostSourceKind, +} + +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "source")] +pub enum ChatBoostSourceKind { + Premium(ChatBoostSourcePremium), + GiftCode(ChatBoostSourceGiftCode), + Giveaway(ChatBoostSourceGiveaway), +} + +/// The boost was obtained by subscribing to Telegram Premium or by gifting a +/// Telegram Premium subscription to another user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourcePremium { + /// User that boosted the chat. + pub user: User, +} + +/// The boost was obtained by the creation of Telegram Premium gift codes to +/// boost a chat. Each such code boosts the chat 4 times for the duration of the +/// corresponding Telegram Premium subscription. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourceGiftCode { + /// User for which the gift code was created. + pub user: User, +} + +/// The boost was obtained by the creation of a Telegram Premium giveaway. This +/// boosts the chat 4 times for the duration of the corresponding Telegram +/// Premium subscription. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostSourceGiveaway { + /// Identifier of a message in the chat with the giveaway; the message could + /// have been deleted already. May be 0 if the message isn't sent yet. + #[serde(flatten, with = "crate::types::prefix_giveaway_message_id")] + pub giveaway_message_id: MessageId, + + /// User that won the prize in the giveaway if any. + pub user: Option, + + /// `true`, if the giveaway was completed, but there was no user to win the + /// prize. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_unclaimed: bool, +} + +impl ChatBoostSource { + #[must_use] + pub fn user(&self) -> Option<&User> { + Some(match &self.kind { + ChatBoostSourceKind::Premium(premium) => &premium.user, + ChatBoostSourceKind::GiftCode(gift_code) => &gift_code.user, + ChatBoostSourceKind::Giveaway(giveaway) => return giveaway.user.as_ref(), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_premium() { + let data = r#" + { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + "#; + serde_json::from_str::(data).unwrap(); + } + + #[test] + fn deserialize_gift_code() { + let data = r#" + { + "source": "gift_code", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": false + } + } + "#; + serde_json::from_str::(data).unwrap(); + } + + #[test] + fn deserialize_giveaway() { + let data = r#" + { + "source": "giveaway", + "giveaway_message_id": 420, + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": false + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} From dbf138a5b30b712883484f12ae079ee255fa5870 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 17:42:26 +0400 Subject: [PATCH 27/51] Add `ChatBoostUpdated` and `ChatBoostRemoved` updates --- crates/teloxide-core/src/types.rs | 4 + .../teloxide-core/src/types/allowed_update.rs | 2 + .../src/types/chat_boost_removed.rs | 54 +++++ .../src/types/chat_boost_updated.rs | 49 +++++ crates/teloxide-core/src/types/update.rs | 208 +++++++++++++++++- crates/teloxide/src/dispatching/filter_ext.rs | 2 + .../src/dispatching/handler_description.rs | 2 + 7 files changed, 313 insertions(+), 8 deletions(-) create mode 100644 crates/teloxide-core/src/types/chat_boost_removed.rs create mode 100644 crates/teloxide-core/src/types/chat_boost_updated.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index eea23ca5..ae8375d8 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -14,7 +14,9 @@ pub use chat::*; pub use chat_action::*; pub use chat_administrator_rights::*; pub use chat_boost::*; +pub use chat_boost_removed::*; pub use chat_boost_source::*; +pub use chat_boost_updated::*; pub use chat_full_info::*; pub use chat_invite_link::*; pub use chat_join_request::*; @@ -159,7 +161,9 @@ mod chat; mod chat_action; mod chat_administrator_rights; mod chat_boost; +mod chat_boost_removed; mod chat_boost_source; +mod chat_boost_updated; mod chat_full_info; mod chat_invite_link; mod chat_join_request; diff --git a/crates/teloxide-core/src/types/allowed_update.rs b/crates/teloxide-core/src/types/allowed_update.rs index a77040b3..12949ec5 100644 --- a/crates/teloxide-core/src/types/allowed_update.rs +++ b/crates/teloxide-core/src/types/allowed_update.rs @@ -19,4 +19,6 @@ pub enum AllowedUpdate { MyChatMember, ChatMember, ChatJoinRequest, + ChatBoost, + RemovedChatBoost, } diff --git a/crates/teloxide-core/src/types/chat_boost_removed.rs b/crates/teloxide-core/src/types/chat_boost_removed.rs new file mode 100644 index 00000000..094a0bde --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_removed.rs @@ -0,0 +1,54 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, ChatBoostSource}; + +/// This object represents a boost removed from a chat. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostRemoved { + /// Chat which was boosted + pub chat: Chat, + + /// Unique identifier of the boost + pub boost_id: String, + + /// Point in time (Unix timestamp) when the boost was removed + #[serde(with = "crate::types::serde_date_from_unix_timestamp")] + pub remove_date: DateTime, + + /// Source of the removed boost + pub source: ChatBoostSource, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "remove_date": 1745089963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/chat_boost_updated.rs b/crates/teloxide-core/src/types/chat_boost_updated.rs new file mode 100644 index 00000000..b4646b5f --- /dev/null +++ b/crates/teloxide-core/src/types/chat_boost_updated.rs @@ -0,0 +1,49 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{Chat, ChatBoost}; + +/// This object represents a boost added to a chat or changed. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ChatBoostUpdated { + /// Chat which was boosted + pub chat: Chat, + + /// Infomation about the chat boost + pub boost: ChatBoost, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost": { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + "#; + serde_json::from_str::(data).unwrap(); + } +} diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 2d7fa7b6..5a1e8804 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -3,9 +3,9 @@ use serde::{de::MapAccess, Deserialize, Serialize, Serializer}; use serde_json::Value; use crate::types::{ - CallbackQuery, Chat, ChatJoinRequest, ChatMemberUpdated, ChosenInlineResult, InlineQuery, - Message, MessageReactionCountUpdated, MessageReactionUpdated, Poll, PollAnswer, - PreCheckoutQuery, ShippingQuery, User, + CallbackQuery, Chat, ChatBoostRemoved, ChatBoostUpdated, ChatJoinRequest, ChatMemberUpdated, + ChosenInlineResult, InlineQuery, Message, MessageReactionCountUpdated, MessageReactionUpdated, + Poll, PollAnswer, PreCheckoutQuery, ShippingQuery, User, }; /// This [object] represents an incoming update. @@ -127,6 +127,14 @@ pub enum UpdateKind { /// updates. ChatJoinRequest(ChatJoinRequest), + /// A chat boost was added or changed. The bot must be an administrator in + /// the chat to receive these updates. + ChatBoost(ChatBoostUpdated), + + /// A boost was removed from a chat. The bot must be an administrator in the + /// chat to receive these updates. + RemovedChatBoost(ChatBoostRemoved), + /// An error that happened during deserialization. /// /// This allows `teloxide` to continue working even if telegram adds a new @@ -160,6 +168,8 @@ impl Update { MyChatMember(m) | ChatMember(m) => &m.from, ChatJoinRequest(r) => &r.from, + ChatBoost(b) => return b.boost.source.user(), + RemovedChatBoost(b) => return b.source.user(), MessageReactionCount(_) | Poll(_) | Error(_) => return None, }; @@ -236,6 +246,20 @@ impl Update { i4(member.mentioned_users()) } UpdateKind::ChatJoinRequest(request) => i5(request.mentioned_users()), + + UpdateKind::ChatBoost(b) => { + if let Some(user) = b.boost.source.user() { + return i1(once(user)); + } + i6(empty()) + } + UpdateKind::RemovedChatBoost(b) => { + if let Some(user) = b.source.user() { + return i1(once(user)); + } + i6(empty()) + } + UpdateKind::MessageReactionCount(_) | UpdateKind::Error(_) => i6(empty()), } } @@ -253,6 +277,8 @@ impl Update { ChatJoinRequest(c) => &c.chat, MessageReaction(r) => &r.chat, MessageReactionCount(r) => &r.chat, + ChatBoost(b) => &b.chat, + RemovedChatBoost(b) => &b.chat, InlineQuery(_) | ChosenInlineResult(_) @@ -361,6 +387,13 @@ impl<'de> Deserialize<'de> for UpdateKind { .next_value::() .ok() .map(UpdateKind::ChatJoinRequest), + "chat_boost" => { + map.next_value::().ok().map(UpdateKind::ChatBoost) + } + "removed_chat_boost" => map + .next_value::() + .ok() + .map(UpdateKind::RemovedChatBoost), _ => Some(empty_error()), }) .unwrap_or_else(empty_error); @@ -416,6 +449,10 @@ impl Serialize for UpdateKind { UpdateKind::ChatJoinRequest(v) => { s.serialize_newtype_variant(name, 15, "chat_join_request", v) } + UpdateKind::ChatBoost(v) => s.serialize_newtype_variant(name, 16, "chat_boost", v), + UpdateKind::RemovedChatBoost(v) => { + s.serialize_newtype_variant(name, 17, "removed_chat_boost", v) + } UpdateKind::Error(v) => v.serialize(s), } } @@ -428,11 +465,12 @@ fn empty_error() -> UpdateKind { #[cfg(test)] mod test { use crate::types::{ - Chat, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, LinkPreviewOptions, - MediaKind, MediaText, Message, MessageCommon, MessageId, MessageKind, - MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, PublicChatKind, - PublicChatSupergroup, ReactionCount, ReactionType, ReactionTypeKind, Update, UpdateId, - UpdateKind, User, UserId, + Chat, ChatBoost, ChatBoostRemoved, ChatBoostSource, ChatBoostSourceKind, + ChatBoostSourcePremium, ChatBoostUpdated, ChatFullInfo, ChatId, ChatKind, ChatPrivate, + ChatPublic, LinkPreviewOptions, MediaKind, MediaText, Message, MessageCommon, MessageId, + MessageKind, MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, + PublicChatKind, PublicChatSupergroup, ReactionCount, ReactionType, ReactionTypeKind, + Update, UpdateId, UpdateKind, User, UserId, }; use chrono::DateTime; @@ -948,4 +986,158 @@ mod test { let actual = serde_json::from_str::(json).unwrap(); assert_eq!(expected, actual); } + + #[test] + fn chat_boost_updated() { + let json = r#" + { + "update_id": 71651297, + "chat_boost": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost": { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + } + "#; + + let expected = Update { + id: UpdateId(71651297), + kind: UpdateKind::ChatBoost(ChatBoostUpdated { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + }), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + }, + boost: ChatBoost { + boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), + add_date: DateTime::from_timestamp(1721399621, 0).unwrap(), + expiration_date: DateTime::from_timestamp(1745088963, 0).unwrap(), + source: ChatBoostSource { + kind: ChatBoostSourceKind::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), + }, + }, + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn chat_boost_removed() { + let json = r#" + { + "update_id": 71651297, + "removed_chat_boost": { + "chat": { + "id": -1002236736395, + "title": "Test", + "type": "channel" + }, + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "remove_date": 1721999621, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + } + "#; + + let expected = Update { + id: UpdateId(71651297), + kind: UpdateKind::RemovedChatBoost(ChatBoostRemoved { + chat: Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + username: None, + linked_chat_id: None, + }), + description: None, + invite_link: None, + has_protected_content: None, + }), + photo: None, + available_reactions: None, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false, + has_aggressive_anti_spam_enabled: false, + chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + }, + boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), + remove_date: DateTime::from_timestamp(1721999621, 0).unwrap(), + source: ChatBoostSource { + kind: ChatBoostSourceKind::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), + }, + }), + }; + + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(expected, actual); + } } diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index d0f1d744..a2dbc7c6 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -161,4 +161,6 @@ define_update_ext! { (filter_my_chat_member, UpdateKind::MyChatMember, MyChatMember), (filter_chat_member, UpdateKind::ChatMember, ChatMember), (filter_chat_join_request, UpdateKind::ChatJoinRequest, ChatJoinRequest), + (filter_chat_boost, UpdateKind::ChatBoost, ChatBoost), + (filter_removed_chat_boost, UpdateKind::RemovedChatBoost, RemovedChatBoost), } diff --git a/crates/teloxide/src/dispatching/handler_description.rs b/crates/teloxide/src/dispatching/handler_description.rs index 2c1aefab..b18ebee8 100644 --- a/crates/teloxide/src/dispatching/handler_description.rs +++ b/crates/teloxide/src/dispatching/handler_description.rs @@ -77,6 +77,8 @@ impl EventKind for Kind { MyChatMember, ChatMember, ChatJoinRequest, + ChatBoost, + RemovedChatBoost, ] .into_iter() .map(Kind) From 48b7c7b24b82d25e4b805fa114fbd379a0a7aeb9 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 19:06:55 +0400 Subject: [PATCH 28/51] Add `UserChatBoosts` struct --- crates/teloxide-core/src/types.rs | 2 + .../src/types/user_chat_boosts.rs | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 crates/teloxide-core/src/types/user_chat_boosts.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index ae8375d8..50a888ae 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -132,6 +132,7 @@ pub use unit_false::*; pub use unit_true::*; pub use update::*; pub use user::*; +pub use user_chat_boosts::*; pub use user_profile_photos::*; pub use users_shared::*; pub use venue::*; @@ -252,6 +253,7 @@ mod unit_false; mod unit_true; mod update; mod user; +mod user_chat_boosts; mod user_profile_photos; mod users_shared; mod venue; diff --git a/crates/teloxide-core/src/types/user_chat_boosts.rs b/crates/teloxide-core/src/types/user_chat_boosts.rs new file mode 100644 index 00000000..4b11bd4b --- /dev/null +++ b/crates/teloxide-core/src/types/user_chat_boosts.rs @@ -0,0 +1,43 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ChatBoost; + +/// This object represents a list of boosts added to a chat by a user. +#[serde_with::skip_serializing_none] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct UserChatBoosts { + /// The list of boosts added to the chat by the user. + pub boosts: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize() { + let data = r#" + { + "boosts": [ + { + "boost_id": "4506e1b7e866e33fcbde78fe1746ec3a", + "add_date": 1721399621, + "expiration_date": 1745088963, + "source": { + "source": "premium", + "user": { + "id": 1459074222, + "is_bot": false, + "first_name": "shadowchain", + "username": "shdwchn10", + "language_code": "en", + "is_premium": true + } + } + } + ] + } + "#; + serde_json::from_str::(data).unwrap(); + } +} From 467468c546ea9495d2d8a73aaf3f8c24e58a8937 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 18:22:08 +0400 Subject: [PATCH 29/51] Add `getUserChatBoosts` TBA method --- crates/teloxide-core/schema.ron | 22 +++++++++++++++++++ crates/teloxide-core/src/adaptors/cache_me.rs | 1 + crates/teloxide-core/src/adaptors/erased.rs | 15 +++++++++++++ .../teloxide-core/src/adaptors/parse_mode.rs | 1 + .../src/adaptors/throttle/requester_impl.rs | 1 + crates/teloxide-core/src/adaptors/trace.rs | 1 + crates/teloxide-core/src/bot/api.rs | 12 ++++++++++ crates/teloxide-core/src/local_macros.rs | 8 +++++++ crates/teloxide-core/src/payloads.rs | 2 ++ .../src/payloads/get_user_chat_boosts.rs | 20 +++++++++++++++++ crates/teloxide-core/src/payloads/setters.rs | 15 +++++++------ .../teloxide-core/src/requests/requester.rs | 8 +++++++ 12 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 crates/teloxide-core/src/payloads/get_user_chat_boosts.rs diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 2169a8a7..c7a9a3b4 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -2931,6 +2931,28 @@ Schema( ), ], ), + Method( + names: ("getUserChatBoosts", "GetUserChatBoosts", "get_user_chat_boosts"), + return_ty: RawTy("UserChatBoosts"), + doc: Doc( + md: "Use this method to get the list of boosts added to a chat by a user. Requires administrator rights in the chat. Returns a [UserChatBoosts] object.", + md_links: {"UserChatBoosts": "https://core.telegram.org/bots/api#userchatboosts"}, + ), + tg_doc: "https://core.telegram.org/bots/api#getuserchatboosts", + tg_category: "Available methods", + params: [ + Param( + name: "chat_id", + ty: RawTy("Recipient"), + descr: Doc(md: "Unique identifier for the chat or username of the channel (in the format @channelusername)"), + ), + Param( + name: "user_id", + ty: RawTy("UserId"), + descr: Doc(md: "Unique identifier of the target user"), + ), + ], + ), Method( names: ("setMyCommands", "SetMyCommands", "set_my_commands"), return_ty: True, diff --git a/crates/teloxide-core/src/adaptors/cache_me.rs b/crates/teloxide-core/src/adaptors/cache_me.rs index 4ab96541..aafe2aca 100644 --- a/crates/teloxide-core/src/adaptors/cache_me.rs +++ b/crates/teloxide-core/src/adaptors/cache_me.rs @@ -161,6 +161,7 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, diff --git a/crates/teloxide-core/src/adaptors/erased.rs b/crates/teloxide-core/src/adaptors/erased.rs index 0c2a907d..7d4eae0f 100644 --- a/crates/teloxide-core/src/adaptors/erased.rs +++ b/crates/teloxide-core/src/adaptors/erased.rs @@ -260,6 +260,7 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, @@ -738,6 +739,12 @@ trait ErasableRequester<'a> { callback_query_id: String, ) -> ErasedRequest<'a, AnswerCallbackQuery, Self::Err>; + fn get_user_chat_boosts( + &self, + chat_id: Recipient, + user_id: UserId, + ) -> ErasedRequest<'a, GetUserChatBoosts, Self::Err>; + fn set_my_commands( &self, commands: Vec, @@ -1580,6 +1587,14 @@ where Requester::answer_callback_query(self, callback_query_id).erase() } + fn get_user_chat_boosts( + &self, + chat_id: Recipient, + user_id: UserId, + ) -> ErasedRequest<'a, GetUserChatBoosts, Self::Err> { + Requester::get_user_chat_boosts(self, chat_id, user_id).erase() + } + fn set_my_commands( &self, commands: Vec, diff --git a/crates/teloxide-core/src/adaptors/parse_mode.rs b/crates/teloxide-core/src/adaptors/parse_mode.rs index 1b4fba80..2906302e 100644 --- a/crates/teloxide-core/src/adaptors/parse_mode.rs +++ b/crates/teloxide-core/src/adaptors/parse_mode.rs @@ -242,6 +242,7 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, diff --git a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs index 0fbe3a5f..9c6fbd28 100644 --- a/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs +++ b/crates/teloxide-core/src/adaptors/throttle/requester_impl.rs @@ -144,6 +144,7 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, diff --git a/crates/teloxide-core/src/adaptors/trace.rs b/crates/teloxide-core/src/adaptors/trace.rs index 7c6ebfa8..4a865dc3 100644 --- a/crates/teloxide-core/src/adaptors/trace.rs +++ b/crates/teloxide-core/src/adaptors/trace.rs @@ -190,6 +190,7 @@ where unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, diff --git a/crates/teloxide-core/src/bot/api.rs b/crates/teloxide-core/src/bot/api.rs index 27912b41..2088bbcb 100644 --- a/crates/teloxide-core/src/bot/api.rs +++ b/crates/teloxide-core/src/bot/api.rs @@ -860,6 +860,18 @@ impl Requester for Bot { ) } + type GetUserChatBoosts = JsonRequest; + + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts + where + C: Into, + { + Self::GetUserChatBoosts::new( + self.clone(), + payloads::GetUserChatBoosts::new(chat_id, user_id), + ) + } + type SetMyCommands = JsonRequest; fn set_my_commands(&self, commands: C) -> Self::SetMyCommands diff --git a/crates/teloxide-core/src/local_macros.rs b/crates/teloxide-core/src/local_macros.rs index 242f807b..3fd2bbc7 100644 --- a/crates/teloxide-core/src/local_macros.rs +++ b/crates/teloxide-core/src/local_macros.rs @@ -1053,6 +1053,14 @@ macro_rules! requester_forward { $body!(answer_callback_query this (callback_query_id: C)) } }; + (@method get_user_chat_boosts $body:ident $ty:ident) => { + type GetUserChatBoosts = $ty![GetUserChatBoosts]; + + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts where C: Into { + let this = self; + $body!(get_user_chat_boosts this (chat_id: C, user_id: UserId)) + } + }; (@method set_my_commands $body:ident $ty:ident) => { type SetMyCommands = $ty![SetMyCommands]; diff --git a/crates/teloxide-core/src/payloads.rs b/crates/teloxide-core/src/payloads.rs index cdcd85f1..47fbac31 100644 --- a/crates/teloxide-core/src/payloads.rs +++ b/crates/teloxide-core/src/payloads.rs @@ -77,6 +77,7 @@ mod get_my_name; mod get_my_short_description; mod get_sticker_set; mod get_updates; +mod get_user_chat_boosts; mod get_user_profile_photos; mod get_webhook_info; mod hide_general_forum_topic; @@ -213,6 +214,7 @@ pub use get_my_name::{GetMyName, GetMyNameSetters}; pub use get_my_short_description::{GetMyShortDescription, GetMyShortDescriptionSetters}; pub use get_sticker_set::{GetStickerSet, GetStickerSetSetters}; pub use get_updates::{GetUpdates, GetUpdatesSetters}; +pub use get_user_chat_boosts::{GetUserChatBoosts, GetUserChatBoostsSetters}; pub use get_user_profile_photos::{GetUserProfilePhotos, GetUserProfilePhotosSetters}; pub use get_webhook_info::{GetWebhookInfo, GetWebhookInfoSetters}; pub use hide_general_forum_topic::{HideGeneralForumTopic, HideGeneralForumTopicSetters}; diff --git a/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs b/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs new file mode 100644 index 00000000..6cbfadc3 --- /dev/null +++ b/crates/teloxide-core/src/payloads/get_user_chat_boosts.rs @@ -0,0 +1,20 @@ +//! Generated by `codegen_payloads`, do not edit by hand. + +use serde::Serialize; + +use crate::types::{Recipient, UserChatBoosts, UserId}; + +impl_payload! { + /// Use this method to get the list of boosts added to a chat by a user. Requires administrator rights in the chat. Returns a [`UserChatBoosts`] object. + /// + /// [`UserChatBoosts`]: crate::types::UserChatBoosts + #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize)] + pub GetUserChatBoosts (GetUserChatBoostsSetters) => UserChatBoosts { + required { + /// Unique identifier for the chat or username of the channel (in the format @channelusername) + pub chat_id: Recipient [into], + /// Unique identifier of the target user + pub user_id: UserId, + } + } +} diff --git a/crates/teloxide-core/src/payloads/setters.rs b/crates/teloxide-core/src/payloads/setters.rs index 279fda1d..06a24b76 100644 --- a/crates/teloxide-core/src/payloads/setters.rs +++ b/crates/teloxide-core/src/payloads/setters.rs @@ -25,13 +25,14 @@ pub use crate::payloads::{ GetGameHighScoresSetters as _, GetMeSetters as _, GetMyCommandsSetters as _, GetMyDefaultAdministratorRightsSetters as _, GetMyDescriptionSetters as _, GetMyNameSetters as _, GetMyShortDescriptionSetters as _, GetStickerSetSetters as _, - GetUpdatesSetters as _, GetUserProfilePhotosSetters as _, GetWebhookInfoSetters as _, - HideGeneralForumTopicSetters as _, KickChatMemberSetters as _, LeaveChatSetters as _, - LogOutSetters as _, PinChatMessageSetters as _, PromoteChatMemberSetters as _, - ReopenForumTopicSetters as _, ReopenGeneralForumTopicSetters as _, - RestrictChatMemberSetters as _, RevokeChatInviteLinkSetters as _, SendAnimationSetters as _, - SendAudioSetters as _, SendChatActionSetters as _, SendContactSetters as _, - SendDiceSetters as _, SendDocumentSetters as _, SendGameSetters as _, SendInvoiceSetters as _, + GetUpdatesSetters as _, GetUserChatBoostsSetters as _, GetUserProfilePhotosSetters as _, + GetWebhookInfoSetters as _, HideGeneralForumTopicSetters as _, KickChatMemberSetters as _, + LeaveChatSetters as _, LogOutSetters as _, PinChatMessageSetters as _, + PromoteChatMemberSetters as _, ReopenForumTopicSetters as _, + ReopenGeneralForumTopicSetters as _, RestrictChatMemberSetters as _, + RevokeChatInviteLinkSetters as _, SendAnimationSetters as _, SendAudioSetters as _, + SendChatActionSetters as _, SendContactSetters as _, SendDiceSetters as _, + SendDocumentSetters as _, SendGameSetters as _, SendInvoiceSetters as _, SendLocationSetters as _, SendMediaGroupSetters as _, SendMessageSetters as _, SendPhotoSetters as _, SendPollSetters as _, SendStickerSetters as _, SendVenueSetters as _, SendVideoNoteSetters as _, SendVideoSetters as _, SendVoiceSetters as _, diff --git a/crates/teloxide-core/src/requests/requester.rs b/crates/teloxide-core/src/requests/requester.rs index 2936e8b2..977e2c19 100644 --- a/crates/teloxide-core/src/requests/requester.rs +++ b/crates/teloxide-core/src/requests/requester.rs @@ -817,6 +817,13 @@ pub trait Requester { where C: Into; + type GetUserChatBoosts: Request; + + /// For Telegram documentation see [`GetUserChatBoosts`]. + fn get_user_chat_boosts(&self, chat_id: C, user_id: UserId) -> Self::GetUserChatBoosts + where + C: Into; + type SetMyCommands: Request; /// For Telegram documentation see [`SetMyCommands`]. @@ -1363,6 +1370,7 @@ macro_rules! forward_all { unhide_general_forum_topic, unpin_all_general_forum_topic_messages, answer_callback_query, + get_user_chat_boosts, set_my_commands, get_my_commands, set_my_name, From b78dac4ee5f34cb2d570702c9cf786244d54edc4 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 21:44:20 +0400 Subject: [PATCH 30/51] Update CHANGELOG.md and README.md --- README.md | 2 +- crates/teloxide-core/CHANGELOG.md | 22 ++++++++++++++++++++++ crates/teloxide-core/README.md | 2 +- 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6c19b5aa..f0a53ea3 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - + diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 262f99cc..f95d1a9f 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -88,6 +88,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for TBA 6.9 ([#1095](pr1095)) - Add `can_post_stories`, `can_edit_stories` and `can_delete_stories` fields to `ChatMemberKind::Administrator`, `ChatAdministratorRights` and `PromoteChatMember` - Add `from_request` and `from_attachment_menu` fields to `WriteAccessAllowed` +- Support for TBA 7.0 + - Reactions: + - Add `ReactionType`, `MessageReactionUpdated` and `MessageReactionCountUpdated` structs + - Add `MessageReaction` and `MessageReactionCount` variants to `UpdateKind` enum + - Add `filter_message_reaction_updated` and `filter_message_reaction_count_updated` filters to `UpdateFilterExt` trait + - Add `set_message_reaction` TBA method to `Requester` trait + - Add `available_reactions` field to `Chat` struct + - Link Preview Customization + - Remove `disable_web_page_preview` field from `send_message` and `send_message` TBA methods and `InputMessageContentText` struct + - Add `LinkPreviewOptions` struct + - Add `link_preview_options` field to `InputMessageContentText` and `Message` structs + - Add `link_preview_options` field to `send_message` and `send_message` TBA methods + - Chat Boost + - Add `ChatBoost`, `ChatBoostSource`, `ChatBoostUpdated`, `ChatBoostRemoved` and `UserChatBoosts` structs + - Add `ChatBoost` and `RemovedChatBoost` variants to `UpdateKind` enum + - Add `filter_chat_boost` and `filter_removed_chat_boost` filters to `UpdateFilterExt` trait + - Add `get_user_chat_boosts` TBA method to `Requester` trait + - Giveaway: + - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` structs + - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` variants to `MessageKind` enum + - Add `giveaway`, `giveaway_created`, `giveaway_winners` and `giveaway_completed` getters to `Message` struct + - Add `filter_giveaway`, `filter_giveaway_completed`, `filter_giveaway_created` and `filter_giveaway_winners` filters to `MessageFilterExt` trait [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 diff --git a/crates/teloxide-core/README.md b/crates/teloxide-core/README.md index d4748be4..46bb7dd5 100644 --- a/crates/teloxide-core/README.md +++ b/crates/teloxide-core/README.md @@ -12,7 +12,7 @@ - + From 664717e07d76a981e5631ad6f25eff7c080d74ed Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Fri, 19 Jul 2024 22:43:22 +0400 Subject: [PATCH 31/51] Use ChatFullInfo::default() in gieaway, message_reaction and chat_boost tests --- crates/teloxide-core/src/types/message.rs | 8 ++++---- crates/teloxide-core/src/types/update.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index a375597e..c5e71570 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -2390,7 +2390,7 @@ mod tests { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }], winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), winner_count: 1, @@ -2499,7 +2499,7 @@ mod tests { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, via_bot: None, kind: MessageKind::Giveaway(MessageGiveaway { @@ -2522,7 +2522,7 @@ mod tests { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }], winners_selection_date: DateTime::from_timestamp(1721162701, 0) .unwrap(), @@ -2623,7 +2623,7 @@ mod tests { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None } + chat_full_info: ChatFullInfo::default() }, giveaway_message_id: MessageId(27), winners_selection_date: DateTime::from_timestamp(1721162701, 0).unwrap(), diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 5a1e8804..185e5ebe 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -882,7 +882,7 @@ mod test { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, message_id: MessageId(35), user: Some(User { @@ -962,7 +962,7 @@ mod test { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, message_id: MessageId(36), date: DateTime::from_timestamp(1721306391, 0).unwrap(), @@ -1039,7 +1039,7 @@ mod test { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, boost: ChatBoost { boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), @@ -1116,7 +1116,7 @@ mod test { message_auto_delete_time: None, has_hidden_members: false, has_aggressive_anti_spam_enabled: false, - chat_full_info: ChatFullInfo { emoji_status_expiration_date: None }, + chat_full_info: ChatFullInfo::default(), }, boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), remove_date: DateTime::from_timestamp(1721999621, 0).unwrap(), From b625e813cc394ebcc7b9aa51c50d94c7621995b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sat, 20 Jul 2024 08:39:27 +0300 Subject: [PATCH 32/51] Fix issue #945 (PR 946 originally) --- crates/teloxide-core/src/types/message.rs | 83 ++++++++++++------- crates/teloxide-core/src/types/update.rs | 28 ++++--- crates/teloxide/examples/admin.rs | 6 +- .../teloxide/examples/dispatching_features.rs | 8 +- crates/teloxide/src/dispatching/filter_ext.rs | 3 + 5 files changed, 77 insertions(+), 51 deletions(-) diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index c5e71570..a8589dd8 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -31,6 +31,15 @@ pub struct Message { #[serde(rename = "message_thread_id")] pub thread_id: Option, + /// Sender, empty for messages sent to channels. + pub from: Option, + + /// Sender of the message, sent on behalf of a chat. The channel itself for + /// channel messages. The supergroup itself for messages from anonymous + /// group administrators. The linked channel for messages automatically + /// forwarded to the discussion group + pub sender_chat: Option, + /// Date the message was sent in Unix time. #[serde(with = "crate::types::serde_date_from_unix_timestamp")] pub date: DateTime, @@ -38,6 +47,10 @@ pub struct Message { /// Conversation the message belongs to. pub chat: Chat, + /// `true`, if the message is sent to a forum topic. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_topic_message: bool, + /// Bot through which the message was sent. pub via_bot: Option, @@ -94,15 +107,6 @@ pub enum MessageKind { #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageCommon { - /// Sender, empty for messages sent to channels. - pub from: Option, - - /// Sender of the message, sent on behalf of a chat. The channel itself for - /// channel messages. The supergroup itself for messages from anonymous - /// group administrators. The linked channel for messages automatically - /// forwarded to the discussion group - pub sender_chat: Option, - /// Signature of the post author for messages in channels, or the custom /// title of an anonymous group administrator. pub author_signature: Option, @@ -130,12 +134,6 @@ pub struct MessageCommon { /// represented as ordinary `url` buttons. pub reply_markup: Option, - /// `true`, if the message is sent to a forum topic. - // FIXME: `is_topic_message` is included even in service messages, like ForumTopicCreated. - // more this to `Message` - #[serde(default, skip_serializing_if = "std::ops::Not::not")] - pub is_topic_message: bool, - /// `true`, if the message is a channel post that was automatically /// forwarded to the connected discussion group. #[serde(default, skip_serializing_if = "std::ops::Not::not")] @@ -708,12 +706,10 @@ mod getters { /// [telegram docs]: https://core.telegram.org/bots/api#message impl Message { /// Returns the user who sent the message. + #[deprecated(since = "0.13.0", note = "use `.from` field instead")] #[must_use] pub fn from(&self) -> Option<&User> { - match &self.kind { - Common(MessageCommon { from, .. }) => from.as_ref(), - _ => None, - } + self.from.as_ref() } #[must_use] @@ -724,12 +720,10 @@ mod getters { } } + #[deprecated(since = "0.13.0", note = "use `.sender_chat` field instead")] #[must_use] pub fn sender_chat(&self) -> Option<&Chat> { - match &self.kind { - Common(MessageCommon { sender_chat, .. }) => sender_chat.as_ref(), - _ => None, - } + self.sender_chat.as_ref() } #[must_use] @@ -1746,8 +1740,8 @@ impl Message { // Lets just hope we didn't forget something here... - self.from() - .into_iter() + self.from + .iter() .chain(self.via_bot.as_ref()) .chain(self.chat.mentioned_users_rec()) .chain(flatten(self.reply_to_message().map(Self::mentioned_users_rec))) @@ -1844,7 +1838,10 @@ mod tests { Message { id: MessageId(198283), thread_id: None, + from: None, + sender_chat: None, date: chrono::DateTime::from_timestamp(1567927221, 0).unwrap(), + is_topic_message: false, chat: Chat { id: ChatId(250918540), kind: ChatKind::Private(ChatPrivate { @@ -2101,9 +2098,9 @@ mod tests { chat_full_info: ChatFullInfo::default(), }; - assert!(message.from().unwrap().is_anonymous()); + assert!(message.from.as_ref().unwrap().is_anonymous()); assert_eq!(message.author_signature().unwrap(), "TITLE2"); - assert_eq!(message.sender_chat().unwrap(), &group); + assert_eq!(message.sender_chat.as_ref().unwrap(), &group); assert_eq!(&message.chat, &group); assert_eq!(message.forward_from_chat().unwrap(), &group); assert_eq!(message.forward_author_signature().unwrap(), "TITLE"); @@ -2126,7 +2123,7 @@ mod tests { assert_eq!(message.migrate_to_chat_id(), Some(&new)); // The user who initialized the migration - assert!(message.from().is_some()); + assert!(message.from.is_some()); // Migration from a common group let json = r#"{"chat":{"id":-1001555296434,"title":"test","type":"supergroup"},"date":1629404938,"from":{"first_name":"Group","id":1087968824,"is_bot":true,"username":"GroupAnonymousBot"},"message_id":1,"migrate_from_chat_id":-599075523,"sender_chat":{"id":-1001555296434,"title":"test","type":"supergroup"}}"#; @@ -2137,10 +2134,10 @@ mod tests { assert_eq!(message.migrate_from_chat_id(), Some(&old)); // Anonymous bot - assert!(message.from().is_some()); + assert!(message.from.is_some()); // The chat to which the group migrated - assert!(message.sender_chat().is_some()); + assert!(message.sender_chat.is_some()); } /// Regression test for @@ -2296,7 +2293,9 @@ mod tests { "message_thread_id":4 }"#; - let _: Message = serde_json::from_str(json).unwrap(); + let message: Message = serde_json::from_str(json).unwrap(); + // https://github.com/teloxide/teloxide/issues/945 + assert!(message.from.is_some()); } #[test] @@ -2480,6 +2479,28 @@ mod tests { giveaway_message: Some(Box::new(Message { id: MessageId(24), thread_id: None, + from: None, + sender_chat: Some(Chat { + id: ChatId(-1002236736395), + kind: ChatKind::Public(ChatPublic { + title: Some("Test".to_owned()), + kind: PublicChatKind::Channel(PublicChatChannel { + linked_chat_id: None, + username: None + }), + description: None, + invite_link: None, + has_protected_content: None + }), + chat_full_info: ChatFullInfo::default(), + available_reactions: None, + photo: None, + has_aggressive_anti_spam_enabled: false, + has_hidden_members: false, + message_auto_delete_time: None, + pinned_message: None + }), + is_topic_message: false, date: DateTime::from_timestamp(1721161230, 0).unwrap(), chat: Chat { id: ChatId(-1002236736395), diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 185e5ebe..7b6f24fb 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -156,7 +156,9 @@ impl Update { use UpdateKind::*; let from = match &self.kind { - Message(m) | EditedMessage(m) | ChannelPost(m) | EditedChannelPost(m) => m.from()?, + Message(m) | EditedMessage(m) | ChannelPost(m) | EditedChannelPost(m) => { + m.from.as_ref()? + } CallbackQuery(query) => &query.from, ChosenInlineResult(chosen) => &chosen.from, @@ -510,6 +512,18 @@ mod test { via_bot: None, id: MessageId(6557), thread_id: None, + from: Some(User { + id: UserId(218_485_655), + is_bot: false, + first_name: String::from("Waffle"), + last_name: None, + username: Some(String::from("WaffleLapkin")), + language_code: Some(String::from("en")), + is_premium: false, + added_to_attachment_menu: false, + }), + sender_chat: None, + is_topic_message: false, date, chat: Chat { id: ChatId(218_485_655), @@ -530,16 +544,6 @@ mod test { chat_full_info: ChatFullInfo::default(), }, kind: MessageKind::Common(MessageCommon { - from: Some(User { - id: UserId(218_485_655), - is_bot: false, - first_name: String::from("Waffle"), - last_name: None, - username: Some(String::from("WaffleLapkin")), - language_code: Some(String::from("en")), - is_premium: false, - added_to_attachment_menu: false, - }), reply_to_message: None, forward_origin: None, quote: None, @@ -556,9 +560,7 @@ mod test { }), }), reply_markup: None, - sender_chat: None, author_signature: None, - is_topic_message: false, is_automatic_forward: false, has_protected_content: false, }), diff --git a/crates/teloxide/examples/admin.rs b/crates/teloxide/examples/admin.rs index c54b7b67..a240a3bc 100644 --- a/crates/teloxide/examples/admin.rs +++ b/crates/teloxide/examples/admin.rs @@ -79,7 +79,7 @@ async fn kick_user(bot: Bot, msg: Message) -> ResponseResult<()> { match msg.reply_to_message() { Some(replied) => { // bot.unban_chat_member can also kicks a user from a group chat. - bot.unban_chat_member(msg.chat.id, replied.from().unwrap().id).await?; + bot.unban_chat_member(msg.chat.id, replied.from.as_ref().unwrap().id).await?; } None => { bot.send_message(msg.chat.id, "Use this command in reply to another message").await?; @@ -94,7 +94,7 @@ async fn ban_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> Some(replied) => { bot.kick_chat_member( msg.chat.id, - replied.from().expect("Must be MessageKind::Common").id, + replied.from.as_ref().expect("Must be MessageKind::Common").id, ) .until_date(msg.date + time) .await?; @@ -113,7 +113,7 @@ async fn mute_user(bot: Bot, msg: Message, time: Duration) -> ResponseResult<()> Some(replied) => { bot.restrict_chat_member( msg.chat.id, - replied.from().expect("Must be MessageKind::Common").id, + replied.from.as_ref().expect("Must be MessageKind::Common").id, ChatPermissions::empty(), ) .until_date(msg.date + time) diff --git a/crates/teloxide/examples/dispatching_features.rs b/crates/teloxide/examples/dispatching_features.rs index e5739a96..f69dc9de 100644 --- a/crates/teloxide/examples/dispatching_features.rs +++ b/crates/teloxide/examples/dispatching_features.rs @@ -34,7 +34,7 @@ async fn main() { .branch( // Filter a maintainer by a user ID. dptree::filter(|cfg: ConfigParameters, msg: Message| { - msg.from().map(|user| user.id == cfg.bot_maintainer).unwrap_or_default() + msg.from.map(|user| user.id == cfg.bot_maintainer).unwrap_or_default() }) .filter_command::() .endpoint(|msg: Message, bot: Bot, cmd: MaintainerCommands| async move { @@ -125,7 +125,7 @@ async fn simple_commands_handler( ) -> Result<(), teloxide::RequestError> { let text = match cmd { SimpleCommand::Help => { - if msg.from().unwrap().id == cfg.bot_maintainer { + if msg.from.unwrap().id == cfg.bot_maintainer { format!( "{}\n\n{}", SimpleCommand::descriptions(), @@ -138,7 +138,7 @@ async fn simple_commands_handler( } } SimpleCommand::Maintainer => { - if msg.from().unwrap().id == cfg.bot_maintainer { + if msg.from.as_ref().unwrap().id == cfg.bot_maintainer { "Maintainer is you!".into() } else if let Some(username) = cfg.maintainer_username { format!("Maintainer is @{username}") @@ -147,7 +147,7 @@ async fn simple_commands_handler( } } SimpleCommand::MyId => { - format!("{}", msg.from().unwrap().id) + format!("{}", msg.from.unwrap().id) } }; diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index a2dbc7c6..445acc52 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -1,4 +1,6 @@ #![allow(clippy::redundant_closure_call)] +// Required for the `filter_from` currently +#![allow(deprecated)] use dptree::{di::DependencyMap, Handler}; @@ -68,6 +70,7 @@ macro_rules! define_message_ext { } } +// FIXME: change macro so that we can filter things without getters // May be expanded in the future. define_message_ext! { // MessageCommon From d42146fbc693cc22330f99b6dd92582a5e740a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sat, 20 Jul 2024 09:22:02 +0300 Subject: [PATCH 33/51] Add field `external_reply` of struct` ExternalReplyInfo` to the `Message` --- crates/teloxide-core/src/types.rs | 3 +- .../src/types/external_reply_info.rs | 60 +++++++++++++++++++ crates/teloxide-core/src/types/message.rs | 8 ++- crates/teloxide-core/src/types/update.rs | 1 + 4 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 crates/teloxide-core/src/types/external_reply_info.rs diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 50a888ae..2d0af202 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -34,6 +34,7 @@ pub use dice_emoji::*; pub use document::*; pub use encrypted_credentials::*; pub use encrypted_passport_element::*; +pub use external_reply_info::*; pub use file::*; pub use force_reply::*; pub use forum_topic::*; @@ -180,6 +181,7 @@ mod contact; mod dice; mod dice_emoji; mod document; +mod external_reply_info; mod file; mod force_reply; mod forum_topic; @@ -443,7 +445,6 @@ pub(crate) mod option_url_from_string { } } -#[allow(dead_code)] pub(crate) mod option_msg_id_as_int { use crate::types::MessageId; diff --git a/crates/teloxide-core/src/types/external_reply_info.rs b/crates/teloxide-core/src/types/external_reply_info.rs new file mode 100644 index 00000000..866705e8 --- /dev/null +++ b/crates/teloxide-core/src/types/external_reply_info.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::{ + Animation, Audio, Chat, Contact, Dice, Document, Game, Giveaway, GiveawayWinners, Invoice, + LinkPreviewOptions, Location, MessageId, MessageOrigin, PhotoSize, Poll, Sticker, Story, Venue, + Video, VideoNote, Voice, +}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ExternalReplyInfo { + /// Origin of the message replied to by the given message + pub origin: MessageOrigin, + /// Chat the original message belongs to. Available only if the chat is a + /// supergroup or a channel. + pub chat: Option, + /// Unique message identifier inside the original chat. Available only if + /// the original chat is a supergroup or a channel. + #[serde(with = "crate::types::option_msg_id_as_int")] + pub message_id: Option, + /// Options used for link preview generation for the original message, if it + /// is a text message + pub link_preview_options: Option, + /// _true_, if the message media is covered by a spoiler animation + #[serde(default)] + pub has_media_spoiler: bool, + + #[serde(flatten)] + pub kind: ExternalReplyInfoKind, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum ExternalReplyInfoKind { + // Note: + // - `Venue` must be in front of `Location` + // - `Animation` must be in front of `Document` + // + // This is needed so serde doesn't parse `Venue` as `Location` or `Animation` as `Document` + // (for backward compatability telegram duplicates some fields) + // + // See + Animation(Animation), + Audio(Audio), + Contact(Contact), + Dice(Dice), + Document(Document), + Game(Game), + Venue(Venue), + Location(Location), + Photo(Vec), + Poll(Poll), + Sticker(Sticker), + Story(Story), + Giveaway(Giveaway), + GiveawayWinners(GiveawayWinners), + Video(Video), + VideoNote(VideoNote), + Voice(Voice), + Invoice(Invoice), +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index a8589dd8..93440682 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -6,8 +6,8 @@ use url::Url; use crate::types::{ Animation, Audio, BareChatId, Chat, ChatId, ChatShared, Contact, Dice, Document, - ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, - GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCompleted, + ExternalReplyInfo, ForumTopicClosed, ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, + Game, GeneralForumTopicHidden, GeneralForumTopicUnhidden, Giveaway, GiveawayCompleted, GiveawayCreated, GiveawayWinners, InlineKeyboardMarkup, Invoice, LinkPreviewOptions, Location, MaybeInaccessibleMessage, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, MessageOrigin, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, @@ -119,6 +119,10 @@ pub struct MessageCommon { /// itself is a reply. pub reply_to_message: Option>, + /// Information about the message that is being replied to, which may come + /// from another chat or forum topic + pub external_reply: Option, + /// For replies that quote part of the original message, the quoted part of /// the message pub quote: Option, diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index 7b6f24fb..d2a7477b 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -546,6 +546,7 @@ mod test { kind: MessageKind::Common(MessageCommon { reply_to_message: None, forward_origin: None, + external_reply: None, quote: None, edit_date: None, media_kind: MediaKind::Text(MediaText { From 5c88b0b72476d8d7c742f019acff355a9f4f5bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sat, 20 Jul 2024 10:02:01 +0300 Subject: [PATCH 34/51] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ crates/teloxide-core/CHANGELOG.md | 47 +++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9d73e6e..a7d77f12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use [deadpool-redis](https://crates.io/crates/deadpool-redis) for Redis connection pooling ([PR 1081](https://github.com/teloxide/teloxide/pull/1081)). - Add `MessageExt::filter_story` method for the corresponding `MediaKind::Story` variant ([PR 1087](https://github.com/teloxide/teloxide/pull/1087)). - Add `update_listeners::webhooks::Options::path`, an option to make the webhook server listen on a different path, which can be useful behind a reverse proxy. +- Add `filter_giveaway`, `filter_giveaway_completed`, `filter_giveaway_created` and `filter_giveaway_winners` filters to `MessageFilterExt` trait ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) ### Fixed @@ -69,6 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MSRV (Minimal Supported Rust Version) was bumped from `1.68.0` to `1.70.0` ([PR 996][https://github.com/teloxide/teloxide/pull/996]) - `axum` was bumped to `0.7`, along with related libraries used for webhooks ([PR 1093][https://github.com/teloxide/teloxide/pull/1093]) - `Polling`'s exponential backoff now results in 64 seconds maximum delay instead of 17 minutes ([PR 1113][https://github.com/teloxide/teloxide/pull/1113]) +- `filter_forward_from` was removed ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) ### Removed diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index f95d1a9f..66f476c4 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -88,18 +88,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for TBA 6.9 ([#1095](pr1095)) - Add `can_post_stories`, `can_edit_stories` and `can_delete_stories` fields to `ChatMemberKind::Administrator`, `ChatAdministratorRights` and `PromoteChatMember` - Add `from_request` and `from_attachment_menu` fields to `WriteAccessAllowed` -- Support for TBA 7.0 +- Support for TBA 7.0 ([#1101](pr1101)) - Reactions: - Add `ReactionType`, `MessageReactionUpdated` and `MessageReactionCountUpdated` structs - Add `MessageReaction` and `MessageReactionCount` variants to `UpdateKind` enum - Add `filter_message_reaction_updated` and `filter_message_reaction_count_updated` filters to `UpdateFilterExt` trait - Add `set_message_reaction` TBA method to `Requester` trait - Add `available_reactions` field to `Chat` struct + - Replies 2.0 + - Add the fields `MessageCommon::{external_reply, quote}` of types `ExternalReplyInfo` and `TextQuote` respectively - Link Preview Customization - Remove `disable_web_page_preview` field from `send_message` and `send_message` TBA methods and `InputMessageContentText` struct - Add `LinkPreviewOptions` struct - Add `link_preview_options` field to `InputMessageContentText` and `Message` structs - Add `link_preview_options` field to `send_message` and `send_message` TBA methods + - Multiple Message Actions + - Add TBA methods `delete_messages`, `forward_messages` and `copy_messages` to `Requester` trait - Chat Boost - Add `ChatBoost`, `ChatBoostSource`, `ChatBoostUpdated`, `ChatBoostRemoved` and `UserChatBoosts` structs - Add `ChatBoost` and `RemovedChatBoost` variants to `UpdateKind` enum @@ -108,8 +112,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Giveaway: - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` structs - Add `Giveaway`, `GiveawayCreated`, `GiveawayWinners` and `GiveawayCompleted` variants to `MessageKind` enum - - Add `giveaway`, `giveaway_created`, `giveaway_winners` and `giveaway_completed` getters to `Message` struct - - Add `filter_giveaway`, `filter_giveaway_completed`, `filter_giveaway_created` and `filter_giveaway_winners` filters to `MessageFilterExt` trait + - Add `giveaway`, `giveaway_created`, `giveaway_winners` and `giveaway_completed` getters to `Message` + - Other Changes + - Add fields `ChafFullInfo::{has_visible_history, accent_color_id, background_custom_emoji_id, profile_accent_color_id, profile_background_custom_emoji_id}` [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 @@ -119,6 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr1086]: https://github.com/teloxide/teloxide/pull/1086 [pr1087]: https://github.com/teloxide/teloxide/pull/1087 [pr1095]: https://github.com/teloxide/teloxide/pull/1095 +[pr1101]: https://github.com/teloxide/teloxide/pull/1101 ### Fixed @@ -130,6 +136,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Deserialization of empty (content-less) messages that can sometimes appear as a part of callback query ([#850][pr850], issue [#873][issue873]) - Type of `chat_id` in `send_game`: `u32` => `ChatId` ([#1066][pr1066]) - `Requester::Err` was bounded to the `AsResponseParameters`. In case of `RetryAfter(..)` errors the polling is paused for the specified delay instead of falling into the backoff strategy ([#1113][pr1113]) +- `SendPoll` documentation ([#1101][pr1101], issue [#1058][issue1058]) +- `from`, `sender_chat` and `is_topic_message` are moved into the `Message` from the `MessageCommon` ([#1101][pr1101], issue [#945][issue945]). Initially [#946][pr946] +- Add support for `blockquote` entity in received messages + in `MarkdownV2` and `HTML` ([#1101][pr1101], issue [#1062][issue1062]) [pr839]: https://github.com/teloxide/teloxide/pull/839 [pr879]: https://github.com/teloxide/teloxide/pull/879 @@ -191,6 +200,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Method `set_sticker_set_thumb` and it's parameter `thumb` have been renamed to `set_sticker_set_thumbnail` and `thumbnail` respectively - Fields `{InlineQueryResultArticle, InlineQueryResultContact, InlineQueryResultDocument, InlineQueryResultLocation, InlineQueryResultVenue}::{thumb_url, thumb_width, thumb_height}` have been renamed to `{thumbnail_url, thumbnail_width, thumbnail_height}` respectively - Field `{InlineQueryResultPhoto, InlineQueryResultVideo, InlineQueryResultGif, InlineQueryResultMpeg4Gif}::thumb_url` has been renamed to `thumbnail_url` +- Support for TBA 7.0 ([#1101](pr1101)) + - Replies 2.0 + - Parameter `reply_parameters` of type `ReplyParameters` replaces parameters `reply_to_message_id` and `allow_sending_without_reply` in the methods: + - `copy_message` + - `send_message` + - `send_photo` + - `send_video` + - `send_animation` + - `send_audio` + - `send_document` + - `send_sticker` + - `send_video_note` + - `send_voice` + - `send_location` + - `send_venue` + - `send_contact` + - `send_poll` + - `send_dice` + - `send_invoice` + - `send_game` + - `send_media_group` + - Request for multiple users + - Struct `KeyboardButtonRequestUser` was renamed to `KeyboardButtonRequestUsers` + added field `max_quantity` to it + - Field `KeyboardButton::request_user` was renamed to `request_users` + - `MessageUserShared` was renamed to `MessageUsersShared` + - Other Changes + - `Message::pinned_message` and `CallbackQuery::message` now have `MaybeInaccessibleMessage` type + - Field `emoji_status_custom_emoji_id` is allowed in non-private chats (moved to the `ChatFullInfo`) + - Struct `Forward` was replaced by `MessageOrigin` in `MessageCommon` + [pr852]: https://github.com/teloxide/teloxide/pull/853 [pr859]: https://github.com/teloxide/teloxide/pull/859 @@ -204,8 +243,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Deprecated - `Update::user`, use `Update::from` instead ([#850][pr850]) +- `Message::from()` and `Message::sender_chat()` in favour of fields with the same name([initially #946][pr946][#1101][pr1101]) [pr850]: https://github.com/teloxide/teloxide/pull/850 +[pr946]: https://github.com/teloxide/teloxide/pull/946 ### Removed From ddb991712f47994352b72b77b57b2b6e64375926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Sat, 20 Jul 2024 22:22:55 +0300 Subject: [PATCH 35/51] Add missing #[serde(flatten)] to the MessageId --- crates/teloxide-core/src/types/inaccessible_message.rs | 1 + crates/teloxide-core/src/types/message_origin.rs | 1 + crates/teloxide-core/src/types/reply_parameters.rs | 1 + 3 files changed, 3 insertions(+) diff --git a/crates/teloxide-core/src/types/inaccessible_message.rs b/crates/teloxide-core/src/types/inaccessible_message.rs index 8e1cd6d2..9302d493 100644 --- a/crates/teloxide-core/src/types/inaccessible_message.rs +++ b/crates/teloxide-core/src/types/inaccessible_message.rs @@ -9,5 +9,6 @@ pub struct InaccessibleMessage { /// Chat the message belonged to pub chat: Chat, /// Unique message identifier inside the chat + #[serde(flatten)] pub message_id: MessageId, } diff --git a/crates/teloxide-core/src/types/message_origin.rs b/crates/teloxide-core/src/types/message_origin.rs index 240539dd..1c1e4f0d 100644 --- a/crates/teloxide-core/src/types/message_origin.rs +++ b/crates/teloxide-core/src/types/message_origin.rs @@ -39,6 +39,7 @@ pub enum MessageOrigin { /// Channel chat to which the message was originally sent chat: Chat, /// Unique message identifier inside the chat + #[serde(flatten)] message_id: MessageId, /// Signature of the original post author author_signature: Option, diff --git a/crates/teloxide-core/src/types/reply_parameters.rs b/crates/teloxide-core/src/types/reply_parameters.rs index c39abfcf..57c02c47 100644 --- a/crates/teloxide-core/src/types/reply_parameters.rs +++ b/crates/teloxide-core/src/types/reply_parameters.rs @@ -8,6 +8,7 @@ use crate::types::{MessageId, Recipient}; pub struct ReplyParameters { /// Identifier of the message that will be replied to in the current chat, /// or in the chat _chat\_id_ if it is specified + #[serde(flatten)] pub message_id: MessageId, /// If the message to be replied to is from a different chat, unique /// identifier for the chat or username of the channel (in the format From 72b34d0ea3f236bc5b62ded41a55b4c0c6874674 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Sun, 21 Jul 2024 17:22:23 +0400 Subject: [PATCH 36/51] Bump supported TBA version to 7.0 in crates docs --- crates/teloxide-core/src/lib.rs | 2 +- crates/teloxide/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/lib.rs b/crates/teloxide-core/src/lib.rs index a2b8f688..2bc732ae 100644 --- a/crates/teloxide-core/src/lib.rs +++ b/crates/teloxide-core/src/lib.rs @@ -1,7 +1,7 @@ //! Core part of the [`teloxide`] library. //! //! This library provides tools for making requests to the [Telegram Bot API] -//! (Currently, version `6.9` is supported) with ease. The library is fully +//! (Currently, version `7.0` is supported) with ease. The library is fully //! asynchronous and built using [`tokio`]. //! //!```toml diff --git a/crates/teloxide/src/lib.rs b/crates/teloxide/src/lib.rs index 7be22d58..097643af 100644 --- a/crates/teloxide/src/lib.rs +++ b/crates/teloxide/src/lib.rs @@ -1,6 +1,6 @@ //! A full-featured framework that empowers you to easily build [Telegram bots] //! using [Rust]. It handles all the difficult stuff so you can focus only on -//! your business logic. Currently, version `6.9` of [Telegram Bot API] is +//! your business logic. Currently, version `7.0` of [Telegram Bot API] is //! supported. //! //! For a high-level overview, see [our GitHub repository](https://github.com/teloxide/teloxide). From ab069009b5bea346cb6d0236f322b5f16811d0ca Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 16:36:57 +0400 Subject: [PATCH 37/51] Mention in changelog that `disable_web_page_preview` was replaced by `link_preview_options` --- crates/teloxide-core/CHANGELOG.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 66f476c4..38ca8c93 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -98,10 +98,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Replies 2.0 - Add the fields `MessageCommon::{external_reply, quote}` of types `ExternalReplyInfo` and `TextQuote` respectively - Link Preview Customization - - Remove `disable_web_page_preview` field from `send_message` and `send_message` TBA methods and `InputMessageContentText` struct - - Add `LinkPreviewOptions` struct - - Add `link_preview_options` field to `InputMessageContentText` and `Message` structs - - Add `link_preview_options` field to `send_message` and `send_message` TBA methods + - `disable_web_page_preview` replaced with `link_preview_options`: + - Remove `disable_web_page_preview` field from `send_message` and `send_message` TBA methods and `InputMessageContentText` struct + - Add `LinkPreviewOptions` struct + - Add `link_preview_options` field to `InputMessageContentText` and `Message` structs + - Add `link_preview_options` field to `send_message` and `send_message` TBA methods - Multiple Message Actions - Add TBA methods `delete_messages`, `forward_messages` and `copy_messages` to `Requester` trait - Chat Boost From 62000c820439e1352021ad851d38f3ee6bac8a02 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 17:07:21 +0400 Subject: [PATCH 38/51] Replace useless structs `ChatBoostSource` and `ReactionType` with enums --- crates/teloxide-core/CHANGELOG.md | 6 +- crates/teloxide-core/src/types/chat.rs | 8 +- .../src/types/chat_boost_source.rs | 17 ++--- .../teloxide-core/src/types/reaction_type.rs | 21 ++--- crates/teloxide-core/src/types/update.rs | 76 ++++++++----------- 5 files changed, 50 insertions(+), 78 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 38ca8c93..44a0fe54 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -90,7 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `from_request` and `from_attachment_menu` fields to `WriteAccessAllowed` - Support for TBA 7.0 ([#1101](pr1101)) - Reactions: - - Add `ReactionType`, `MessageReactionUpdated` and `MessageReactionCountUpdated` structs + - Add `ReactionType` enum + - Add `MessageReactionUpdated` and `MessageReactionCountUpdated` structs - Add `MessageReaction` and `MessageReactionCount` variants to `UpdateKind` enum - Add `filter_message_reaction_updated` and `filter_message_reaction_count_updated` filters to `UpdateFilterExt` trait - Add `set_message_reaction` TBA method to `Requester` trait @@ -106,7 +107,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Multiple Message Actions - Add TBA methods `delete_messages`, `forward_messages` and `copy_messages` to `Requester` trait - Chat Boost - - Add `ChatBoost`, `ChatBoostSource`, `ChatBoostUpdated`, `ChatBoostRemoved` and `UserChatBoosts` structs + - Add `ChatBoostSource` enum + - Add `ChatBoost`, `ChatBoostUpdated`, `ChatBoostRemoved` and `UserChatBoosts` structs - Add `ChatBoost` and `RemovedChatBoost` variants to `UpdateKind` enum - Add `filter_chat_boost` and `filter_removed_chat_boost` filters to `UpdateFilterExt` trait - Add `get_user_chat_boosts` TBA method to `Requester` trait diff --git a/crates/teloxide-core/src/types/chat.rs b/crates/teloxide-core/src/types/chat.rs index eeef702f..efa276d2 100644 --- a/crates/teloxide-core/src/types/chat.rs +++ b/crates/teloxide-core/src/types/chat.rs @@ -609,9 +609,7 @@ mod tests { has_protected_content: None, }), photo: None, - available_reactions: Some(vec![ReactionType { - kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, - }]), + available_reactions: Some(vec![ReactionType::Emoji { emoji: "🌭".to_owned() }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, @@ -649,9 +647,7 @@ mod tests { has_restricted_voice_and_video_messages: None, }), photo: None, - available_reactions: Some(vec![ReactionType { - kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, - }]), + available_reactions: Some(vec![ReactionType::Emoji { emoji: "🌭".to_owned() }]), pinned_message: None, message_auto_delete_time: None, has_hidden_members: false, diff --git a/crates/teloxide-core/src/types/chat_boost_source.rs b/crates/teloxide-core/src/types/chat_boost_source.rs index 3ffd7451..561c0ae5 100644 --- a/crates/teloxide-core/src/types/chat_boost_source.rs +++ b/crates/teloxide-core/src/types/chat_boost_source.rs @@ -3,18 +3,11 @@ use serde::{Deserialize, Serialize}; use crate::types::{MessageId, User}; /// This object describes the source of a chat boost. -#[serde_with::skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct ChatBoostSource { - #[serde(flatten)] - pub kind: ChatBoostSourceKind, -} - #[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "source")] -pub enum ChatBoostSourceKind { +pub enum ChatBoostSource { Premium(ChatBoostSourcePremium), GiftCode(ChatBoostSourceGiftCode), Giveaway(ChatBoostSourceGiveaway), @@ -62,10 +55,10 @@ pub struct ChatBoostSourceGiveaway { impl ChatBoostSource { #[must_use] pub fn user(&self) -> Option<&User> { - Some(match &self.kind { - ChatBoostSourceKind::Premium(premium) => &premium.user, - ChatBoostSourceKind::GiftCode(gift_code) => &gift_code.user, - ChatBoostSourceKind::Giveaway(giveaway) => return giveaway.user.as_ref(), + Some(match &self { + Self::Premium(premium) => &premium.user, + Self::GiftCode(gift_code) => &gift_code.user, + Self::Giveaway(giveaway) => return giveaway.user.as_ref(), }) } } diff --git a/crates/teloxide-core/src/types/reaction_type.rs b/crates/teloxide-core/src/types/reaction_type.rs index 7a3250e4..94033793 100644 --- a/crates/teloxide-core/src/types/reaction_type.rs +++ b/crates/teloxide-core/src/types/reaction_type.rs @@ -1,19 +1,10 @@ use serde::{Deserialize, Serialize}; -/// The reaction type is based on an emoji. -#[serde_with::skip_serializing_none] -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct ReactionType { - /// Kind of this reaction type - emoji or custom emoji. - #[serde(flatten)] - pub kind: ReactionTypeKind, -} - -/// Kind of a [`ReactionType`] - emoji or custom emoji. +/// The reaction type is based on an emoji or custom emoji. #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] -pub enum ReactionTypeKind { +pub enum ReactionType { /// "emoji" or "custom_emoji" reaction Emoji { /// Reaction emoji. Currently, it can be one of "👍", "👎", "❤", "🔥", @@ -36,16 +27,16 @@ pub enum ReactionTypeKind { impl ReactionType { #[must_use] pub fn emoji(&self) -> Option<&String> { - match &self.kind { - ReactionTypeKind::Emoji { emoji } => Some(emoji), + match &self { + Self::Emoji { emoji } => Some(emoji), _ => None, } } #[must_use] pub fn custom_emoji_id(&self) -> Option<&String> { - match &self.kind { - ReactionTypeKind::CustomEmoji { custom_emoji_id } => Some(custom_emoji_id), + match &self { + Self::CustomEmoji { custom_emoji_id } => Some(custom_emoji_id), _ => None, } } diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index d2a7477b..cdd0dd57 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -467,12 +467,12 @@ fn empty_error() -> UpdateKind { #[cfg(test)] mod test { use crate::types::{ - Chat, ChatBoost, ChatBoostRemoved, ChatBoostSource, ChatBoostSourceKind, - ChatBoostSourcePremium, ChatBoostUpdated, ChatFullInfo, ChatId, ChatKind, ChatPrivate, - ChatPublic, LinkPreviewOptions, MediaKind, MediaText, Message, MessageCommon, MessageId, - MessageKind, MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, - PublicChatKind, PublicChatSupergroup, ReactionCount, ReactionType, ReactionTypeKind, - Update, UpdateId, UpdateKind, User, UserId, + Chat, ChatBoost, ChatBoostRemoved, ChatBoostSource, ChatBoostSourcePremium, + ChatBoostUpdated, ChatFullInfo, ChatId, ChatKind, ChatPrivate, ChatPublic, + LinkPreviewOptions, MediaKind, MediaText, Message, MessageCommon, MessageId, MessageKind, + MessageReactionCountUpdated, MessageReactionUpdated, PublicChatChannel, PublicChatKind, + PublicChatSupergroup, ReactionCount, ReactionType, Update, UpdateId, UpdateKind, User, + UserId, }; use chrono::DateTime; @@ -901,9 +901,7 @@ mod test { actor_chat: None, date: DateTime::from_timestamp(1721306082, 0).unwrap(), old_reaction: vec![], - new_reaction: vec![ReactionType { - kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, - }], + new_reaction: vec![ReactionType::Emoji { emoji: "🌭".to_owned() }], }), }; @@ -971,15 +969,11 @@ mod test { date: DateTime::from_timestamp(1721306391, 0).unwrap(), reactions: vec![ ReactionCount { - r#type: ReactionType { - kind: ReactionTypeKind::Emoji { emoji: "🗿".to_owned() }, - }, + r#type: ReactionType::Emoji { emoji: "🗿".to_owned() }, total_count: 2, }, ReactionCount { - r#type: ReactionType { - kind: ReactionTypeKind::Emoji { emoji: "🌭".to_owned() }, - }, + r#type: ReactionType::Emoji { emoji: "🌭".to_owned() }, total_count: 1, }, ], @@ -1048,20 +1042,18 @@ mod test { boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), add_date: DateTime::from_timestamp(1721399621, 0).unwrap(), expiration_date: DateTime::from_timestamp(1745088963, 0).unwrap(), - source: ChatBoostSource { - kind: ChatBoostSourceKind::Premium(ChatBoostSourcePremium { - user: User { - id: UserId(1459074222), - is_bot: false, - first_name: "shadowchain".to_owned(), - last_name: None, - username: Some("shdwchn10".to_owned()), - language_code: Some("en".to_owned()), - is_premium: true, - added_to_attachment_menu: false, - }, - }), - }, + source: ChatBoostSource::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), }, }), }; @@ -1123,20 +1115,18 @@ mod test { }, boost_id: "4506e1b7e866e33fcbde78fe1746ec3a".to_owned(), remove_date: DateTime::from_timestamp(1721999621, 0).unwrap(), - source: ChatBoostSource { - kind: ChatBoostSourceKind::Premium(ChatBoostSourcePremium { - user: User { - id: UserId(1459074222), - is_bot: false, - first_name: "shadowchain".to_owned(), - last_name: None, - username: Some("shdwchn10".to_owned()), - language_code: Some("en".to_owned()), - is_premium: true, - added_to_attachment_menu: false, - }, - }), - }, + source: ChatBoostSource::Premium(ChatBoostSourcePremium { + user: User { + id: UserId(1459074222), + is_bot: false, + first_name: "shadowchain".to_owned(), + last_name: None, + username: Some("shdwchn10".to_owned()), + language_code: Some("en".to_owned()), + is_premium: true, + added_to_attachment_menu: false, + }, + }), }), }; From ae88d60b4eab0c1fc8cd364571c3c64dce354d80 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 17:07:37 +0400 Subject: [PATCH 39/51] Fix comments in `ReactionType` enum --- crates/teloxide-core/src/types/reaction_type.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/reaction_type.rs b/crates/teloxide-core/src/types/reaction_type.rs index 94033793..9ba328fb 100644 --- a/crates/teloxide-core/src/types/reaction_type.rs +++ b/crates/teloxide-core/src/types/reaction_type.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[serde(tag = "type")] #[serde(rename_all = "snake_case")] pub enum ReactionType { - /// "emoji" or "custom_emoji" reaction + /// Emoji reaction. Emoji { /// Reaction emoji. Currently, it can be one of "👍", "👎", "❤", "🔥", /// "🥰", "👏", "😁", "🤔", "🤯", "😱", "🤬", "😢", "🎉", "🤩", @@ -17,7 +17,7 @@ pub enum ReactionType { /// "💊", "🙊", "😎", "👾", "🤷‍♂", "🤷", "🤷‍♀", "😡" emoji: String, }, - /// Custom emoji sticker. + /// Custom emoji reaction. CustomEmoji { /// Custom emoji identifier. custom_emoji_id: String, From 4870338a751de197a8e997287d8a32030eee9734 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 17:27:16 +0400 Subject: [PATCH 40/51] Replace `Option` with `bool` in LinkPreviewOptions --- .../src/types/inline_query_result.rs | 128 +++++++++--------- .../src/types/input_message_content.rs | 8 +- .../src/types/link_preview_options.rs | 12 +- crates/teloxide-core/src/types/update.rs | 8 +- 4 files changed, 80 insertions(+), 76 deletions(-) diff --git a/crates/teloxide-core/src/types/inline_query_result.rs b/crates/teloxide-core/src/types/inline_query_result.rs index 1d58fd56..45f658ff 100644 --- a/crates/teloxide-core/src/types/inline_query_result.rs +++ b/crates/teloxide-core/src/types/inline_query_result.rs @@ -305,11 +305,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), caption_entities: None, @@ -359,11 +359,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), caption_entities: None, @@ -414,11 +414,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), caption_entities: None, @@ -473,11 +473,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), thumbnail_url: Some(reqwest::Url::parse("http://thumb_url/").unwrap()), @@ -527,11 +527,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -588,11 +588,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -639,11 +639,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -700,11 +700,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -753,11 +753,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -812,11 +812,11 @@ mod tests { parse_mode: Some(ParseMode::MarkdownV2), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -855,11 +855,11 @@ mod tests { entities: None, parse_mode: Some(ParseMode::MarkdownV2), link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), }); @@ -1095,11 +1095,11 @@ mod tests { message_text: String::from("message_text"), entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), parse_mode: None, }), @@ -1129,11 +1129,11 @@ mod tests { entities: None, parse_mode: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), }), reply_markup: Some(InlineKeyboardMarkup::default()), @@ -1188,11 +1188,11 @@ mod tests { entities: None, parse_mode: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), @@ -1280,11 +1280,11 @@ mod tests { entities: None, parse_mode: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), @@ -1343,11 +1343,11 @@ mod tests { entities: None, parse_mode: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), })), thumbnail_url: Some(Url::parse("http://thumb_url/").unwrap()), diff --git a/crates/teloxide-core/src/types/input_message_content.rs b/crates/teloxide-core/src/types/input_message_content.rs index ed517b7a..62c7da33 100644 --- a/crates/teloxide-core/src/types/input_message_content.rs +++ b/crates/teloxide-core/src/types/input_message_content.rs @@ -590,11 +590,11 @@ mod tests { parse_mode: None, entities: None, link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), }); diff --git a/crates/teloxide-core/src/types/link_preview_options.rs b/crates/teloxide-core/src/types/link_preview_options.rs index 4f56d8b0..96407e90 100644 --- a/crates/teloxide-core/src/types/link_preview_options.rs +++ b/crates/teloxide-core/src/types/link_preview_options.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct LinkPreviewOptions { /// `true`, if the link preview is disabled - pub is_disabled: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub is_disabled: bool, /// URL to use for the link preview. If empty, then the first URL found in /// the message text will be used @@ -14,16 +15,19 @@ pub struct LinkPreviewOptions { /// `true`, if the media in the link preview is suppposed to be shrunk; /// ignored if the URL isn't explicitly specified or media size change isn't /// supported for the preview - pub prefer_small_media: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub prefer_small_media: bool, /// `true`, if the media in the link preview is suppposed to be enlarged; /// ignored if the URL isn't explicitly specified or media size change isn't /// supported for the preview - pub prefer_large_media: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub prefer_large_media: bool, /// `true`, if the link preview must be shown above the message text; /// otherwise, the link preview will be shown below the message text - pub show_above_text: Option, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub show_above_text: bool, } #[cfg(test)] diff --git a/crates/teloxide-core/src/types/update.rs b/crates/teloxide-core/src/types/update.rs index cdd0dd57..33d3f4a0 100644 --- a/crates/teloxide-core/src/types/update.rs +++ b/crates/teloxide-core/src/types/update.rs @@ -553,11 +553,11 @@ mod test { text: String::from("hello there"), entities: vec![], link_preview_options: Some(LinkPreviewOptions { - is_disabled: Some(true), + is_disabled: true, url: None, - prefer_small_media: None, - prefer_large_media: None, - show_above_text: None, + prefer_small_media: false, + prefer_large_media: false, + show_above_text: false, }), }), reply_markup: None, From 25666a301cc678bdca853d7b2c7ff1d9fffa7887 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 17:30:16 +0400 Subject: [PATCH 41/51] Use `CountryCode` instead of `String` in `Giveaway` struct --- crates/teloxide-core/src/types/giveaway.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/teloxide-core/src/types/giveaway.rs b/crates/teloxide-core/src/types/giveaway.rs index 6865bf2a..91306106 100644 --- a/crates/teloxide-core/src/types/giveaway.rs +++ b/crates/teloxide-core/src/types/giveaway.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::Chat; +use crate::types::{Chat, CountryCode}; /// This object represents a message about a scheduled giveaway. #[serde_with::skip_serializing_none] @@ -37,7 +37,7 @@ pub struct Giveaway { /// empty, then all users can participate in the giveaway. Users with a /// phone number that was bought on Fragment can always participate in /// giveaways. - pub country_codes: Option>, + pub country_codes: Option>, /// The number of months the Telegram Premium subscription won from the /// giveaway will be active for From 3d4e25743e51760b73d29e7e0ef028422a534d88 Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Tue, 30 Jul 2024 17:32:20 +0400 Subject: [PATCH 42/51] Add FIXME to `boost_id` --- crates/teloxide-core/src/types/chat_boost_removed.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/teloxide-core/src/types/chat_boost_removed.rs b/crates/teloxide-core/src/types/chat_boost_removed.rs index 094a0bde..1be28960 100644 --- a/crates/teloxide-core/src/types/chat_boost_removed.rs +++ b/crates/teloxide-core/src/types/chat_boost_removed.rs @@ -10,6 +10,7 @@ pub struct ChatBoostRemoved { /// Chat which was boosted pub chat: Chat, + // FIXME: BoostId /// Unique identifier of the boost pub boost_id: String, From 65435ec430bf8ad6de51a1764fa439d16a2eb2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:04:47 +0300 Subject: [PATCH 43/51] Add `RequestId` type --- crates/teloxide-core/CHANGELOG.md | 4 +++- crates/teloxide-core/src/types.rs | 6 +++-- crates/teloxide-core/src/types/chat_shared.rs | 4 ++-- .../src/types/keyboard_button.rs | 7 +++++- .../src/types/keyboard_button_request_chat.rs | 6 ++--- ...er.rs => keyboard_button_request_users.rs} | 14 +++++++---- crates/teloxide-core/src/types/message.rs | 5 +++- crates/teloxide-core/src/types/request_id.rs | 24 +++++++++++++++++++ .../teloxide-core/src/types/users_shared.rs | 4 ++-- 9 files changed, 58 insertions(+), 16 deletions(-) rename crates/teloxide-core/src/types/{keyboard_button_request_user.rs => keyboard_button_request_users.rs} (89%) create mode 100644 crates/teloxide-core/src/types/request_id.rs diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 44a0fe54..4ee5206a 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -43,7 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `shared_chat` method to `Message` - Add `KeyboardButtonRequestUser` and `UserShared` types - Add `RequestUser` variant to `ButtonRequest` - - Add `UserShared` variant to `MessageKind` + - Add `UserShared` variant to `MessageKind` - Add `shared_user` method to `Message` - Support for TBA 6.6 ([#1040](pr1040)) - Add methods for working with bot's description: @@ -118,6 +118,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `giveaway`, `giveaway_created`, `giveaway_winners` and `giveaway_completed` getters to `Message` - Other Changes - Add fields `ChafFullInfo::{has_visible_history, accent_color_id, background_custom_emoji_id, profile_accent_color_id, profile_background_custom_emoji_id}` + - Add `RequestId` type [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 @@ -232,6 +233,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Message::pinned_message` and `CallbackQuery::message` now have `MaybeInaccessibleMessage` type - Field `emoji_status_custom_emoji_id` is allowed in non-private chats (moved to the `ChatFullInfo`) - Struct `Forward` was replaced by `MessageOrigin` in `MessageCommon` + - `RequestId` replaces `i32` in `ChatShared` and `KeyboardButtonRequestChat` structs [pr852]: https://github.com/teloxide/teloxide/pull/853 diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 2d0af202..3d222acf 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -84,7 +84,7 @@ pub use invoice::*; pub use keyboard_button::*; pub use keyboard_button_poll_type::*; pub use keyboard_button_request_chat::*; -pub use keyboard_button_request_user::*; +pub use keyboard_button_request_users::*; pub use label_price::*; pub use link_preview_options::*; pub use location::*; @@ -116,6 +116,7 @@ pub use reply_keyboard_markup::*; pub use reply_keyboard_remove::*; pub use reply_markup::*; pub use reply_parameters::*; +pub use request_id::*; pub use response_parameters::*; pub use sent_web_app_message::*; pub use shipping_address::*; @@ -209,7 +210,7 @@ mod invoice; mod keyboard_button; mod keyboard_button_poll_type; mod keyboard_button_request_chat; -mod keyboard_button_request_user; +mod keyboard_button_request_users; mod label_price; mod link_preview_options; mod location; @@ -238,6 +239,7 @@ mod reply_keyboard_markup; mod reply_keyboard_remove; mod reply_markup; mod reply_parameters; +mod request_id; mod response_parameters; mod sent_web_app_message; mod shipping_address; diff --git a/crates/teloxide-core/src/types/chat_shared.rs b/crates/teloxide-core/src/types/chat_shared.rs index 4c75b94e..f23c94bb 100644 --- a/crates/teloxide-core/src/types/chat_shared.rs +++ b/crates/teloxide-core/src/types/chat_shared.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::ChatId; +use crate::types::{ChatId, RequestId}; /// Information about the chat whose identifier was shared with the bot using a /// [`KeyboardButtonRequestChat`] button. @@ -9,7 +9,7 @@ use crate::types::ChatId; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct ChatShared { /// Identifier of the request. - pub request_id: i32, + pub request_id: RequestId, /// Identifier of the shared chat. pub chat_id: ChatId, } diff --git a/crates/teloxide-core/src/types/keyboard_button.rs b/crates/teloxide-core/src/types/keyboard_button.rs index 60470977..56893d12 100644 --- a/crates/teloxide-core/src/types/keyboard_button.rs +++ b/crates/teloxide-core/src/types/keyboard_button.rs @@ -198,6 +198,8 @@ impl Serialize for ButtonRequest { #[cfg(test)] mod tests { + use crate::types::RequestId; + use super::*; #[test] @@ -221,7 +223,10 @@ mod tests { fn serialize_chat_request() { let button = KeyboardButton { text: String::from(""), - request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new(0, false))), + request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new( + RequestId(0), + false, + ))), }; let expected = r#"{"text":"","request_chat":{"request_id":0,"chat_is_channel":false}}"#; let actual = serde_json::to_string(&button).unwrap(); diff --git a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs index fef4222b..2765aa69 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::ChatAdministratorRights; +use crate::types::{ChatAdministratorRights, RequestId}; /// This object defines the criteria used to request a suitable chat. The /// identifier of the selected chat will be shared with the bot when the @@ -14,7 +14,7 @@ pub struct KeyboardButtonRequestChat { /// [`ChatShared`] object. Must be unique within the message. /// /// [`ChatShared`]: crate::types::ChatShared - pub request_id: i32, + pub request_id: RequestId, /// Pass `true` to request a channel chat, pass `false` to request a group /// or a supergroup chat. @@ -57,7 +57,7 @@ pub struct KeyboardButtonRequestChat { impl KeyboardButtonRequestChat { /// Creates a new [`KeyboardButtonRequestChat`]. - pub fn new(request_id: i32, chat_is_channel: bool) -> Self { + pub fn new(request_id: RequestId, chat_is_channel: bool) -> Self { Self { request_id, chat_is_channel, diff --git a/crates/teloxide-core/src/types/keyboard_button_request_user.rs b/crates/teloxide-core/src/types/keyboard_button_request_users.rs similarity index 89% rename from crates/teloxide-core/src/types/keyboard_button_request_user.rs rename to crates/teloxide-core/src/types/keyboard_button_request_users.rs index 9636cf16..6f126622 100644 --- a/crates/teloxide-core/src/types/keyboard_button_request_user.rs +++ b/crates/teloxide-core/src/types/keyboard_button_request_users.rs @@ -1,5 +1,7 @@ use serde::{Deserialize, Serialize}; +use crate::types::RequestId; + /// This object defines the criteria used to request a suitable users. The /// identifiers of the selected users will be shared with the bot when the /// corresponding button is pressed. More about requesting users » @@ -12,7 +14,7 @@ pub struct KeyboardButtonRequestUsers { /// [`UsersShared`] object. Must be unique within the message. /// /// [`UsersShared`]: crate::types::UsersShared - pub request_id: i32, + pub request_id: RequestId, /// Pass `true` to request a bot, pass `false` to request a regular user. If /// not specified, no additional restrictions are applied. @@ -26,13 +28,13 @@ pub struct KeyboardButtonRequestUsers { pub user_is_premium: Option, /// The maximum number of users to be selected; 1-10. Defaults to 1. - #[serde(default = "de_max_quantity_default")] + #[serde(default = "one", skip_serializing_if = "is_one")] pub max_quantity: u8, } impl KeyboardButtonRequestUsers { /// Creates a new [`KeyboardButtonRequestUsers`]. - pub fn new(request_id: i32) -> Self { + pub fn new(request_id: RequestId) -> Self { Self { request_id, user_is_bot: None, user_is_premium: None, max_quantity: 1 } } @@ -57,6 +59,10 @@ impl KeyboardButtonRequestUsers { } } -fn de_max_quantity_default() -> u8 { +fn one() -> u8 { 1 } + +fn is_one(value: &u8) -> bool { + *value == 1 +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 93440682..750700e8 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -1865,7 +1865,10 @@ mod tests { chat_full_info: ChatFullInfo::default() }, kind: MessageKind::ChatShared(MessageChatShared { - chat_shared: ChatShared { request_id: 348349, chat_id: ChatId(384939) } + chat_shared: ChatShared { + request_id: RequestId(348349), + chat_id: ChatId(384939) + } }), via_bot: None } diff --git a/crates/teloxide-core/src/types/request_id.rs b/crates/teloxide-core/src/types/request_id.rs new file mode 100644 index 00000000..98cd87f3 --- /dev/null +++ b/crates/teloxide-core/src/types/request_id.rs @@ -0,0 +1,24 @@ +use derive_more::Display; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[serde(transparent)] +pub struct RequestId(pub i32); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_request_id_de() { + #[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)] + struct Dummy { + request_id: RequestId, + } + let json = r#"{"request_id":42}"#; + let dummy = Dummy { request_id: RequestId(42) }; + + assert_eq!(serde_json::to_string(&dummy).unwrap(), json); + assert_eq!(dummy, serde_json::from_str(json).unwrap()); + } +} diff --git a/crates/teloxide-core/src/types/users_shared.rs b/crates/teloxide-core/src/types/users_shared.rs index 46b20679..d522fa54 100644 --- a/crates/teloxide-core/src/types/users_shared.rs +++ b/crates/teloxide-core/src/types/users_shared.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::UserId; +use crate::types::{RequestId, UserId}; /// This object contains information about the users whose identifiers were /// shared with the bot using a [KeyboardButtonRequestUsers] button. @@ -9,7 +9,7 @@ use crate::types::UserId; #[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] pub struct UsersShared { /// Identifier of the request - pub request_id: i32, + pub request_id: RequestId, /// Identifiers of the shared users pub user_ids: Vec, } From 565316aa7d4fcd199543a5af05242a16645944f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:06:37 +0300 Subject: [PATCH 44/51] Add docs for `ExternalReplyInfo` --- crates/teloxide-core/src/types/external_reply_info.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/external_reply_info.rs b/crates/teloxide-core/src/types/external_reply_info.rs index 866705e8..0dcd9e09 100644 --- a/crates/teloxide-core/src/types/external_reply_info.rs +++ b/crates/teloxide-core/src/types/external_reply_info.rs @@ -6,9 +6,11 @@ use crate::types::{ Video, VideoNote, Voice, }; +/// This object contains information about a message that is being replied to, +/// which may come from another chat or forum topic. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ExternalReplyInfo { - /// Origin of the message replied to by the given message + /// Origin of the message replied to by the given message. pub origin: MessageOrigin, /// Chat the original message belongs to. Available only if the chat is a /// supergroup or a channel. From fc29530fe7fc64dffd8708fc18b3927079abbb76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:10:02 +0300 Subject: [PATCH 45/51] Fix docs for `CallbackQuery::message` --- crates/teloxide-core/src/types/callback_query.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index 72fbe80f..511ac32e 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -25,7 +25,10 @@ pub struct CallbackQuery { pub from: User, /// Message sent by the bot with the callback button that originated the - /// query + /// query. + /// + /// Note: if the message is too old, it will be + /// [`MaybeInaccessibleMessage::Inaccessible`]. pub message: Option, /// An identifier of the message sent via the bot in inline mode, that From 96d7c8e02286bf4e80ee22571880c964a405a624 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:32:35 +0300 Subject: [PATCH 46/51] Add missing parameters to `ReplyParameters` --- crates/teloxide-core/src/types/reply_parameters.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/teloxide-core/src/types/reply_parameters.rs b/crates/teloxide-core/src/types/reply_parameters.rs index 57c02c47..09c1ede8 100644 --- a/crates/teloxide-core/src/types/reply_parameters.rs +++ b/crates/teloxide-core/src/types/reply_parameters.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::types::{MessageId, Recipient}; +use crate::types::{MessageEntity, MessageId, Recipient}; /// Describes reply parameters for the message that is being sent. #[serde_with::skip_serializing_none] @@ -24,6 +24,16 @@ pub struct ReplyParameters { /// _spoiler_, and _custom_emoji_ entities. The message will fail to send if /// the quote isn't found in the original message. pub quote: Option, + /// Mode for parsing entities in the quote. See [formatting options] for + /// more details. + /// + /// [formatting options]: https://core.telegram.org/bots/api#formatting-options + pub quote_parse_mode: Option, + /// A JSON-serialized list of special entities that appear in the quote. It + /// can be specified instead of quote_parse_mode. + pub quote_entities: Option>, + /// Position of the quote in the original message in UTF-16 code units + pub quote_position: Option, } impl ReplyParameters { From a353439e3b315db27d02b8cbfe28ca566014b739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:36:00 +0300 Subject: [PATCH 47/51] Add docs for `MaybeInaccessibleMessage` --- crates/teloxide-core/src/types/maybe_inaccessible_message.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs index ff0d09ed..81e583aa 100644 --- a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs +++ b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs @@ -2,6 +2,10 @@ use serde::{Deserialize, Serialize}; use crate::types::{Chat, InaccessibleMessage, Message, MessageId}; +/// This object describes a message that can be inaccessible to the bot. It can +/// be one of: +/// - [Message] +/// - [InaccessibleMessage] #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(untagged)] pub enum MaybeInaccessibleMessage { From 0cb716a9acb90d580281f1f3b7c107b7bace7ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 15:50:02 +0300 Subject: [PATCH 48/51] Add `CallbackQuery::regular_message` getter --- crates/teloxide-core/CHANGELOG.md | 1 + crates/teloxide-core/src/types/callback_query.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/teloxide-core/CHANGELOG.md b/crates/teloxide-core/CHANGELOG.md index 4ee5206a..34b7ec97 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -119,6 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Other Changes - Add fields `ChafFullInfo::{has_visible_history, accent_color_id, background_custom_emoji_id, profile_accent_color_id, profile_background_custom_emoji_id}` - Add `RequestId` type + - Add `CallbackQuery::regular_message` getter [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index 511ac32e..a31d61d7 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -60,13 +60,15 @@ impl CallbackQuery { use crate::util::flatten; use std::iter::once; - once(&self.from).chain(flatten( - self.message - .as_ref() - // If we can access the message - .and_then(|maybe| maybe.message()) - .map(Message::mentioned_users), - )) + once(&self.from).chain(flatten(self.regular_message().map(Message::mentioned_users))) + } + + #[must_use] + pub fn regular_message(&self) -> Option<&Message> { + self.message + .as_ref() + // If we can access the message + .and_then(|maybe| maybe.message()) } } From 3029dcdee655fe06bb29293e0d5a0d22d39d092b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D1=8B=D1=80=D1=86=D0=B5=D0=B2=20=D0=92=D0=B0=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=98=D0=B3=D0=BE=D1=80=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 31 Jul 2024 16:00:25 +0300 Subject: [PATCH 49/51] Tweak `MaybeInaccessibleMessage` --- crates/teloxide-core/src/types/callback_query.rs | 2 +- .../teloxide-core/src/types/maybe_inaccessible_message.rs | 8 +++----- crates/teloxide/examples/buttons.rs | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/crates/teloxide-core/src/types/callback_query.rs b/crates/teloxide-core/src/types/callback_query.rs index a31d61d7..4ab58252 100644 --- a/crates/teloxide-core/src/types/callback_query.rs +++ b/crates/teloxide-core/src/types/callback_query.rs @@ -68,7 +68,7 @@ impl CallbackQuery { self.message .as_ref() // If we can access the message - .and_then(|maybe| maybe.message()) + .and_then(|maybe| maybe.regular_message()) } } diff --git a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs index 81e583aa..985ea1cb 100644 --- a/crates/teloxide-core/src/types/maybe_inaccessible_message.rs +++ b/crates/teloxide-core/src/types/maybe_inaccessible_message.rs @@ -21,17 +21,15 @@ impl MaybeInaccessibleMessage { } } - pub fn message(&self) -> Option<&Message> { + #[must_use] + pub fn regular_message(&self) -> Option<&Message> { match self { Self::Regular(message) => Some(message), Self::Inaccessible(_) => None, } } - pub fn chat_and_id(&self) -> (&Chat, MessageId) { - (self.chat(), self.id()) - } - + #[must_use] pub fn chat(&self) -> &Chat { match self { Self::Regular(message) => &message.chat, diff --git a/crates/teloxide/examples/buttons.rs b/crates/teloxide/examples/buttons.rs index 8a2269f7..daa538c2 100644 --- a/crates/teloxide/examples/buttons.rs +++ b/crates/teloxide/examples/buttons.rs @@ -116,9 +116,8 @@ async fn callback_handler(bot: Bot, q: CallbackQuery) -> Result<(), Box Date: Wed, 31 Jul 2024 16:19:29 +0300 Subject: [PATCH 50/51] Add `filter_forward_origin` --- CHANGELOG.md | 2 +- crates/teloxide-core/src/types/external_reply_info.rs | 6 +++--- crates/teloxide/src/dispatching/filter_ext.rs | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7d77f12..0bb6b0bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,7 +70,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - MSRV (Minimal Supported Rust Version) was bumped from `1.68.0` to `1.70.0` ([PR 996][https://github.com/teloxide/teloxide/pull/996]) - `axum` was bumped to `0.7`, along with related libraries used for webhooks ([PR 1093][https://github.com/teloxide/teloxide/pull/1093]) - `Polling`'s exponential backoff now results in 64 seconds maximum delay instead of 17 minutes ([PR 1113][https://github.com/teloxide/teloxide/pull/1113]) -- `filter_forward_from` was removed ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) +- `filter_forward_from` was renamed to `filter_forward_origin` and now returns `MessageOrigin` instead of `ForwardFrom` ([PR 1101](https://github.com/teloxide/teloxide/pull/1101)) ### Removed diff --git a/crates/teloxide-core/src/types/external_reply_info.rs b/crates/teloxide-core/src/types/external_reply_info.rs index 0dcd9e09..14c89ae3 100644 --- a/crates/teloxide-core/src/types/external_reply_info.rs +++ b/crates/teloxide-core/src/types/external_reply_info.rs @@ -20,9 +20,9 @@ pub struct ExternalReplyInfo { #[serde(with = "crate::types::option_msg_id_as_int")] pub message_id: Option, /// Options used for link preview generation for the original message, if it - /// is a text message + /// is a text message. pub link_preview_options: Option, - /// _true_, if the message media is covered by a spoiler animation + /// _true_, if the message media is covered by a spoiler animation. #[serde(default)] pub has_media_spoiler: bool, @@ -38,7 +38,7 @@ pub enum ExternalReplyInfoKind { // - `Animation` must be in front of `Document` // // This is needed so serde doesn't parse `Venue` as `Location` or `Animation` as `Document` - // (for backward compatability telegram duplicates some fields) + // (for backward compatability telegram duplicates some fields). // // See Animation(Animation), diff --git a/crates/teloxide/src/dispatching/filter_ext.rs b/crates/teloxide/src/dispatching/filter_ext.rs index 445acc52..1f7394e3 100644 --- a/crates/teloxide/src/dispatching/filter_ext.rs +++ b/crates/teloxide/src/dispatching/filter_ext.rs @@ -95,6 +95,7 @@ define_message_ext! { (filter_migration_from, Message::migrate_from_chat_id), (filter_migration_to, Message::migrate_to_chat_id), (filter_reply_to_message, Message::reply_to_message), + (filter_forward_origin, Message::forward_origin), // Rest variants of a MessageKind (filter_new_chat_members, Message::new_chat_members), (filter_left_chat_member, Message::left_chat_member), From ccdeb3f2b03644ed0b9de860f02bd4bc799b54bd Mon Sep 17 00:00:00 2001 From: Andrey Brusnik Date: Thu, 15 Aug 2024 17:04:19 +0400 Subject: [PATCH 51/51] Test that all possible updates are specified in `Kind::full_set()` --- .../src/dispatching/handler_description.rs | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/crates/teloxide/src/dispatching/handler_description.rs b/crates/teloxide/src/dispatching/handler_description.rs index b18ebee8..0398f2df 100644 --- a/crates/teloxide/src/dispatching/handler_description.rs +++ b/crates/teloxide/src/dispatching/handler_description.rs @@ -95,10 +95,12 @@ mod tests { #[cfg(feature = "macros")] use crate::{ self as teloxide, // fixup for the `BotCommands` macro - dispatching::{HandlerExt, UpdateFilterExt}, + dispatching::{handler_description::Kind, HandlerExt, UpdateFilterExt}, types::{AllowedUpdate::*, Update}, utils::command::BotCommands, }; + #[cfg(feature = "macros")] + use dptree::description::EventKind; #[cfg(feature = "macros")] #[derive(BotCommands, Clone)] @@ -132,4 +134,43 @@ mod tests { fn discussion_648() { panic!("this test requires `macros` feature") } + + // Test that all possible updates are specified in `Kind::full_set()` + #[test] + #[cfg(feature = "macros")] + fn allowed_updates_full_set() { + let full_set = Kind::full_set(); + let allowed_updates_reference = vec![ + Message, + EditedMessage, + ChannelPost, + EditedChannelPost, + MessageReaction, + MessageReactionCount, + InlineQuery, + ChosenInlineResult, + CallbackQuery, + ShippingQuery, + PreCheckoutQuery, + Poll, + PollAnswer, + MyChatMember, + ChatMember, + ChatJoinRequest, + ChatBoost, + RemovedChatBoost, + ]; + + for update in allowed_updates_reference { + match update { + // CAUTION: Don't forget to add new `UpdateKind` to `allowed_updates_reference`! + Message | EditedMessage | ChannelPost | EditedChannelPost | MessageReaction + | MessageReactionCount | InlineQuery | ChosenInlineResult | CallbackQuery + | ShippingQuery | PreCheckoutQuery | Poll | PollAnswer | MyChatMember + | ChatMember | ChatJoinRequest | ChatBoost | RemovedChatBoost => { + assert!(full_set.contains(&Kind(update))) + } + } + } + } }