diff --git a/README.md b/README.md index 45153b31..c0029f0f 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 100ef1f0..258f0d0e 100644 --- a/crates/teloxide-core/CHANGELOG.md +++ b/crates/teloxide-core/CHANGELOG.md @@ -33,6 +33,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `video_chat_ended` - `web_app_data` - `is_delete_chat_photo`, `is_group_chat_created`, `is_super_group_chat_created`, `is_channel_chat_created` functions to `Message` ([#982][pr982]) +- Support for TBA 6.5 ([#954][pr954]) + - Add `can_send_audios`, `can_send_documents`, `can_send_photos`, `can_send_videos`, `can_send_video_notes`, and `can_send_voice_notes` to `ChatPermissions` and `Restricted` + - Add `use_independent_chat_permissions` optional parameter to `restrict_chat_member` and `set_chat_permissions` + - Add `user_chat_id` field to `ChatJoinRequest` + - Add `KeyboardButtonRequestChat` and `ChatShared` types + - Add `RequestChat` variant to `ButtonRequest` + - Add `ChatShared` variant to `MessageKind` + - Add `shared_chat` method to `Message` + - Add `KeyboardButtonRequestUser` and `UserShared` types + - Add `RequestUser` variant to `ButtonRequest` + - Add `UserShared` variant to `MessageKind` + - Add `shared_user` method to `Message` [pr851]: https://github.com/teloxide/teloxide/pull/851 [pr887]: https://github.com/teloxide/teloxide/pull/887 @@ -104,6 +116,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [pr850]: https://github.com/teloxide/teloxide/pull/850 +### Removed +- Remove `can_send_media_messages` from `ChatPermissions` ([#954][pr954]) +- Remove `can_send_media_messages` field from `Restricted` ([#954][pr954]) + +[pr954]: https://github.com/teloxide/teloxide/pull/954 + ### Fixed - Deserialization of `ApiError::CantParseEntities` ([#839][pr839]) diff --git a/crates/teloxide-core/schema.ron b/crates/teloxide-core/schema.ron index 6d6c7ca6..346f64b5 100644 --- a/crates/teloxide-core/schema.ron +++ b/crates/teloxide-core/schema.ron @@ -1988,6 +1988,11 @@ Schema( ty: RawTy("ChatPermissions"), descr: Doc(md: "A JSON-serialized object for new user permissions") ), + Param( + name: "use_independent_chat_permissions", + ty: Option(bool), + descr: Doc(md: "Pass _True_ if chat permissions are set independently. Otherwise, the _can\\_send\\_other\\_messages_ and _can\\_add\\_web\\_page\\_previews_ permissions will imply the _can\\_send\\_messages_, _can\\_send\\_audios_, _can\\_send\\_documents_, _can\\_send\\_photos_, _can\\_send\\_videos_, _can\\_send\\_video\\_notes_, and _can\\_send\\_voice\\_notes_ permissions; the _can\\_send\\_polls_ permission will imply the _can\\_send\\_messages_ permission.") + ), Param( name: "until_date", ty: Option(u64), @@ -2153,6 +2158,11 @@ Schema( ty: RawTy("ChatPermissions"), descr: Doc(md: "New default chat permissions") ), + Param( + name: "use_independent_chat_permissions", + ty: Option(bool), + descr: Doc(md: "Pass _True_ if chat permissions are set independently. Otherwise, the _can\\_send\\_other\\_messages_ and _can\\_add\\_web\\_page\\_previews_ permissions will imply the _can\\_send\\_messages_, _can\\_send\\_audios_, _can\\_send\\_documents_, _can\\_send\\_photos_, _can\\_send\\_videos_, _can\\_send\\_video\\_notes_, and _can\\_send\\_voice\\_notes_ permissions; the _can\\_send\\_polls_ permission will imply the _can\\_send\\_messages_ permission.") + ), ], ), Method( diff --git a/crates/teloxide-core/src/payloads/restrict_chat_member.rs b/crates/teloxide-core/src/payloads/restrict_chat_member.rs index 282af1dd..9ed55079 100644 --- a/crates/teloxide-core/src/payloads/restrict_chat_member.rs +++ b/crates/teloxide-core/src/payloads/restrict_chat_member.rs @@ -18,6 +18,8 @@ impl_payload! { pub permissions: ChatPermissions, } optional { + /// Pass _True_ if chat permissions are set independently. Otherwise, the _can\_send\_other\_messages_ and _can\_add\_web\_page\_previews_ permissions will imply the _can\_send\_messages_, _can\_send\_audios_, _can\_send\_documents_, _can\_send\_photos_, _can\_send\_videos_, _can\_send\_video\_notes_, and _can\_send\_voice\_notes_ permissions; the _can\_send\_polls_ permission will imply the _can\_send\_messages_ permission. + pub use_independent_chat_permissions: bool, /// Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever #[serde(with = "crate::types::serde_opt_date_from_unix_timestamp")] pub until_date: DateTime [into], diff --git a/crates/teloxide-core/src/payloads/set_chat_permissions.rs b/crates/teloxide-core/src/payloads/set_chat_permissions.rs index debfc9a9..38d40b4e 100644 --- a/crates/teloxide-core/src/payloads/set_chat_permissions.rs +++ b/crates/teloxide-core/src/payloads/set_chat_permissions.rs @@ -14,5 +14,9 @@ impl_payload! { /// New default chat permissions pub permissions: ChatPermissions, } + optional { + /// Pass _True_ if chat permissions are set independently. Otherwise, the _can\_send\_other\_messages_ and _can\_add\_web\_page\_previews_ permissions will imply the _can\_send\_messages_, _can\_send\_audios_, _can\_send\_documents_, _can\_send\_photos_, _can\_send\_videos_, _can\_send\_video\_notes_, and _can\_send\_voice\_notes_ permissions; the _can\_send\_polls_ permission will imply the _can\_send\_messages_ permission. + pub use_independent_chat_permissions: bool, + } } } diff --git a/crates/teloxide-core/src/types.rs b/crates/teloxide-core/src/types.rs index 0a94ce54..89897656 100644 --- a/crates/teloxide-core/src/types.rs +++ b/crates/teloxide-core/src/types.rs @@ -17,6 +17,7 @@ pub use chat_member::*; pub use chat_member_updated::*; pub use chat_permissions::*; pub use chat_photo::*; +pub use chat_shared::*; pub use chat_type::*; pub use chosen_inline_result::*; pub use contact::*; @@ -67,6 +68,8 @@ pub use input_sticker::*; 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 label_price::*; pub use location::*; pub use login_url::*; @@ -106,6 +109,7 @@ pub use unit_true::*; pub use update::*; pub use user::*; pub use user_profile_photos::*; +pub use user_shared::*; pub use venue::*; pub use video::*; pub use video_chat_ended::*; @@ -136,6 +140,7 @@ mod chat_member; mod chat_member_updated; mod chat_permissions; mod chat_photo; +mod chat_shared; mod chat_type; mod chosen_inline_result; mod contact; @@ -162,6 +167,8 @@ mod input_sticker; mod invoice; mod keyboard_button; mod keyboard_button_poll_type; +mod keyboard_button_request_chat; +mod keyboard_button_request_user; mod label_price; mod location; mod login_url; @@ -198,6 +205,7 @@ mod unit_true; mod update; mod user; mod user_profile_photos; +mod user_shared; mod venue; mod video; mod video_chat_ended; diff --git a/crates/teloxide-core/src/types/chat_join_request.rs b/crates/teloxide-core/src/types/chat_join_request.rs index 9f9d9a6d..0d5b896c 100644 --- a/crates/teloxide-core/src/types/chat_join_request.rs +++ b/crates/teloxide-core/src/types/chat_join_request.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use crate::types::{Chat, ChatInviteLink, User}; +use crate::types::{Chat, ChatId, ChatInviteLink, User}; /// Represents a join request sent to a chat. #[serde_with_macros::skip_serializing_none] @@ -11,6 +11,11 @@ pub struct ChatJoinRequest { pub chat: Chat, /// User that sent the join request pub from: User, + /// Identifier of a private chat with the user who sent the join request. + /// The bot can use this identifier for 5 minutes to send messages until + /// the join request is processed, assuming no other administrator + /// contacted the user. + pub user_chat_id: ChatId, /// Date the request was sent in Unix time #[serde(with = "crate::types::serde_date_from_unix_timestamp")] pub date: DateTime, diff --git a/crates/teloxide-core/src/types/chat_member.rs b/crates/teloxide-core/src/types/chat_member.rs index 8b91b03b..221bc4a6 100644 --- a/crates/teloxide-core/src/types/chat_member.rs +++ b/crates/teloxide-core/src/types/chat_member.rs @@ -120,9 +120,23 @@ pub struct Restricted { /// venues. pub can_send_messages: bool, - /// `true` if the user is allowed to send audios, documents, photos, videos, - /// video notes and voice notes. - pub can_send_media_messages: bool, + /// `true` if the user can send audios. + pub can_send_audios: bool, + + /// `true` if the user can send documents. + pub can_send_documents: bool, + + /// `true` if the user can send photos. + pub can_send_photos: bool, + + /// `true` if the user can send videos. + pub can_send_videos: bool, + + /// `true` if the user can send video notes. + pub can_send_video_notes: bool, + + /// `true` if the user can send voice notes. + pub can_send_voice_notes: bool, /// `true` if the user is allowed to send animations, games, stickers and /// use inline bots. @@ -623,19 +637,26 @@ impl ChatMemberKind { /// /// I.e. returns **`false`** if the user /// - has left or has been banned in the chat - /// - is restricted and doesn't have the [`can_send_media_messages`] right + /// - is restricted and doesn't have all send media right /// Returns `true` otherwise. - /// - /// [`can_send_media_messages`]: Restricted::can_send_media_messages - #[deprecated( - since = "0.9.0", - note = "Match manually and use `can_send_media_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781" - )] #[must_use] pub fn can_send_media_messages(&self) -> bool { match &self { - Self::Restricted(Restricted { can_send_media_messages, .. }) => { - *can_send_media_messages + Self::Restricted(Restricted { + can_send_audios, + can_send_documents, + can_send_photos, + can_send_videos, + can_send_video_notes, + can_send_voice_notes, + .. + }) => { + *can_send_audios + && *can_send_documents + && *can_send_photos + && *can_send_videos + && *can_send_video_notes + && *can_send_voice_notes } Self::Owner(_) | Self::Administrator(_) | Self::Member => true, Self::Left | Self::Banned(_) => false, @@ -647,10 +668,10 @@ impl ChatMemberKind { /// /// I.e. returns **`false`** if the user /// - has left or has been banned from the chat - /// - is restricted and doesn't have the [`can_send_media_messages`] right + /// - is restricted and has no [`can_send_other_messages`] right /// Returns `true` otherwise. /// - /// [`can_send_media_messages`]: Restricted::can_send_media_messages + /// [`can_send_other_messages`]: Restricted::can_send_other_messages #[deprecated( since = "0.9.0", note = "Match manually and use `can_send_other_messages` field directly. Details: https://github.com/teloxide/teloxide/issues/781" @@ -671,10 +692,10 @@ impl ChatMemberKind { /// /// I.e. returns **`false`** if the user /// - has left or has been banned from the chat - /// - is restricted and doesn't have the [`can_send_media_messages`] right + /// - is restricted and has no [`can_add_web_page_previews`] right /// Returns `true` otherwise. /// - /// [`can_send_media_messages`]: Restricted::can_send_media_messages + /// [`can_add_web_page_previews`]: Restricted::can_add_web_page_previews #[deprecated( since = "0.9.0", note = "Match manually and use `can_add_web_page_previews` field directly. Details: https://github.com/teloxide/teloxide/issues/781" @@ -826,7 +847,7 @@ mod tests { use super::*; #[test] - fn deserialize() { + fn deserialize_administrator() { let json = r#"{ "user":{ "id":1029940401, @@ -879,4 +900,70 @@ mod tests { let actual = serde_json::from_str::(json).unwrap(); assert_eq!(actual, expected) } + + #[test] + fn deserialize_restricted() { + let json = r#"{ + "user":{ + "id":1029940401, + "is_bot":false, + "first_name":"First", + "last_name":"Last", + "username":"fl", + "language_code":"en" + }, + "status":"restricted", + "is_member": true, + "can_send_messages": true, + "can_send_media_messages": true, + "can_send_audios": false, + "can_send_documents": false, + "can_send_photos": true, + "can_send_videos": true, + "can_send_video_notes": false, + "can_send_voice_notes": true, + "can_manage_topics": false, + "can_send_polls": true, + "can_send_other_messages": true, + "can_add_web_page_previews": true, + "can_change_info": true, + "can_invite_users": true, + "can_pin_messages": true, + "until_date": 1620000000 + }"#; + let expected = ChatMember { + user: User { + id: UserId(1029940401), + is_bot: false, + first_name: "First".to_string(), + last_name: Some("Last".to_string()), + username: Some("fl".to_string()), + language_code: Some("en".to_string()), + is_premium: false, + added_to_attachment_menu: false, + }, + kind: ChatMemberKind::Restricted(Restricted { + is_member: true, + can_send_messages: true, + can_send_audios: false, + can_send_documents: false, + can_send_photos: true, + can_send_videos: true, + can_send_video_notes: false, + can_send_voice_notes: true, + can_manage_topics: false, + can_send_polls: true, + can_send_other_messages: true, + can_add_web_page_previews: true, + can_change_info: true, + can_invite_users: true, + can_pin_messages: true, + until_date: UntilDate::Date( + chrono::NaiveDateTime::from_timestamp_opt(1620000000, 0).unwrap().and_utc(), + ), + }), + }; + let actual = serde_json::from_str::(json).unwrap(); + assert_eq!(actual, expected) + } } diff --git a/crates/teloxide-core/src/types/chat_permissions.rs b/crates/teloxide-core/src/types/chat_permissions.rs index 2a634c35..31b73e15 100644 --- a/crates/teloxide-core/src/types/chat_permissions.rs +++ b/crates/teloxide-core/src/types/chat_permissions.rs @@ -26,23 +26,23 @@ bitflags::bitflags! { /// assert!(!permissions_v0.contains(ChatPermissions::SEND_MESSAGES)); /// /// // Union, add permissions - /// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_MEDIA_MESSAGES; + /// let permissions_v1 = permissions_v0 | ChatPermissions::SEND_VIDEOS; /// assert!(permissions_v1.contains(ChatPermissions::INVITE_USERS)); - /// assert!(permissions_v1.contains(ChatPermissions::SEND_MEDIA_MESSAGES)); + /// assert!(permissions_v1.contains(ChatPermissions::SEND_VIDEOS)); /// - /// // Implied by `SEND_MEDIA_MESSAGES` + /// // Implied by `SEND_VIDEOS` /// 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)); + /// let permissions_v2 = permissions_v1 - ChatPermissions::SEND_VIDEOS; + /// assert!(!permissions_v2.contains(ChatPermissions::SEND_VIDEOS)); /// - /// // Removing `SEND_MEDIA_MESSAGES` also removes `SEND_MESSAGES` and vice versa - /// // because `SEND_MESSAGES` is implied by `SEND_MEDIA_MESSAGES` + /// // Removing `SEND_VIDEOS` also removes `SEND_MESSAGES` and vice versa + /// // because `SEND_MESSAGES` is implied by `SEND_VIDEOS` /// assert!(!permissions_v2.contains(ChatPermissions::SEND_MESSAGES)); /// /// let permissions_v3 = permissions_v1 - ChatPermissions::SEND_MESSAGES; - /// assert!(!permissions_v3.contains(ChatPermissions::SEND_MEDIA_MESSAGES)); + /// assert!(!permissions_v3.contains(ChatPermissions::SEND_VIDEOS)); /// ``` #[derive(Serialize, Deserialize)] #[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")] @@ -51,22 +51,17 @@ bitflags::bitflags! { /// locations and venues. const SEND_MESSAGES = 1; - /// 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; - /// Set if the user is allowed to send polls, implies /// `SEND_MESSAGES`. const SEND_POLLS = (1 << 2) | Self::SEND_MESSAGES.bits; /// 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; + const SEND_OTHER_MESSAGES = (1 << 3); /// 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; + const ADD_WEB_PAGE_PREVIEWS = (1 << 4); /// Set if the user is allowed to change the chat title, photo and /// other settings. Ignored in public supergroups. @@ -81,6 +76,44 @@ bitflags::bitflags! { /// Set if the user is allowed to create, rename, close, and reopen forum topics. const MANAGE_TOPICS = (1 << 8); + + /// Set if the user is allowed to send audios. implies + /// `SEND_MESSAGES`. + const SEND_AUDIOS = (1 << 9) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send documents. implies + /// `SEND_MESSAGES`. + const SEND_DOCUMENTS = (1 << 10) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send photos. implies + /// `SEND_MESSAGES`. + const SEND_PHOTOS = (1 << 11) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send videos. implies + /// `SEND_MESSAGES`. + const SEND_VIDEOS = (1 << 12) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send video notes. implies + /// `SEND_MESSAGES`. + const SEND_VIDEO_NOTES = (1 << 13) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send voice notes. implies + /// `SEND_MESSAGES`. + const SEND_VOICE_NOTES = (1 << 14) | Self::SEND_MESSAGES.bits; + + /// Set if the user is allowed to send audios, documents, + /// photos, videos, video notes and voice notes, implies + /// `SEND_MESSAGES`, `SEND_AUDIOS`, `SEND_DOCUMENTS`, + /// `SEND_PHOTOS`, `SEND_VIDEOS`, `SEND_VIDEO_NOTES` and `SEND_VOICE_NOTES`. + /// Note: this is not a separate permission on it's own, this is just a alias for all the permissions mentioned. + const SEND_MEDIA_MESSAGES = Self::SEND_MESSAGES.bits + | Self::SEND_AUDIOS.bits + | Self::SEND_DOCUMENTS.bits + | Self::SEND_PHOTOS.bits + | Self::SEND_VIDEOS.bits + | Self::SEND_VIDEO_NOTES.bits + | Self::SEND_VOICE_NOTES.bits; + } } @@ -92,6 +125,48 @@ impl ChatPermissions { self.contains(ChatPermissions::SEND_MESSAGES) } + /// Checks for [`SEND_AUDIOS`] permission. + /// + /// [`SEND_AUDIOS`]: ChatPermissions::SEND_AUDIOS + pub fn can_send_audios(&self) -> bool { + self.contains(ChatPermissions::SEND_AUDIOS) + } + + /// Checks for [`SEND_DOCUMENTS`] permission. + /// + /// [`SEND_DOCUMENTS`]: ChatPermissions::SEND_DOCUMENTS + pub fn can_send_documents(&self) -> bool { + self.contains(ChatPermissions::SEND_DOCUMENTS) + } + + /// Checks for [`SEND_PHOTOS`] permission. + /// + /// [`SEND_PHOTOS`]: ChatPermissions::SEND_PHOTOS + pub fn can_send_photos(&self) -> bool { + self.contains(ChatPermissions::SEND_PHOTOS) + } + + /// Checks for [`SEND_VIDEOS`] permission. + /// + /// [`SEND_VIDEOS`]: ChatPermissions::SEND_VIDEOS + pub fn can_send_videos(&self) -> bool { + self.contains(ChatPermissions::SEND_VIDEOS) + } + + /// Checks for [`SEND_VIDEO_NOTES`] permission. + /// + /// [`SEND_VIDEO_NOTES`]: ChatPermissions::SEND_VIDEO_NOTES + pub fn can_send_video_notes(&self) -> bool { + self.contains(ChatPermissions::SEND_VIDEO_NOTES) + } + + /// Checks for [`SEND_VOICE_NOTES`] permission. + /// + /// [`SEND_VOICE_NOTES`]: ChatPermissions::SEND_VOICE_NOTES + pub fn can_send_voice_notes(&self) -> bool { + self.contains(ChatPermissions::SEND_VOICE_NOTES) + } + /// Checks for [`SEND_MEDIA_MESSAGES`] permission. /// /// [`SEND_MEDIA_MESSAGES`]: ChatPermissions::SEND_MEDIA_MESSAGES @@ -156,7 +231,22 @@ struct ChatPermissionsRaw { can_send_messages: bool, #[serde(default, skip_serializing_if = "Not::not")] - can_send_media_messages: bool, + can_send_audios: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_documents: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_photos: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_videos: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_video_notes: bool, + + #[serde(default, skip_serializing_if = "Not::not")] + can_send_voice_notes: bool, #[serde(default, skip_serializing_if = "Not::not")] can_send_polls: bool, @@ -188,7 +278,12 @@ impl From for ChatPermissionsRaw { fn from(this: ChatPermissions) -> Self { Self { can_send_messages: this.can_send_messages(), - can_send_media_messages: this.can_send_media_messages(), + can_send_audios: this.contains(ChatPermissions::SEND_AUDIOS), + can_send_documents: this.contains(ChatPermissions::SEND_DOCUMENTS), + can_send_photos: this.contains(ChatPermissions::SEND_PHOTOS), + can_send_videos: this.contains(ChatPermissions::SEND_VIDEOS), + can_send_video_notes: this.contains(ChatPermissions::SEND_VIDEO_NOTES), + can_send_voice_notes: this.contains(ChatPermissions::SEND_VOICE_NOTES), can_send_polls: this.can_send_polls(), can_send_other_messages: this.can_send_other_messages(), can_add_web_page_previews: this.can_add_web_page_previews(), @@ -204,7 +299,12 @@ impl From for ChatPermissions { fn from( ChatPermissionsRaw { can_send_messages, - can_send_media_messages, + can_send_audios, + can_send_documents, + can_send_photos, + can_send_videos, + can_send_video_notes, + can_send_voice_notes, can_send_polls, can_send_other_messages, can_add_web_page_previews, @@ -219,8 +319,23 @@ impl From for ChatPermissions { if can_send_messages { this |= Self::SEND_MESSAGES; } - if can_send_media_messages { - this |= Self::SEND_MEDIA_MESSAGES + if can_send_audios { + this |= Self::SEND_AUDIOS; + } + if can_send_documents { + this |= Self::SEND_DOCUMENTS; + } + if can_send_photos { + this |= Self::SEND_PHOTOS; + } + if can_send_videos { + this |= Self::SEND_VIDEOS; + } + if can_send_video_notes { + this |= Self::SEND_VIDEO_NOTES; + } + if can_send_voice_notes { + this |= Self::SEND_VOICE_NOTES; } if can_send_polls { this |= Self::SEND_POLLS; @@ -255,17 +370,16 @@ mod tests { #[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,"can_manage_topics":false}"#; + let permissions = ChatPermissions::SEND_AUDIOS | ChatPermissions::PIN_MESSAGES; + let expected = r#"{"can_send_messages":true,"can_send_audios":true,"can_pin_messages":true,"can_manage_topics":false}"#; 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 json = r#"{"can_send_messages":true,"can_send_photos":true,"can_pin_messages":true}"#; + let expected = ChatPermissions::SEND_PHOTOS | ChatPermissions::PIN_MESSAGES; let actual = serde_json::from_str(json).unwrap(); assert_eq!(expected, actual); } diff --git a/crates/teloxide-core/src/types/chat_shared.rs b/crates/teloxide-core/src/types/chat_shared.rs new file mode 100644 index 00000000..4c75b94e --- /dev/null +++ b/crates/teloxide-core/src/types/chat_shared.rs @@ -0,0 +1,15 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ChatId; + +/// Information about the chat whose identifier was shared with the bot using a +/// [`KeyboardButtonRequestChat`] button. +/// +/// [`KeyboardButtonRequestChat`]: crate::types::KeyboardButtonRequestChat +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] +pub struct ChatShared { + /// Identifier of the request. + pub request_id: i32, + /// 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 c6c3390d..42aab9bd 100644 --- a/crates/teloxide-core/src/types/keyboard_button.rs +++ b/crates/teloxide-core/src/types/keyboard_button.rs @@ -1,6 +1,8 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; -use crate::types::{KeyboardButtonPollType, True, WebAppInfo}; +use crate::types::{ + KeyboardButtonPollType, KeyboardButtonRequestChat, KeyboardButtonRequestUser, True, WebAppInfo, +}; /// This object represents one button of the reply keyboard. /// @@ -59,6 +61,18 @@ pub enum ButtonRequest { /// 9 April, 2016. Older clients will display unsupported message. Contact, + /// If this variant is used, pressing the button will open a list of + /// suitable chats. Tapping on a chat will send its identifier to the bot in + /// a [`chat_shared`] service message. + /// + /// [`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 this variant is used, the user will be asked to create a poll and /// send it to the bot when the button is pressed. /// @@ -89,6 +103,18 @@ struct RawRequest { #[serde(rename = "request_location")] location: Option, + /// If specified, pressing the button will open a list of suitable chats. + /// Tapping on a chat will send its identifier to the bot in a “chat_shared” + /// service message. Available in private chats only. + #[serde(rename = "request_chat")] + 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, + /// 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 /// chats only. @@ -108,25 +134,36 @@ impl<'de> Deserialize<'de> for ButtonRequest { { let raw = RawRequest::deserialize(deserializer)?; match raw { - RawRequest { contact, location, poll, web_app } + RawRequest { contact, location, chat, user, 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 + poll.is_some() as u8 + web_app.is_some() as u8) => { Err(D::Error::custom( - "`request_contact`, `request_location`, `request_poll` and `web_app` fields \ - are mutually exclusive", + "`request_contact`, `request_location`, `request_chat`, `request_user`, \ + `request_poll` and `web_app` fields are mutually exclusive", )) } - RawRequest { contact: Some(_), .. } => Ok(Self::Contact), - RawRequest { location: Some(_), .. } => Ok(Self::Location), + 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 { poll: Some(poll_type), .. } => Ok(Self::Poll(poll_type)), RawRequest { web_app: Some(web_app), .. } => Ok(Self::WebApp(web_app)), - _ => Err(D::Error::custom( - "Either one of `request_contact`, `request_location`, `request_poll` and \ - `web_app` fields is required", + RawRequest { + contact: None, + location: None, + chat: None, + user: None, + poll: None, + web_app: None, + } => Err(D::Error::custom( + "Either one of `request_contact`, `request_chat`, `request_user`, \ + `request_location`, `request_poll` and `web_app` fields is required", )), } } @@ -137,11 +174,20 @@ impl Serialize for ButtonRequest { where S: Serializer, { - let mut raw = RawRequest { contact: None, location: None, poll: None, web_app: None }; + let mut raw = RawRequest { + contact: None, + location: None, + chat: None, + user: None, + poll: None, + web_app: None, + }; match self { 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::Poll(poll_type) => raw.poll = Some(poll_type.clone()), Self::WebApp(web_app) => raw.web_app = Some(web_app.clone()), }; @@ -171,6 +217,17 @@ mod tests { assert_eq!(expected, actual); } + #[test] + fn serialize_chat_request() { + let button = KeyboardButton { + text: String::from(""), + request: Some(ButtonRequest::RequestChat(KeyboardButtonRequestChat::new(0, false))), + }; + let expected = r#"{"text":"","request_chat":{"request_id":0,"chat_is_channel":false}}"#; + let actual = serde_json::to_string(&button).unwrap(); + assert_eq!(expected, actual); + } + #[test] fn deserialize_no_request() { let json = r#"{"text":""}"#; diff --git a/crates/teloxide-core/src/types/keyboard_button_request_chat.rs b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs new file mode 100644 index 00000000..fef4222b --- /dev/null +++ b/crates/teloxide-core/src/types/keyboard_button_request_chat.rs @@ -0,0 +1,114 @@ +use serde::{Deserialize, Serialize}; + +use crate::types::ChatAdministratorRights; + +/// 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 +/// corresponding button is pressed. [More about requesting chats »] +/// +/// [More about requesting chats »]: 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 KeyboardButtonRequestChat { + /// identifier of the request, which will be received back in the + /// [`ChatShared`] object. Must be unique within the message. + /// + /// [`ChatShared`]: crate::types::ChatShared + pub request_id: i32, + + /// Pass `true` to request a channel chat, pass `false` to request a group + /// or a supergroup chat. + pub chat_is_channel: bool, + + /// Pass `true` to request a forum supergroup, pass `false` to request a + /// non-forum chat. If not specified, no additional restrictions are + /// applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub chat_is_forum: Option, + + /// Pass `true` to request a supergroup or a channel with a username, pass + /// `false` to request a chat without a username. If not specified, no + /// additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub chat_has_username: Option, + + /// Pass `true` to request a chat owned by the user. Otherwise, no + /// additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub chat_is_created: Option, + + /// Listing the required administrator rights of the user in the chat. The + /// rights must be a superset of bot_administrator_rights. If not specified, + /// no additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_administrator_rights: Option, + + /// Listing the required administrator rights of the bot in the chat. The + /// rights must be a subset of user_administrator_rights. If not specified, + /// no additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bot_administrator_rights: Option, + + /// Pass `true` to request a chat with the bot as a member. Otherwise, no + /// additional restrictions are applied. + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub bot_is_member: bool, +} + +impl KeyboardButtonRequestChat { + /// Creates a new [`KeyboardButtonRequestChat`]. + pub fn new(request_id: i32, chat_is_channel: bool) -> Self { + Self { + request_id, + chat_is_channel, + chat_is_forum: None, + chat_has_username: None, + chat_is_created: None, + user_administrator_rights: None, + bot_administrator_rights: None, + bot_is_member: false, + } + } + + /// Setter for `chat_is_forum` field. + #[must_use] + pub fn chat_is_forum(mut self, value: bool) -> Self { + self.chat_is_forum = Some(value); + self + } + + /// Setter for `chat_has_username` field. + #[must_use] + pub fn chat_has_username(mut self, value: bool) -> Self { + self.chat_has_username = Some(value); + self + } + + /// Setter for `chat_is_created` field. + #[must_use] + pub fn chat_is_created(mut self, value: bool) -> Self { + self.chat_is_created = Some(value); + self + } + + /// Request a chat where the user has the specified administrator rights. + #[must_use] + pub fn user_administrator_rights(mut self, rights: ChatAdministratorRights) -> Self { + self.user_administrator_rights = Some(rights); + self + } + + /// Request a chat where the bot has the specified administrator rights. + #[must_use] + pub fn bot_administrator_rights(mut self, rights: ChatAdministratorRights) -> Self { + self.bot_administrator_rights = Some(rights); + self + } + + /// Setter for `bot_is_member` field. + #[must_use] + pub fn bot_is_member(mut self, value: bool) -> Self { + self.bot_is_member = value; + self + } +} diff --git a/crates/teloxide-core/src/types/keyboard_button_request_user.rs b/crates/teloxide-core/src/types/keyboard_button_request_user.rs new file mode 100644 index 00000000..843b86d1 --- /dev/null +++ b/crates/teloxide-core/src/types/keyboard_button_request_user.rs @@ -0,0 +1,44 @@ +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 +/// corresponding button is pressed. More about requesting users » +#[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. + /// + /// [`UserShared`]: crate::types::UserShared + pub request_id: i32, + + /// Pass `true` to request a bot, pass `false` to request a regular user. If + /// not specified, no additional restrictions are applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_is_bot: Option, + + /// Pass `true` to request a premium user, pass `false` to request a + /// non-premium user. If not specified, no additional restrictions are + /// applied. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub user_is_premium: Option, +} + +impl KeyboardButtonRequestUser { + /// Creates a new [`KeyboardButtonRequestUser`]. + pub fn new(request_id: i32) -> Self { + Self { request_id, user_is_bot: None, user_is_premium: None } + } + + /// Setter for `user_is_bot` field + pub fn user_is_bot(mut self, value: bool) -> Self { + self.user_is_bot = Some(value); + self + } + + /// Setter for `user_is_premium` field + pub fn user_is_premium(mut self, value: bool) -> Self { + self.user_is_premium = Some(value); + self + } +} diff --git a/crates/teloxide-core/src/types/message.rs b/crates/teloxide-core/src/types/message.rs index 781aab72..d988fad5 100644 --- a/crates/teloxide-core/src/types/message.rs +++ b/crates/teloxide-core/src/types/message.rs @@ -5,12 +5,12 @@ use serde::{Deserialize, Serialize}; use url::Url; use crate::types::{ - Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, ForumTopicClosed, - ForumTopicCreated, ForumTopicEdited, ForumTopicReopened, Game, GeneralForumTopicHidden, - GeneralForumTopicUnhidden, InlineKeyboardMarkup, Invoice, Location, + 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, SuccessfulPayment, ThreadId, True, User, - Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, + UserShared, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, WriteAccessAllowed, }; @@ -60,6 +60,8 @@ pub enum MessageKind { ChannelChatCreated(MessageChannelChatCreated), MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged), Pinned(MessagePinned), + ChatShared(MessageChatShared), + UserShared(MessageUserShared), Invoice(MessageInvoice), SuccessfulPayment(MessageSuccessfulPayment), ConnectedWebsite(MessageConnectedWebsite), @@ -247,6 +249,18 @@ pub struct MessagePinned { pub pinned: Box, } +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageChatShared { + /// A chat was shared with the bot. + pub chat_shared: ChatShared, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MessageUserShared { + /// A chat was shared with the bot. + pub user_shared: UserShared, +} + #[serde_with_macros::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MessageInvoice { @@ -655,12 +669,13 @@ mod getters { self, message::MessageKind::*, Chat, ChatId, ChatMigration, Forward, ForwardedFrom, MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind, MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo, - MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon, - MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity, + MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageChatShared, + MessageCommon, MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity, MessageGroupChatCreated, MessageId, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment, - MessageSupergroupChatCreated, MessageVideoChatParticipantsInvited, PhotoSize, User, + MessageSupergroupChatCreated, MessageUserShared, MessageVideoChatParticipantsInvited, + PhotoSize, User, }; use super::{ @@ -1269,6 +1284,22 @@ mod getters { } } + #[must_use] + pub fn shared_chat(&self) -> Option<&types::ChatShared> { + match &self.kind { + ChatShared(MessageChatShared { chat_shared }) => Some(chat_shared), + _ => None, + } + } + + #[must_use] + pub fn shared_user(&self) -> Option<&types::UserShared> { + match &self.kind { + UserShared(MessageUserShared { user_shared }) => Some(user_shared), + _ => None, + } + } + #[must_use] pub fn dice(&self) -> Option<&types::Dice> { match &self.kind { @@ -1699,6 +1730,56 @@ mod tests { assert!(message.is_ok()); } + #[test] + fn de_shared_chat() { + let json = r#"{ + "message_id": 198283, + "chat": { + "id": 250918540, + "first_name": "Андрей", + "last_name": "Власов", + "username": "aka_dude", + "type": "private" + }, + "date": 1567927221, + "chat_shared": { + "request_id": 348349, + "chat_id": 384939 + } + }"#; + let message = from_str::(json); + assert!(message.is_ok()); + assert_eq!( + message.unwrap(), + Message { + id: MessageId(198283), + thread_id: None, + date: chrono::NaiveDateTime::from_timestamp_opt(1567927221, 0).unwrap().and_utc(), + chat: Chat { + id: ChatId(250918540), + kind: ChatKind::Private(ChatPrivate { + first_name: Some("Андрей".to_string()), + 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 + }), + photo: None, + has_aggressive_anti_spam_enabled: false, + pinned_message: None, + message_auto_delete_time: None, + has_hidden_members: false + }, + kind: MessageKind::ChatShared(MessageChatShared { + chat_shared: ChatShared { request_id: 348349, chat_id: ChatId(384939) } + }), + via_bot: None + } + ); + } + #[test] fn de_media_group_forwarded() { let json = r#"{ diff --git a/crates/teloxide-core/src/types/user_shared.rs b/crates/teloxide-core/src/types/user_shared.rs new file mode 100644 index 00000000..1fd0cefe --- /dev/null +++ b/crates/teloxide-core/src/types/user_shared.rs @@ -0,0 +1,15 @@ +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, +}