diff --git a/CHANGELOG.md b/CHANGELOG.md index 755a9793..8a2ed06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ApiError::TooMuchInlineQueryResults` ([#135][pr135]) - `ApiError::NotEnoughRightsToChangeChatPermissions` ([#155][pr155]) - Support for 5.4 telegram bot API ([#133][pr133]) -- Support for 5.5 telegram bot API ([#143][pr143]) +- Support for 5.5 telegram bot API ([#143][pr143], [#164][pr164]) - `EditedMessageIsTooLong` error ([#109][pr109]) - `UntilDate` enum and use it for `{Restricted, Banned}::until_date` ([#116][pr116]) - `Limits::messages_per_min_channel` ([#121][pr121]) @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr151]: https://github.com/teloxide/teloxide-core/pull/151 [pr155]: https://github.com/teloxide/teloxide-core/pull/155 [pr156]: https://github.com/teloxide/teloxide-core/pull/156 +[pr164]: https://github.com/teloxide/teloxide-core/pull/164 ### Changed @@ -45,11 +46,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `InputFile` and related structures now do **not** implement `PartialEq`, `Eq` and `Hash` ([#133][pr133]) - How forwarded messages are represented ([#151][pr151]) - `RequestError::InvalidJson` now has a `raw` field with raw json for easier debugability ([#150][pr150]) +- `ChatPermissions` is now bitflags ([#157][pr157]) [pr115]: https://github.com/teloxide/teloxide-core/pull/115 [pr125]: https://github.com/teloxide/teloxide-core/pull/125 [pr134]: https://github.com/teloxide/teloxide-core/pull/134 [pr150]: https://github.com/teloxide/teloxide-core/pull/150 +[pr157]: https://github.com/teloxide/teloxide-core/pull/157 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index ad4bb0b8..baa83bfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,9 +42,9 @@ once_cell = "1.5.0" never = "0.1.0" chrono = { version = "0.4.19", default-features = false } either = "1.6.1" +bitflags = { version = "1.2" } vecrem = { version = "0.1", optional = true } -bitflags = { version = "1.2", optional = true } [dev-dependencies] pretty_env_logger = "0.4" @@ -66,7 +66,7 @@ nightly = [] throttle = ["vecrem"] # Trace bot adaptor -trace_adaptor = ["bitflags"] +trace_adaptor = [] # Erased bot adaptor erased = [] diff --git a/LICENSE b/LICENSE index 84a1b0b0..3bc0c2bb 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 teloxide +Copyright (c) 2020-2022 teloxide Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 186621c4..02d3faef 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ <img src="https://img.shields.io/badge/license-MIT-blue.svg"> </a> <a href="https://core.telegram.org/bots/api"> - <img src="https://img.shields.io/badge/API%20coverage-Up%20to%205.3%20(inclusively)-green.svg"> + <img src="https://img.shields.io/badge/API%20coverage-Up%20to%205.6%20(inclusively)-green.svg"> </a> <a href="https://crates.io/crates/teloxide_core"> <img src="https://img.shields.io/crates/v/teloxide_core.svg"> diff --git a/src/lib.rs b/src/lib.rs index 13d8a213..dd7b59a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ // (see: `teloxide`). We can't use `docsrs` as it breaks tokio compilation in this case. #![cfg_attr( all(any(docsrs, dep_docsrs), feature = "nightly"), - feature(doc_cfg, doc_notable_trait) + feature(doc_cfg, doc_auto_cfg, doc_notable_trait) )] #![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))] #![cfg_attr(all(feature = "full", docsrs), deny(rustdoc::broken_intra_doc_links))] diff --git a/src/payloads/copy_message.rs b/src/payloads/copy_message.rs index b003fcf8..6eec57d5 100644 --- a/src/payloads/copy_message.rs +++ b/src/payloads/copy_message.rs @@ -37,6 +37,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/forward_message.rs b/src/payloads/forward_message.rs index 2ccad51e..9c34f0b7 100644 --- a/src/payloads/forward_message.rs +++ b/src/payloads/forward_message.rs @@ -29,6 +29,8 @@ impl_payload! { /// /// [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/src/payloads/send_animation.rs b/src/payloads/send_animation.rs index 6e05aa5b..e5c69e98 100644 --- a/src/payloads/send_animation.rs +++ b/src/payloads/send_animation.rs @@ -48,6 +48,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_audio.rs b/src/payloads/send_audio.rs index 2097ccd3..61dd6c01 100644 --- a/src/payloads/send_audio.rs +++ b/src/payloads/send_audio.rs @@ -51,6 +51,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_contact.rs b/src/payloads/send_contact.rs index 01852e8f..ab530447 100644 --- a/src/payloads/send_contact.rs +++ b/src/payloads/send_contact.rs @@ -35,6 +35,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_dice.rs b/src/payloads/send_dice.rs index fc19d585..426154ac 100644 --- a/src/payloads/send_dice.rs +++ b/src/payloads/send_dice.rs @@ -27,6 +27,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_document.rs b/src/payloads/send_document.rs index 3ca72ba4..71f5103a 100644 --- a/src/payloads/send_document.rs +++ b/src/payloads/send_document.rs @@ -44,6 +44,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_game.rs b/src/payloads/send_game.rs index eaa8e9e9..8d30f29c 100644 --- a/src/payloads/send_game.rs +++ b/src/payloads/send_game.rs @@ -27,6 +27,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_invoice.rs b/src/payloads/send_invoice.rs index 3ffe3b93..503ddfb6 100644 --- a/src/payloads/send_invoice.rs +++ b/src/payloads/send_invoice.rs @@ -72,6 +72,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_location.rs b/src/payloads/send_location.rs index 96c7e4c4..ba482dc2 100644 --- a/src/payloads/send_location.rs +++ b/src/payloads/send_location.rs @@ -39,6 +39,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_media_group.rs b/src/payloads/send_media_group.rs index 66d9f619..95fd7886 100644 --- a/src/payloads/send_media_group.rs +++ b/src/payloads/send_media_group.rs @@ -27,6 +27,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_message.rs b/src/payloads/send_message.rs index e3ff3dab..e27ddfc1 100644 --- a/src/payloads/send_message.rs +++ b/src/payloads/send_message.rs @@ -35,6 +35,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_photo.rs b/src/payloads/send_photo.rs index a5dffae2..d54e912d 100644 --- a/src/payloads/send_photo.rs +++ b/src/payloads/send_photo.rs @@ -38,6 +38,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_poll.rs b/src/payloads/send_poll.rs index 88ecf6a8..52d19938 100644 --- a/src/payloads/send_poll.rs +++ b/src/payloads/send_poll.rs @@ -53,6 +53,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_sticker.rs b/src/payloads/send_sticker.rs index 357e10c3..a66f4ba2 100644 --- a/src/payloads/send_sticker.rs +++ b/src/payloads/send_sticker.rs @@ -30,6 +30,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_venue.rs b/src/payloads/send_venue.rs index b3181b78..78a901a2 100644 --- a/src/payloads/send_venue.rs +++ b/src/payloads/send_venue.rs @@ -43,6 +43,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_video.rs b/src/payloads/send_video.rs index c815e255..0dae2154 100644 --- a/src/payloads/send_video.rs +++ b/src/payloads/send_video.rs @@ -51,6 +51,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/payloads/send_video_note.rs b/src/payloads/send_video_note.rs index 417bc1ac..a8c69ce7 100644 --- a/src/payloads/send_video_note.rs +++ b/src/payloads/send_video_note.rs @@ -39,6 +39,8 @@ impl_payload! { /// /// [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, /// If the message is a reply, ID of the original message pub reply_to_message_id: i32, /// Pass _True_, if the message should be sent even if the specified replied-to message is not found diff --git a/src/serde_multipart/mod.rs b/src/serde_multipart/mod.rs index 3eae3aa9..42ffa7f7 100644 --- a/src/serde_multipart/mod.rs +++ b/src/serde_multipart/mod.rs @@ -31,6 +31,7 @@ pub(crate) fn to_form<T: ?Sized + Serialize>(val: &T) -> impl Future<Output = Re } // https://github.com/teloxide/teloxide/issues/473 +#[cfg(test)] #[tokio::test] async fn issue_473() { use crate::{ diff --git a/src/types.rs b/src/types.rs index d4ba801e..7c8f5a46 100644 --- a/src/types.rs +++ b/src/types.rs @@ -244,16 +244,11 @@ pub(crate) mod serde_opt_date_from_unix_timestamp { .map(|timestamp| DateTime::from_utc(NaiveDateTime::from_timestamp(timestamp, 0), Utc))) } - pub(crate) fn none<T>() -> Option<T> { - None - } - #[test] fn test() { #[derive(Serialize, Deserialize)] struct Struct { - #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] - #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] date: Option<DateTime<Utc>>, } diff --git a/src/types/chat.rs b/src/types/chat.rs index e46f06ae..a3e6f3fa 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -70,6 +70,12 @@ pub struct ChatPublic { /// /// [`GetChat`]: crate::payloads::GetChat pub invite_link: Option<String>, + + /// `True`, if messages from the chat can't be forwarded to other chats. + /// Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub has_protected_content: Option<True>, } #[serde_with_macros::skip_serializing_none] @@ -97,7 +103,7 @@ pub struct ChatPrivate { pub bio: Option<String>, /// `True`, if privacy settings of the other party in the private chat - /// allows to use tg://user?id=<user_id> links only in chats with the + /// allows to use `tg://user?id=<user_id>` links only in chats with the /// user. Returned only in [`GetChat`]. /// /// [`GetChat`]: crate::payloads::GetChat @@ -389,6 +395,17 @@ impl Chat { } } + /// `True`, if messages from the chat can't be forwarded to other chats. + /// Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn has_protected_content(&self) -> Option<True> { + match &self.kind { + ChatKind::Public(this) => this.has_protected_content, + _ => None, + } + } + /// A first name of the other party in a private chat. pub fn first_name(&self) -> Option<&str> { match &self.kind { @@ -446,6 +463,7 @@ mod tests { }), description: None, invite_link: None, + has_protected_content: None, }), photo: None, pinned_message: None, @@ -466,7 +484,7 @@ mod tests { first_name: Some("Anon".into()), last_name: None, bio: None, - has_private_forwards: None + has_private_forwards: None, }), photo: None, pinned_message: None, diff --git a/src/types/chat_invite_link.rs b/src/types/chat_invite_link.rs index 10ed8ac0..077d5d76 100644 --- a/src/types/chat_invite_link.rs +++ b/src/types/chat_invite_link.rs @@ -21,8 +21,7 @@ pub struct ChatInviteLink { pub name: Option<String>, /// Point in time when the link will expire or has been /// expired - #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] - #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub expire_date: Option<DateTime<Utc>>, /// Maximum number of users that can be members of the chat simultaneously /// after joining the chat via this invite link; 1-99999 diff --git a/src/types/chat_permissions.rs b/src/types/chat_permissions.rs index d95b6643..77585d70 100644 --- a/src/types/chat_permissions.rs +++ b/src/types/chat_permissions.rs @@ -1,96 +1,192 @@ use serde::{Deserialize, Serialize}; +use std::ops::Not; -/// Describes actions that a non-administrator user is allowed to take in a -/// chat. -/// -/// [The official docs](https://core.telegram.org/bots/api#chatpermissions). -#[serde_with_macros::skip_serializing_none] -#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -pub struct ChatPermissions { - /// `true`, if the user is allowed to send text messages, contacts, - /// locations and venues. - pub can_send_messages: Option<bool>, +bitflags::bitflags! { + /// Describes actions that a non-administrator user is allowed to take in a + /// chat. + /// + /// [The official docs](https://core.telegram.org/bots/api#chatpermissions). + /// + /// ## Examples + /// + /// ``` + /// use teloxide_core::types::ChatPermissions; + /// + /// // No permissions, nothing is allowed + /// let _ = ChatPermissions::empty(); + /// + /// // All permissions, everything is allowed + /// let _ = ChatPermissions::all(); + /// + /// // One particular permission + /// let permissions_v0 = ChatPermissions::INVITE_USERS; + /// + /// // Check what is permitted + /// assert!(permissions_v0.contains(ChatPermissions::INVITE_USERS)); + /// assert!(!permissions_v0.contains(ChatPermissions::SEND_MESSAGES)); + /// + /// // Union, add permissions + /// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_MEDIA_MESSAGES; + /// assert!(permissions_v1.contains(ChatPermissions::INVITE_USERS)); + /// assert!(permissions_v1.contains(ChatPermissions::SEND_MEDIA_MESSAGES)); + /// + /// // Implied by `SEND_MEDIA_MESSAGES` + /// assert!(permissions_v1.contains(ChatPermissions::SEND_MESSAGES)); + /// + /// // Difference, remove permissions + /// let permissions_v2 = permissions_v1 - ChatPermissions::SEND_MEDIA_MESSAGES; + /// assert!(!permissions_v2.contains(ChatPermissions::SEND_MEDIA_MESSAGES)); + /// + /// // Removing `SEND_MEDIA_MESSAGES` also removes `SEND_MESSAGES` and vice versa + /// // because `SEND_MESSAGES` is implied by `SEND_MEDIA_MESSAGES` + /// assert!(!permissions_v2.contains(ChatPermissions::SEND_MESSAGES)); + /// + /// let permissions_v3 = permissions_v1 - ChatPermissions::SEND_MESSAGES; + /// assert!(!permissions_v3.contains(ChatPermissions::SEND_MEDIA_MESSAGES)); + /// ``` + #[derive(Serialize, Deserialize)] + #[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")] + pub struct ChatPermissions: u8 { + /// Set if the user is allowed to send text messages, contacts, + /// locations and venues. + const SEND_MESSAGES = 1; - /// `true`, if the user is allowed to send audios, documents, - /// photos, videos, video notes and voice notes, implies - /// `can_send_messages`. - pub can_send_media_messages: Option<bool>, + /// Set if the user is allowed to send audios, documents, + /// photos, videos, video notes and voice notes, implies + /// `SEND_MESSAGES`. + const SEND_MEDIA_MESSAGES = (1 << 1) | Self::SEND_MESSAGES.bits; - /// `true`, if the user is allowed to send polls, implies - /// `can_send_messages`. - pub can_send_polls: Option<bool>, + /// Set if the user is allowed to send polls, implies + /// `SEND_MESSAGES`. + const SEND_POLLS = (1 << 2) | Self::SEND_MESSAGES.bits; - /// `true`, if the user is allowed to send animations, games, stickers and - /// use inline bots, implies `can_send_media_messages`. - pub can_send_other_messages: Option<bool>, + /// Set if the user is allowed to send animations, games, stickers and + /// use inline bots, implies `SEND_MEDIA_MESSAGES`. + const SEND_OTHER_MESSAGES = (1 << 3) | Self::SEND_MEDIA_MESSAGES.bits; - /// `true`, if the user is allowed to add web page previews to - /// their messages, implies `can_send_media_messages`. - pub can_add_web_page_previews: Option<bool>, + /// Set if the user is allowed to add web page previews to + /// their messages, implies `SEND_MEDIA_MESSAGES`. + const ADD_WEB_PAGE_PREVIEWS = (1 << 4) | Self::SEND_MEDIA_MESSAGES.bits; - /// `true`, if the user is allowed to change the chat title, photo and - /// other settings. Ignored in public supergroups. - pub can_change_info: Option<bool>, + /// Set if the user is allowed to change the chat title, photo and + /// other settings. Ignored in public supergroups. + const CHANGE_INFO = (1 << 5); - /// `true`, if the user is allowed to invite new users to the chat. - pub can_invite_users: Option<bool>, + /// Set if the user is allowed to invite new users to the chat. + const INVITE_USERS = (1 << 6); - /// `true`, if the user is allowed to pin messages. Ignored in public - /// supergroups. - pub can_pin_messages: Option<bool>, + /// Set if the user is allowed to pin messages. Ignored in public + /// supergroups. + const PIN_MESSAGES = (1 << 7); + } } -impl ChatPermissions { - pub const fn new() -> Self { +/// Helper for (de)serialization +#[derive(Serialize, Deserialize)] +struct ChatPermissionsRaw { + #[serde(default, skip_serializing_if = "Not::not")] + can_send_messages: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_media_messages: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_polls: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_other_messages: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_add_web_page_previews: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_change_info: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_invite_users: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_pin_messages: bool, +} + +impl From<ChatPermissions> for ChatPermissionsRaw { + fn from(this: ChatPermissions) -> Self { Self { - can_send_messages: None, - can_send_media_messages: None, - can_send_polls: None, - can_send_other_messages: None, - can_add_web_page_previews: None, - can_change_info: None, - can_invite_users: None, - can_pin_messages: None, + can_send_messages: this.contains(ChatPermissions::SEND_MESSAGES), + can_send_media_messages: this.contains(ChatPermissions::SEND_MEDIA_MESSAGES), + can_send_polls: this.contains(ChatPermissions::SEND_POLLS), + can_send_other_messages: this.contains(ChatPermissions::SEND_OTHER_MESSAGES), + can_add_web_page_previews: this.contains(ChatPermissions::ADD_WEB_PAGE_PREVIEWS), + can_change_info: this.contains(ChatPermissions::CHANGE_INFO), + can_invite_users: this.contains(ChatPermissions::INVITE_USERS), + can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES), } } +} - pub const fn can_send_messages(mut self, val: bool) -> Self { - self.can_send_messages = Some(val); - self - } +impl From<ChatPermissionsRaw> for ChatPermissions { + fn from( + ChatPermissionsRaw { + can_send_messages, + can_send_media_messages, + can_send_polls, + can_send_other_messages, + can_add_web_page_previews, + can_change_info, + can_invite_users, + can_pin_messages, + }: ChatPermissionsRaw, + ) -> Self { + let mut this = Self::empty(); - pub const fn can_send_media_messages(mut self, val: bool) -> Self { - self.can_send_media_messages = Some(val); - self - } + if can_send_messages { + this |= Self::SEND_MESSAGES; + } + if can_send_media_messages { + this |= Self::SEND_MEDIA_MESSAGES + } + if can_send_polls { + this |= Self::SEND_POLLS; + } + if can_send_other_messages { + this |= Self::SEND_OTHER_MESSAGES; + } + if can_add_web_page_previews { + this |= Self::ADD_WEB_PAGE_PREVIEWS; + } + if can_change_info { + this |= Self::CHANGE_INFO; + } + if can_invite_users { + this |= Self::INVITE_USERS; + } + if can_pin_messages { + this |= Self::PIN_MESSAGES; + } - pub const fn can_send_polls(mut self, val: bool) -> Self { - self.can_send_polls = Some(val); - self - } - - pub const fn can_send_other_messages(mut self, val: bool) -> Self { - self.can_send_other_messages = Some(val); - self - } - - pub const fn can_add_web_page_previews(mut self, val: bool) -> Self { - self.can_add_web_page_previews = Some(val); - self - } - - pub const fn can_change_info(mut self, val: bool) -> Self { - self.can_change_info = Some(val); - self - } - - pub const fn can_invite_users(mut self, val: bool) -> Self { - self.can_invite_users = Some(val); - self - } - - pub const fn can_pin_messages(mut self, val: bool) -> Self { - self.can_pin_messages = Some(val); - self + this + } +} + +#[cfg(test)] +mod tests { + use super::ChatPermissions; + + #[test] + fn serialization() { + let permissions = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES; + let expected = + r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#; + let actual = serde_json::to_string(&permissions).unwrap(); + assert_eq!(expected, actual); + } + + #[test] + fn deserialization() { + let json = + r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#; + let expected = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES; + let actual = serde_json::from_str(json).unwrap(); + assert_eq!(expected, actual); } } diff --git a/src/types/document.rs b/src/types/document.rs index 3ea3c861..c3b51792 100644 --- a/src/types/document.rs +++ b/src/types/document.rs @@ -29,7 +29,7 @@ pub struct Document { pub file_name: Option<String>, /// A MIME type of the file as defined by a sender. - #[serde(with = "crate::types::non_telegram_types::mime::opt_deser")] + #[serde(default, with = "crate::types::non_telegram_types::mime::opt_deser")] pub mime_type: Option<Mime>, /// A size of a file. diff --git a/src/types/message.rs b/src/types/message.rs index 95a58ea4..7405c7ad 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -85,8 +85,7 @@ pub struct MessageCommon { pub reply_to_message: Option<Box<Message>>, /// Date the message was last edited in Unix time. - #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] - #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub edit_date: Option<DateTime<Utc>>, #[serde(flatten)] @@ -363,7 +362,7 @@ pub struct MediaDocument { /// For messages with a caption, special entities like usernames, URLs, /// bot commands, etc. that appear in the caption. - #[serde(default = "Vec::new")] + #[serde(default)] pub caption_entities: Vec<MessageEntity>, /// The unique identifier of a media message group this message belongs @@ -1340,10 +1339,11 @@ mod tests { }), description: None, invite_link: None, + has_protected_content: None, }), + message_auto_delete_time: None, photo: None, pinned_message: None, - message_auto_delete_time: None, }; assert!(message.from().unwrap().is_anonymous()); diff --git a/src/types/message_entity.rs b/src/types/message_entity.rs index cd8b6278..7fe19381 100644 --- a/src/types/message_entity.rs +++ b/src/types/message_entity.rs @@ -64,6 +64,7 @@ pub enum MessageEntityKind { TextMention { user: User }, Underline, Strikethrough, + Spoiler, } #[cfg(test)] diff --git a/src/types/parse_mode.rs b/src/types/parse_mode.rs index 88a42c48..edd652fe 100644 --- a/src/types/parse_mode.rs +++ b/src/types/parse_mode.rs @@ -12,13 +12,20 @@ use serde::{Deserialize, Serialize}; /// Formatting options. /// /// The Bot API supports basic formatting for messages. You can use bold, -/// italic, underlined and strikethrough text, as well as inline links and -/// pre-formatted code in your bots' messages. Telegram clients will render +/// italic, underlined, strikethrough, and spoiler text, as well as inline links +/// and pre-formatted code in your bots' messages. Telegram clients will render /// them accordingly. You can use either markdown-style or HTML-style /// formatting. /// /// Note that Telegram clients will display an **alert** to the user before -/// opening an inline link (‘Open this link?’ together with the full URL). +/// opening an inline link ('Open this link?' together with the full URL). +/// +/// Message entities can be nested, providing following restrictions are met: +/// - If two entities have common characters then one of them is fully contained +/// inside another. +/// - bold, italic, underline, strikethrough, and spoiler entities can contain +/// and can be part of any other entities, except pre and code. +/// - All other entities can't contain each other. /// /// Links `tg://user?id=<user_id>` can be used to mention a user by their ID /// without using a username. Please note: @@ -39,7 +46,8 @@ use serde::{Deserialize, Serialize}; /// _italic \*text_ /// __underline__ /// ~strikethrough~ -/// *bold _italic bold ~italic bold strikethrough~ __underline italic bold___ bold* +/// ||spoiler|| +/// *bold _italic bold ~italic bold strikethrough ||italic bold strikethrough spoiler||~ __underline italic bold___ bold* /// [inline URL](http://www.example.com/) /// [inline mention of a user](tg://user?id=123456789) /// `inline fixed-width code` @@ -77,9 +85,10 @@ use serde::{Deserialize, Serialize}; /// <i>italic</i>, <em>italic</em> /// <u>underline</u>, <ins>underline</ins> /// <s>strikethrough</s>, <strike>strikethrough</strike>, <del>strikethrough</del> -/// <b>bold <i>italic bold <s>italic bold strikethrough</s> <u>underline italic bold</u></i> bold</b> -/// <a href="http:// www.example.com/">inline URL</a> -/// <a href="tg:// user?id=123456789">inline mention of a user</a> +/// <span class="tg-spoiler">spoiler</span>, <tg-spoiler>spoiler</tg-spoiler> +/// <b>bold <i>italic bold <s>italic bold strikethrough <span class="tg-spoiler">italic bold strikethrough spoiler</span></s> <u>underline italic bold</u></i> bold</b> +/// <a href="http://www.example.com/">inline URL</a> +/// <a href="tg://user?id=123456789">inline mention of a user</a> /// <code>inline fixed-width code</code> /// <pre>pre-formatted fixed-width code block</pre> #[doc = "<pre><code class=\"language-rust\">pre-formatted fixed-width code block written in the \ diff --git a/src/types/poll.rs b/src/types/poll.rs index a0946ca5..4a35a7d5 100644 --- a/src/types/poll.rs +++ b/src/types/poll.rs @@ -51,8 +51,7 @@ pub struct Poll { pub open_period: Option<u16>, /// Point in time when the poll will be automatically closed. - #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] - #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub close_date: Option<DateTime<Utc>>, } diff --git a/src/types/webhook_info.rs b/src/types/webhook_info.rs index 6a96beb1..f90e2a53 100644 --- a/src/types/webhook_info.rs +++ b/src/types/webhook_info.rs @@ -22,8 +22,7 @@ pub struct WebhookInfo { /// Time of the most recent error that happened when trying to /// deliver an update via webhook. - #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] - #[serde(default = "crate::types::serde_opt_date_from_unix_timestamp::none")] + #[serde(default, with = "crate::types::serde_opt_date_from_unix_timestamp")] pub last_error_date: Option<DateTime<Utc>>, /// Error message in human-readable format for the most recent error that