tg_flows/types/
chat_id.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::UserId;
4
5/// Identifier of a chat.
6///
7/// Note that "a chat" here means any of group, supergroup, channel or user PM.
8#[derive(
9    Clone,
10    Copy,
11    Debug,
12    derive_more::Display,
13    PartialEq,
14    Eq,
15    PartialOrd,
16    Ord,
17    Hash,
18    Serialize,
19    Deserialize,
20)]
21#[serde(transparent)]
22pub struct ChatId(pub i64);
23
24/// Bare chat id as represented in MTProto API.
25///
26/// In MTProto API peer ids can have different types, for example `User(1)` and
27/// `Group(1)` are different chats. For bot API these peer ids are encoded in
28/// such a way that they can be stored in a simple integer (ie bot API chat ids
29/// have the type encoded in them). This type exposes the "bare" "peer id" of a
30/// chat.
31///
32/// `BareChatId` can be created by [`ChatId::to_bare`].
33#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
34pub(crate) enum BareChatId {
35    User(UserId),
36    Group(u64),
37    /// Note: supergroups are considered channels.
38    Channel(u64),
39}
40
41impl ChatId {
42    /// Returns `true` if this is an id of a user.
43    #[must_use]
44    pub fn is_user(self) -> bool {
45        matches!(self.to_bare(), BareChatId::User(_))
46    }
47
48    /// Returns `true` if this is an id of a group.
49    ///
50    /// Note: supergroup is **not** considered a group.
51    #[must_use]
52    pub fn is_group(self) -> bool {
53        matches!(self.to_bare(), BareChatId::Group(_))
54    }
55
56    /// Returns `true` if this is an id of a channel.
57    #[must_use]
58    pub fn is_channel_or_supergroup(self) -> bool {
59        matches!(self.to_bare(), BareChatId::Channel(_))
60    }
61
62    /// Converts this id to "bare" MTProto peer id.
63    ///
64    /// See [`BareChatId`] for more.
65    pub(crate) fn to_bare(self) -> BareChatId {
66        use BareChatId::*;
67
68        match self.0 {
69            id @ MIN_MARKED_CHAT_ID..=MAX_MARKED_CHAT_ID => Group(-id as _),
70            id @ MIN_MARKED_CHANNEL_ID..=MAX_MARKED_CHANNEL_ID => {
71                Channel((MAX_MARKED_CHANNEL_ID - id) as _)
72            }
73            id @ MIN_USER_ID..=MAX_USER_ID => User(UserId(id as _)),
74            id => panic!("malformed chat id: {id}"),
75        }
76    }
77}
78
79impl From<UserId> for ChatId {
80    fn from(UserId(id): UserId) -> Self {
81        Self(id as _)
82    }
83}
84
85impl BareChatId {
86    /// Converts bare chat id back to normal bot API [`ChatId`].
87    #[allow(unused)]
88    pub(crate) fn to_bot_api(self) -> ChatId {
89        use BareChatId::*;
90
91        match self {
92            User(UserId(id)) => ChatId(id as _),
93            Group(id) => ChatId(-(id as i64)),
94            Channel(id) => ChatId(MAX_MARKED_CHANNEL_ID - (id as i64)),
95        }
96    }
97}
98
99// https://github.com/mtcute/mtcute/blob/6933ecc3f82dd2e9100f52b0afec128af564713b/packages/core/src/utils/peer-utils.ts#L4
100const MIN_MARKED_CHANNEL_ID: i64 = -1997852516352;
101const MAX_MARKED_CHANNEL_ID: i64 = -1000000000000;
102const MIN_MARKED_CHAT_ID: i64 = MAX_MARKED_CHANNEL_ID + 1;
103const MAX_MARKED_CHAT_ID: i64 = MIN_USER_ID - 1;
104const MIN_USER_ID: i64 = 0;
105const MAX_USER_ID: i64 = (1 << 40) - 1;
106
107#[cfg(test)]
108mod tests {
109    use serde::{Deserialize, Serialize};
110
111    use crate::types::{BareChatId, ChatId, UserId};
112
113    /// Test that `ChatId` is serialized as the underlying integer
114    #[test]
115    fn deser() {
116        let chat_id = S {
117            chat_id: ChatId(0xAA),
118        };
119        let json = r#"{"chat_id":170}"#;
120
121        #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
122        struct S {
123            chat_id: ChatId,
124        }
125
126        assert_eq!(serde_json::to_string(&chat_id).unwrap(), json);
127        assert_eq!(chat_id, serde_json::from_str(json).unwrap());
128    }
129
130    #[test]
131    fn chonky_user_id_to_bare() {
132        assert!(matches!(
133            ChatId(5298363099).to_bare(),
134            BareChatId::User(UserId(5298363099))
135        ));
136    }
137
138    #[test]
139    fn to_bare_to_bot_api_identity() {
140        fn assert_identity(x: u64) {
141            use BareChatId::*;
142
143            assert_eq!(User(UserId(x)), User(UserId(x)).to_bot_api().to_bare());
144            assert_eq!(Group(x), Group(x).to_bot_api().to_bare());
145            assert_eq!(Channel(x), Channel(x).to_bot_api().to_bare());
146        }
147
148        // Somewhat random numbers
149        let ids = [
150            1,
151            4,
152            17,
153            34,
154            51,
155            777000,
156            1000000,
157            617136926,
158            1666111087,
159            1 << 20,
160            (1 << 35) | 123456,
161        ];
162
163        // rust 2021 when :(
164        ids.iter().copied().for_each(assert_identity);
165    }
166
167    #[test]
168    fn display() {
169        assert_eq!(ChatId(1).to_string(), "1");
170    }
171}