TBA 6.3: types

This commit is contained in:
Maybe Waffle 2022-12-05 17:02:02 +04:00
parent a84a90e038
commit 976100e3ec
11 changed files with 238 additions and 9 deletions

View file

@ -27,6 +27,10 @@ pub use encrypted_credentials::*;
pub use encrypted_passport_element::*; pub use encrypted_passport_element::*;
pub use file::*; pub use file::*;
pub use force_reply::*; pub use force_reply::*;
pub use forum_topic::*;
pub use forum_topic_closed::*;
pub use forum_topic_created::*;
pub use forum_topic_reopened::*;
pub use game::*; pub use game::*;
pub use game_high_score::*; pub use game_high_score::*;
pub use inline_keyboard_button::*; pub use inline_keyboard_button::*;
@ -86,7 +90,6 @@ pub use reply_keyboard_remove::*;
pub use reply_markup::*; pub use reply_markup::*;
pub use response_parameters::*; pub use response_parameters::*;
pub use sent_web_app_message::*; pub use sent_web_app_message::*;
use serde::Serialize;
pub use shipping_address::*; pub use shipping_address::*;
pub use shipping_option::*; pub use shipping_option::*;
pub use shipping_query::*; pub use shipping_query::*;
@ -136,6 +139,10 @@ mod dice_emoji;
mod document; mod document;
mod file; mod file;
mod force_reply; mod force_reply;
mod forum_topic;
mod forum_topic_closed;
mod forum_topic_created;
mod forum_topic_reopened;
mod game; mod game;
mod game_high_score; mod game_high_score;
mod inline_keyboard_button; mod inline_keyboard_button;
@ -239,6 +246,8 @@ pub use chat_id::*;
pub use recipient::*; pub use recipient::*;
pub use user_id::*; pub use user_id::*;
use serde::Serialize;
/// Converts an `i64` timestump to a `choro::DateTime`, producing serde error /// Converts an `i64` timestump to a `choro::DateTime`, producing serde error
/// for invalid timestumps /// for invalid timestumps
pub(crate) fn serde_timestamp<E: serde::de::Error>( pub(crate) fn serde_timestamp<E: serde::de::Error>(
@ -420,3 +429,49 @@ where
{ {
this.map(|MessageId(id)| id).serialize(serializer) this.map(|MessageId(id)| id).serialize(serializer)
} }
pub(crate) mod serde_rgb {
use serde::{de::Visitor, Deserializer, Serializer};
pub fn serialize<S: Serializer>(&this: &[u8; 3], s: S) -> Result<S::Ok, S::Error> {
s.serialize_u32(to_u32(this))
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<[u8; 3], D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = [u8; 3];
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an integer represeting an RGB color")
}
fn visit_u32<E>(self, v: u32) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(from_u32(v))
}
}
d.deserialize_u32(V)
}
fn to_u32([r, g, b]: [u8; 3]) -> u32 {
u32::from_be_bytes([0, r, g, b])
}
fn from_u32(rgb: u32) -> [u8; 3] {
let [_, r, g, b] = rgb.to_be_bytes();
[r, g, b]
}
#[test]
fn bytes() {
assert_eq!(to_u32([0xAA, 0xBB, 0xCC]), 0x00AABBCC);
assert_eq!(from_u32(0x00AABBCC), [0xAA, 0xBB, 0xCC]);
}
#[test]
fn json() {}
}

View file

@ -88,6 +88,13 @@ pub struct ChatPrivate {
/// A last name of the other party in a private chat. /// A last name of the other party in a private chat.
pub last_name: Option<String>, pub last_name: Option<String>,
/// 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<String>,
/// Bio of the other party in a private chat. Returned only in [`GetChat`]. /// Bio of the other party in a private chat. Returned only in [`GetChat`].
/// ///
/// [`GetChat`]: crate::payloads::GetChat /// [`GetChat`]: crate::payloads::GetChat
@ -148,6 +155,16 @@ pub struct PublicChatSupergroup {
/// available. /// available.
pub username: Option<String>, pub username: Option<String>,
/// If non-empty, the list of all active chat usernames; for private chats,
/// supergroups and channels. Returned only from [`GetChat`].
///
/// [`GetChat`]: crate::payloads::GetChat
pub active_usernames: Option<Vec<String>>,
/// `true`, if the supergroup chat is a forum (has topics enabled).
#[serde(default)]
pub is_forum: bool,
/// For supergroups, name of group sticker set. Returned only from /// For supergroups, name of group sticker set. Returned only from
/// [`GetChat`]. /// [`GetChat`].
/// ///
@ -485,6 +502,7 @@ mod serde_helper {
bio: Option<String>, bio: Option<String>,
has_private_forwards: Option<True>, has_private_forwards: Option<True>,
has_restricted_voice_and_video_messages: Option<True>, has_restricted_voice_and_video_messages: Option<True>,
emoji_status_custom_emoji_id: Option<String>,
} }
impl From<ChatPrivate> for super::ChatPrivate { impl From<ChatPrivate> for super::ChatPrivate {
@ -497,6 +515,7 @@ mod serde_helper {
bio, bio,
has_private_forwards, has_private_forwards,
has_restricted_voice_and_video_messages, has_restricted_voice_and_video_messages,
emoji_status_custom_emoji_id,
}: ChatPrivate, }: ChatPrivate,
) -> Self { ) -> Self {
Self { Self {
@ -506,6 +525,7 @@ mod serde_helper {
bio, bio,
has_private_forwards, has_private_forwards,
has_restricted_voice_and_video_messages, has_restricted_voice_and_video_messages,
emoji_status_custom_emoji_id,
} }
} }
} }
@ -519,6 +539,7 @@ mod serde_helper {
bio, bio,
has_private_forwards, has_private_forwards,
has_restricted_voice_and_video_messages, has_restricted_voice_and_video_messages,
emoji_status_custom_emoji_id,
}: super::ChatPrivate, }: super::ChatPrivate,
) -> Self { ) -> Self {
Self { Self {
@ -529,6 +550,7 @@ mod serde_helper {
bio, bio,
has_private_forwards, has_private_forwards,
has_restricted_voice_and_video_messages, has_restricted_voice_and_video_messages,
emoji_status_custom_emoji_id,
} }
} }
} }
@ -574,6 +596,7 @@ mod tests {
bio: None, bio: None,
has_private_forwards: None, has_private_forwards: None,
has_restricted_voice_and_video_messages: None, has_restricted_voice_and_video_messages: None,
emoji_status_custom_emoji_id: None
}), }),
photo: None, photo: None,
pinned_message: None, pinned_message: None,
@ -595,6 +618,7 @@ mod tests {
bio: None, bio: None,
has_private_forwards: None, has_private_forwards: None,
has_restricted_voice_and_video_messages: None, has_restricted_voice_and_video_messages: None,
emoji_status_custom_emoji_id: None,
}), }),
photo: None, photo: None,
pinned_message: None, pinned_message: None,

View file

@ -45,4 +45,8 @@ pub struct ChatAdministratorRights {
/// `true`, if the user is allowed to pin messages; groups and /// `true`, if the user is allowed to pin messages; groups and
/// supergroups only /// supergroups only
pub can_pin_messages: Option<bool>, pub can_pin_messages: Option<bool>,
/// `true`, if the user is allowed to create, rename, close, and reopen
/// forum topics; supergroups only
pub can_manage_topics: Option<bool>,
} }

View file

@ -87,6 +87,10 @@ pub struct Administrator {
/// `true` if the administrator can pin messages, supergroups only. /// `true` if the administrator can pin messages, supergroups only.
pub can_pin_messages: Option<bool>, pub can_pin_messages: Option<bool>,
/// `true`, if the user is allowed to create, rename, close, and reopen
/// forum topics; supergroups only
pub can_manage_topics: Option<bool>,
/// `true` if the administrator can add new administrators with a subset of /// `true` if the administrator can add new administrators with a subset of
/// his own privileges or demote administrators that he has promoted, /// his own privileges or demote administrators that he has promoted,
/// directly or indirectly (promoted by administrators that were appointed /// directly or indirectly (promoted by administrators that were appointed
@ -130,6 +134,10 @@ pub struct Restricted {
/// `true` if the user is allowed to pin messages. /// `true` if the user is allowed to pin messages.
pub can_pin_messages: bool, pub can_pin_messages: bool,
/// `true`, if the user is allowed to create, rename, close, and reopen
/// forum topics
pub can_manage_topics: bool,
/// `true` if the user is allowed to send polls. /// `true` if the user is allowed to send polls.
pub can_send_polls: bool, pub can_send_polls: bool,
} }
@ -514,6 +522,27 @@ impl ChatMemberKind {
} }
} }
/// Returns `true` if the user is allowed to manage topics.
///
/// I.e. returns `true` if the user
/// - is the owner of the chat (even if the chat is not a supergroup)
/// - is an administrator in the given chat and has the
/// [`Administrator::can_manage_topics`] privilege.
/// - is restricted, but does have [`Restricted::can_manage_topics`]
/// privilege
/// Returns `false` otherwise.
#[must_use]
pub fn can_manage_topics(&self) -> bool {
match self {
ChatMemberKind::Owner(_) => true,
ChatMemberKind::Administrator(Administrator { can_manage_topics, .. }) => {
can_manage_topics.unwrap_or_default()
}
ChatMemberKind::Restricted(Restricted { can_manage_topics, .. }) => *can_manage_topics,
ChatMemberKind::Member | ChatMemberKind::Left | ChatMemberKind::Banned(_) => false,
}
}
/// Returns `true` if the user can add new administrators with a subset of /// Returns `true` if the user can add new administrators with a subset of
/// his own privileges or demote administrators that he has promoted, /// his own privileges or demote administrators that he has promoted,
/// directly or indirectly (promoted by administrators that were appointed /// directly or indirectly (promoted by administrators that were appointed
@ -780,6 +809,7 @@ mod tests {
can_restrict_members: true, can_restrict_members: true,
can_pin_messages: Some(true), can_pin_messages: Some(true),
can_promote_members: true, can_promote_members: true,
can_manage_topics: None,
}), }),
}; };
let actual = serde_json::from_str::<ChatMember>(json).unwrap(); let actual = serde_json::from_str::<ChatMember>(json).unwrap();

View file

@ -46,7 +46,7 @@ bitflags::bitflags! {
/// ``` /// ```
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")] #[serde(from = "ChatPermissionsRaw", into = "ChatPermissionsRaw")]
pub struct ChatPermissions: u8 { pub struct ChatPermissions: u16 {
/// Set if the user is allowed to send text messages, contacts, /// Set if the user is allowed to send text messages, contacts,
/// locations and venues. /// locations and venues.
const SEND_MESSAGES = 1; const SEND_MESSAGES = 1;
@ -78,9 +78,14 @@ bitflags::bitflags! {
/// Set if the user is allowed to pin messages. Ignored in public /// Set if the user is allowed to pin messages. Ignored in public
/// supergroups. /// supergroups.
const PIN_MESSAGES = (1 << 7); const PIN_MESSAGES = (1 << 7);
/// Set if the user is allowed to create, rename, close, and reopen forum topics.
const MANAGE_TOPICS = (1 << 8);
} }
} }
// FIXME: add `can_*` methods for convinience
/// Helper for (de)serialization /// Helper for (de)serialization
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
struct ChatPermissionsRaw { struct ChatPermissionsRaw {
@ -107,6 +112,13 @@ struct ChatPermissionsRaw {
#[serde(default, skip_serializing_if = "Not::not")] #[serde(default, skip_serializing_if = "Not::not")]
can_pin_messages: bool, can_pin_messages: bool,
// HACK: do not `skip_serializing_if = "Not::not"`, from tg docs:
// > If omitted defaults to the value of `can_pin_messages`
// but we don't have two different values for "absent" and "false"...
// or did they mean that `can_pin_messages` implies `can_manage_topics`?..
#[serde(default)]
can_manage_topics: bool,
} }
impl From<ChatPermissions> for ChatPermissionsRaw { impl From<ChatPermissions> for ChatPermissionsRaw {
@ -120,6 +132,7 @@ impl From<ChatPermissions> for ChatPermissionsRaw {
can_change_info: this.contains(ChatPermissions::CHANGE_INFO), can_change_info: this.contains(ChatPermissions::CHANGE_INFO),
can_invite_users: this.contains(ChatPermissions::INVITE_USERS), can_invite_users: this.contains(ChatPermissions::INVITE_USERS),
can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES), can_pin_messages: this.contains(ChatPermissions::PIN_MESSAGES),
can_manage_topics: this.contains(ChatPermissions::MANAGE_TOPICS),
} }
} }
} }
@ -135,6 +148,7 @@ impl From<ChatPermissionsRaw> for ChatPermissions {
can_change_info, can_change_info,
can_invite_users, can_invite_users,
can_pin_messages, can_pin_messages,
can_manage_topics,
}: ChatPermissionsRaw, }: ChatPermissionsRaw,
) -> Self { ) -> Self {
let mut this = Self::empty(); let mut this = Self::empty();
@ -163,6 +177,10 @@ impl From<ChatPermissionsRaw> for ChatPermissions {
if can_pin_messages { if can_pin_messages {
this |= Self::PIN_MESSAGES; this |= Self::PIN_MESSAGES;
} }
// FIXME: should we do `|| can_pin_messages` here? (the same tg doc weirdness)
if can_manage_topics {
this |= Self::MANAGE_TOPICS
}
this this
} }
@ -175,8 +193,7 @@ mod tests {
#[test] #[test]
fn serialization() { fn serialization() {
let permissions = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES; let permissions = ChatPermissions::SEND_MEDIA_MESSAGES | ChatPermissions::PIN_MESSAGES;
let expected = let expected = r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true,"can_manage_topics":false}"#;
r#"{"can_send_messages":true,"can_send_media_messages":true,"can_pin_messages":true}"#;
let actual = serde_json::to_string(&permissions).unwrap(); let actual = serde_json::to_string(&permissions).unwrap();
assert_eq!(expected, actual); assert_eq!(expected, actual);
} }

View file

@ -0,0 +1,24 @@
use serde::{Deserialize, Serialize};
/// This object represents a forum topic.
///
/// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated).
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ForumTopic {
/// Unique identifier of the forum topic
// FIXME: MessageThreadId or something
pub message_thread_id: i32,
/// Name of the topic.
pub name: String,
/// Color of the topic icon in RGB format.
// FIXME: use/add a specialized rgb color type?
#[serde(with = "crate::types::serde_rgb")]
pub icon_color: [u8; 3],
/// Unique identifier of the custom emoji shown as the topic icon.
// FIXME: CustomEmojiId
pub icon_custom_emoji_id: Option<String>,
}

View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
/// This object represents a service message about a forum topic closed in the
/// chat. Currently holds no information.
///
/// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated).
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ForumTopicClosed;

View file

@ -0,0 +1,21 @@
use serde::{Deserialize, Serialize};
/// This object represents a service message about a new forum topic created in
/// the chat.
///
/// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated).
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ForumTopicCreated {
/// Name of the topic.
pub name: String,
/// Color of the topic icon in RGB format.
// FIXME: use/add a specialized rgb color type?
#[serde(with = "crate::types::serde_rgb")]
pub icon_color: [u8; 3],
/// Unique identifier of the custom emoji shown as the topic icon.
// FIXME: CustomEmojiId
pub icon_custom_emoji_id: Option<String>,
}

View file

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
/// This object represents a service message about a forum topic reopened in the
/// chat. Currently holds no information.
///
/// [The official docs](https://core.telegram.org/bots/api#forumtopiccreated).
#[serde_with_macros::skip_serializing_none]
#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct ForumTopicReopened;

View file

@ -5,11 +5,12 @@ use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
use crate::types::{ use crate::types::{
Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, Game, Animation, Audio, BareChatId, Chat, ChatId, Contact, Dice, Document, ForumTopicClosed,
InlineKeyboardMarkup, Invoice, Location, MessageAutoDeleteTimerChanged, MessageEntity, ForumTopicCreated, ForumTopicReopened, Game, InlineKeyboardMarkup, Invoice, Location,
MessageEntityRef, MessageId, PassportData, PhotoSize, Poll, ProximityAlertTriggered, Sticker, MessageAutoDeleteTimerChanged, MessageEntity, MessageEntityRef, MessageId, PassportData,
SuccessfulPayment, True, User, Venue, Video, VideoChatEnded, VideoChatParticipantsInvited, PhotoSize, Poll, ProximityAlertTriggered, Sticker, SuccessfulPayment, True, User, Venue, Video,
VideoChatScheduled, VideoChatStarted, VideoNote, Voice, WebAppData, VideoChatEnded, VideoChatParticipantsInvited, VideoChatScheduled, VideoChatStarted, VideoNote,
Voice, WebAppData,
}; };
/// This object represents a message. /// This object represents a message.
@ -21,6 +22,11 @@ pub struct Message {
#[serde(flatten)] #[serde(flatten)]
pub id: MessageId, pub id: MessageId,
/// Unique identifier of a message thread to which the message belongs; for
/// supergroups only.
// FIXME: MessageThreadId or such
pub thread_id: Option<i32>,
/// Date the message was sent in Unix time. /// Date the message was sent in Unix time.
#[serde(with = "crate::types::serde_date_from_unix_timestamp")] #[serde(with = "crate::types::serde_date_from_unix_timestamp")]
pub date: DateTime<Utc>, pub date: DateTime<Utc>,
@ -55,6 +61,9 @@ pub enum MessageKind {
PassportData(MessagePassportData), PassportData(MessagePassportData),
Dice(MessageDice), Dice(MessageDice),
ProximityAlertTriggered(MessageProximityAlertTriggered), ProximityAlertTriggered(MessageProximityAlertTriggered),
ForumTopicCreated(ForumTopicCreated),
ForumTopicClosed(ForumTopicClosed),
ForumTopicReopened(ForumTopicReopened),
VideoChatScheduled(MessageVideoChatScheduled), VideoChatScheduled(MessageVideoChatScheduled),
VideoChatStarted(MessageVideoChatStarted), VideoChatStarted(MessageVideoChatStarted),
VideoChatEnded(MessageVideoChatEnded), VideoChatEnded(MessageVideoChatEnded),
@ -98,6 +107,10 @@ pub struct MessageCommon {
/// represented as ordinary `url` buttons. /// represented as ordinary `url` buttons.
pub reply_markup: Option<InlineKeyboardMarkup>, pub reply_markup: Option<InlineKeyboardMarkup>,
/// `true`, if the message is sent to a forum topic.
#[serde(default)]
pub is_topic_message: bool,
/// `true`, if the message is a channel post that was automatically /// `true`, if the message is a channel post that was automatically
/// forwarded to the connected discussion group. /// forwarded to the connected discussion group.
#[serde(default)] #[serde(default)]
@ -493,6 +506,24 @@ pub struct MessageProximityAlertTriggered {
pub proximity_alert_triggered: ProximityAlertTriggered, pub proximity_alert_triggered: ProximityAlertTriggered,
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageForumTopicCreated {
/// Service message: forum topic created.
pub forum_topic_created: ForumTopicCreated,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageForumTopicClosed {
/// Service message: forum topic closed.
pub forum_topic_closed: ForumTopicClosed,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageForumTopicReopened {
/// Service message: forum topic reopened.
pub forum_topic_reopened: ForumTopicReopened,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct MessageVideoChatScheduled { pub struct MessageVideoChatScheduled {
/// Service message: video chat scheduled /// Service message: video chat scheduled
@ -1543,6 +1574,8 @@ mod tests {
location: None, location: None,
join_by_request: None, join_by_request: None,
join_to_send_messages: None, join_to_send_messages: None,
active_usernames: None,
is_forum: false,
}), }),
description: None, description: None,
invite_link: None, invite_link: None,

View file

@ -332,6 +332,7 @@ mod test {
kind: UpdateKind::Message(Message { kind: UpdateKind::Message(Message {
via_bot: None, via_bot: None,
id: MessageId(6557), id: MessageId(6557),
thread_id: None,
date, date,
chat: Chat { chat: Chat {
id: ChatId(218_485_655), id: ChatId(218_485_655),
@ -342,6 +343,7 @@ mod test {
bio: None, bio: None,
has_private_forwards: None, has_private_forwards: None,
has_restricted_voice_and_video_messages: None, has_restricted_voice_and_video_messages: None,
emoji_status_custom_emoji_id: None,
}), }),
photo: None, photo: None,
pinned_message: None, pinned_message: None,
@ -368,6 +370,7 @@ mod test {
reply_markup: None, reply_markup: None,
sender_chat: None, sender_chat: None,
author_signature: None, author_signature: None,
is_topic_message: false,
is_automatic_forward: false, is_automatic_forward: false,
has_protected_content: false, has_protected_content: false,
}), }),