diff --git a/CHANGELOG.md b/CHANGELOG.md index fd016314..8a2543d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,15 +10,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `impl Clone` for {`CacheMe`, `DefaultParseMode`, `Throttle`} ([#75][pr75]) +- Getters for fields nested in `Chat` ([#80][pr80]) - API errors: `ApiError::NotEnoughRightsToManagePins`, `ApiError::BotKickedFromSupergroup` ([#84][pr84]) - Telegram bot API 5.2 support ([#86][pr86]) [pr75]: https://github.com/teloxide/teloxide-core/pull/75 +[pr80]: https://github.com/teloxide/teloxide-core/pull/80 [pr84]: https://github.com/teloxide/teloxide-core/pull/84 [pr86]: https://github.com/teloxide/teloxide-core/pull/86 ### Changed +- `Message::url` now returns links to messages in private groups too ([#80][pr80]) - Refactor `ChatMember` methods ([#74][pr74]) - impl `Deref<Target = ChatMemberKind>` to make `ChatMemberKind`'s methods callible directly on `ChatMember` - Add `ChatMemberKind::is_{creator,administrator,member,restricted,left,kicked}` which check `kind` along with `is_privileged` and `is_in_chat` which combine some of the above. @@ -29,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Type of `PublicChatSupergroup::slow_mode_delay` field: `Option<i32>`=> `Option<u32>` ([#80][pr80]) +- Add missing `Chat::message_auto_delete_time` field ([#80][pr80]) - Output types of `LeaveChat` `PinChatMessage`, `SetChatDescription`, `SetChatPhoto` `SetChatTitle`, `UnpinAllChatMessages` and `UnpinChatMessage`: `String` => `True` ([#79][pr79]) - `SendChatAction` output type `Message` => `True` ([#75][pr75]) - `GetChatAdministrators` output type `ChatMember` => `Vec<ChatMember>` ([#73][pr73]) diff --git a/src/types/chat.rs b/src/types/chat.rs index 24dd6805..aaedc5d2 100644 --- a/src/types/chat.rs +++ b/src/types/chat.rs @@ -28,6 +28,12 @@ pub struct Chat { /// /// [`GetChat`]: crate::payloads::GetChat pub pinned_message: Option<Box<Message>>, + + /// The time after which all messages sent to the chat will be automatically + /// deleted; in seconds. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub message_auto_delete_time: Option<u32>, } #[serde_with_macros::skip_serializing_none] @@ -153,7 +159,7 @@ pub struct PublicChatSupergroup { /// unpriviledged user. Returned only from [`GetChat`]. /// /// [`GetChat`]: crate::payloads::GetChat - pub slow_mode_delay: Option<i32>, + pub slow_mode_delay: Option<u32>, /// Unique identifier for the linked chat, i.e. the discussion group /// identifier for a channel and vice versa. Returned only in [`GetChat`]. @@ -199,6 +205,7 @@ impl Chat { pub fn is_private(&self) -> bool { matches!(self.kind, ChatKind::Private(_)) } + pub fn is_group(&self) -> bool { matches!( self.kind, @@ -208,6 +215,7 @@ impl Chat { }) ) } + pub fn is_supergroup(&self) -> bool { matches!( self.kind, @@ -217,6 +225,7 @@ impl Chat { }) ) } + pub fn is_channel(&self) -> bool { matches!( self.kind, @@ -232,6 +241,174 @@ impl Chat { } } +/// Getters +impl Chat { + /// A title, for supergroups, channels and group chats. + pub fn title(&self) -> Option<&str> { + match &self.kind { + ChatKind::Public(this) => this.title.as_deref(), + _ => None, + } + } + + /// A username, for private chats, supergroups and channels if available. + pub fn username(&self) -> Option<&str> { + match &self.kind { + ChatKind::Public(this) => match &this.kind { + PublicChatKind::Channel(PublicChatChannel { username, .. }) + | PublicChatKind::Supergroup(PublicChatSupergroup { username, .. }) => { + username.as_deref() + } + PublicChatKind::Group(_) => None, + }, + ChatKind::Private(this) => this.username.as_deref(), + } + } + + /// Unique identifier for the linked chat, i.e. the discussion group + /// identifier for a channel and vice versa. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn linked_chat_id(&self) -> Option<i64> { + match &self.kind { + ChatKind::Public(this) => match &this.kind { + PublicChatKind::Channel(PublicChatChannel { linked_chat_id, .. }) + | PublicChatKind::Supergroup(PublicChatSupergroup { linked_chat_id, .. }) => { + *linked_chat_id + } + PublicChatKind::Group(_) => None, + }, + _ => None, + } + } + + /// A default chat member permissions, for groups and supergroups. Returned + /// only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn permissions(&self) -> Option<ChatPermissions> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Group(PublicChatGroup { permissions }) + | PublicChatKind::Supergroup(PublicChatSupergroup { permissions, .. }) = &this.kind + { + return *permissions; + } + } + + None + } + + /// For supergroups, name of group sticker set. Returned only from + /// [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn sticker_set_name(&self) -> Option<&str> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.sticker_set_name.as_deref(); + } + } + + None + } + + /// `true`, if the bot can change the group sticker set. Returned only + /// from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn can_set_sticker_set(&self) -> Option<bool> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.can_set_sticker_set; + } + } + + None + } + + /// The minimum allowed delay between consecutive messages sent by each + /// unpriviledged user. Returned only from [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn slow_mode_delay(&self) -> Option<u32> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.slow_mode_delay; + } + } + + None + } + + /// The location to which the supergroup is connected. Returned only in + /// [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn location(&self) -> Option<&ChatLocation> { + if let ChatKind::Public(this) = &self.kind { + if let PublicChatKind::Supergroup(this) = &this.kind { + return this.location.as_ref(); + } + } + + None + } + + /// A description, for groups, supergroups and channel chats. Returned + /// only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn description(&self) -> Option<&str> { + match &self.kind { + ChatKind::Public(this) => this.description.as_deref(), + _ => None, + } + } + + /// A chat invite link, for groups, supergroups and channel chats. Each + /// administrator in a chat generates their own invite links, so the + /// bot must first generate the link using + /// [`ExportChatInviteLink`]. Returned only in + /// [`GetChat`]. + /// + /// [`ExportChatInviteLink`]: + /// crate::payloads::ExportChatInviteLink + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn invite_link(&self) -> Option<&str> { + match &self.kind { + ChatKind::Public(this) => this.invite_link.as_deref(), + _ => None, + } + } + + /// A first name of the other party in a private chat. + pub fn first_name(&self) -> Option<&str> { + match &self.kind { + ChatKind::Private(this) => this.first_name.as_deref(), + _ => None, + } + } + + /// A last name of the other party in a private chat. + pub fn last_name(&self) -> Option<&str> { + match &self.kind { + ChatKind::Private(this) => this.last_name.as_deref(), + _ => None, + } + } + + /// Bio of the other party in a private chat. Returned only in [`GetChat`]. + /// + /// [`GetChat`]: crate::payloads::GetChat + pub fn bio(&self) -> Option<&str> { + match &self.kind { + ChatKind::Private(this) => this.bio.as_deref(), + _ => None, + } + } +} + #[cfg(test)] mod tests { use serde_json::from_str; @@ -253,6 +430,7 @@ mod tests { }), photo: None, pinned_message: None, + message_auto_delete_time: None, }; let actual = from_str(r#"{"id":-1,"type":"channel","username":"channelname"}"#).unwrap(); assert_eq!(expected, actual); @@ -272,6 +450,7 @@ mod tests { }), photo: None, pinned_message: None, + message_auto_delete_time: None, }, from_str(r#"{"id":0,"type":"private","username":"username","first_name":"Anon"}"#) .unwrap() diff --git a/src/types/message.rs b/src/types/message.rs index e5241004..71152820 100644 --- a/src/types/message.rs +++ b/src/types/message.rs @@ -989,29 +989,37 @@ mod getters { } impl Message { + /// Produces a direct link to the message. + /// + /// Note that for private groups the link will only be accesible for group + /// members. + /// + /// Returns `None` for private chats (i.e.: DMs). pub fn url(&self) -> Option<reqwest::Url> { - match &self.chat.kind { - ChatKind::Public(ChatPublic { - kind: - PublicChatKind::Channel(PublicChatChannel { - username: Some(username), - .. - }), - .. - }) - | ChatKind::Public(ChatPublic { - kind: - PublicChatKind::Supergroup(PublicChatSupergroup { - username: Some(username), - .. - }), - .. - }) => Some( - reqwest::Url::parse(format!("https://t.me/{0}/{1}/", username, self.id).as_str()) - .unwrap(), - ), - _ => None, + if self.chat.is_private() { + // For private chats (i.e.: DMs) we can't produce "normal" t.me link. + // + // There are "tg://openmessage?user_id={0}&message_id={1}" links, which are + // supposed to open any chat, including private messages, but they + // are only supported by some telegram clients (e.g. Plus Messenger, + // Telegram for Androud 4.9+). + return None; } + + let url = match self.chat.username() { + // If it's public group (i.e. not DM, not private group), we can produce + // "normal" t.me link (accesible to everyone). + Some(username) => format!("https://t.me/{0}/{1}/", username, self.id), + // For private groups we produce "private" t.me/c links. These are only + // accesible to the group members. + None => format!("https://t.me/c/{0}/{1}/", self.chat.id, self.id), + }; + + // UNWRAP: + // + // The `url` produced by formatting is correct since username is + // /[a-zA-Z0-9_]{5,32}/ and chat/message ids are integers. + Some(reqwest::Url::parse(&url).unwrap()) } } diff --git a/src/types/update.rs b/src/types/update.rs index 94ec099e..5a1c1355 100644 --- a/src/types/update.rs +++ b/src/types/update.rs @@ -186,6 +186,7 @@ mod test { }), photo: None, pinned_message: None, + message_auto_delete_time: None, }, kind: MessageKind::Common(MessageCommon { from: Some(User {