Skip to main content

botrs/
manage.rs

1//! Management event functionality for QQ Bot
2//!
3//! This module provides structures and implementations for handling management events,
4//! including group and C2C (client-to-client) management operations.
5
6use crate::api::BotApi;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::str::FromStr;
10
11/// Group management event structure
12#[derive(Debug, Clone, Serialize)]
13pub struct GroupManageEvent {
14    /// API client reference
15    #[serde(skip)]
16    api: BotApi,
17    /// Event ID
18    pub event_id: Option<String>,
19    /// Timestamp of the event
20    pub timestamp: Option<u64>,
21    /// Group OpenID
22    pub group_openid: Option<String>,
23    /// Operator member OpenID
24    pub op_member_openid: Option<String>,
25}
26
27impl GroupManageEvent {
28    /// Create a new GroupManageEvent instance
29    ///
30    /// # Arguments
31    ///
32    /// * `api` - The Bot API client
33    /// * `event_id` - Optional event ID
34    /// * `data` - Management event data from the gateway
35    pub fn new(
36        api: BotApi,
37        event_id: Option<String>,
38        data: &HashMap<String, serde_json::Value>,
39    ) -> Self {
40        Self {
41            api,
42            event_id,
43            timestamp: data.get("timestamp").and_then(|v| v.as_u64()),
44            group_openid: data
45                .get("group_openid")
46                .and_then(|v| v.as_str())
47                .map(String::from),
48            op_member_openid: data
49                .get("op_member_openid")
50                .and_then(|v| v.as_str())
51                .map(String::from),
52        }
53    }
54
55    /// Get the API client reference
56    pub fn api(&self) -> &BotApi {
57        &self.api
58    }
59
60    /// Get the event timestamp as a formatted string
61    pub fn formatted_timestamp(&self) -> Option<String> {
62        self.timestamp.map(|ts| {
63            let datetime = chrono::DateTime::from_timestamp(ts as i64, 0).unwrap_or_default();
64            datetime.format("%Y-%m-%d %H:%M:%S").to_string()
65        })
66    }
67}
68
69impl std::fmt::Display for GroupManageEvent {
70    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        write!(
72            f,
73            "GroupManageEvent {{ event_id: {:?}, timestamp: {:?}, group_openid: {:?}, op_member_openid: {:?} }}",
74            self.event_id, self.timestamp, self.group_openid, self.op_member_openid
75        )
76    }
77}
78
79/// C2C (Client-to-Client) management event structure
80#[derive(Debug, Clone, Serialize)]
81pub struct C2CManageEvent {
82    /// API client reference
83    #[serde(skip)]
84    api: BotApi,
85    /// Event ID
86    pub event_id: Option<String>,
87    /// Timestamp of the event
88    pub timestamp: Option<u64>,
89    /// User OpenID
90    pub openid: Option<String>,
91    /// User nickname
92    pub nick: Option<String>,
93    /// User avatar URL
94    pub avatar: Option<String>,
95}
96
97pub type C2CFriendData = C2CManageEvent;
98
99impl C2CManageEvent {
100    /// Create a new C2CManageEvent instance
101    ///
102    /// # Arguments
103    ///
104    /// * `api` - The Bot API client
105    /// * `event_id` - Optional event ID
106    /// * `data` - Management event data from the gateway
107    pub fn new(
108        api: BotApi,
109        event_id: Option<String>,
110        data: &HashMap<String, serde_json::Value>,
111    ) -> Self {
112        Self {
113            api,
114            event_id,
115            timestamp: data.get("timestamp").and_then(|v| v.as_u64()),
116            openid: data
117                .get("openid")
118                .and_then(|v| v.as_str())
119                .map(String::from),
120            nick: data.get("nick").and_then(|v| v.as_str()).map(String::from),
121            avatar: data
122                .get("avatar")
123                .and_then(|v| v.as_str())
124                .map(String::from),
125        }
126    }
127
128    /// Get the API client reference
129    pub fn api(&self) -> &BotApi {
130        &self.api
131    }
132
133    /// Get the event timestamp as a formatted string
134    pub fn formatted_timestamp(&self) -> Option<String> {
135        self.timestamp.map(|ts| {
136            let datetime = chrono::DateTime::from_timestamp(ts as i64, 0).unwrap_or_default();
137            datetime.format("%Y-%m-%d %H:%M:%S").to_string()
138        })
139    }
140}
141
142impl std::fmt::Display for C2CManageEvent {
143    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144        write!(
145            f,
146            "C2CManageEvent {{ event_id: {:?}, timestamp: {:?}, openid: {:?}, nick: {:?}, avatar: {:?} }}",
147            self.event_id, self.timestamp, self.openid, self.nick, self.avatar
148        )
149    }
150}
151
152/// Event emitted when a user enters AIO.
153#[derive(Debug, Clone, Serialize, Deserialize, Default)]
154pub struct EnterAioEvent {
155    /// User OpenID
156    pub user_openid: Option<String>,
157    /// Source from which the user entered AIO
158    pub from_source: Option<String>,
159    /// Event ID
160    #[serde(skip)]
161    pub event_id: Option<String>,
162}
163
164pub type EnterAIO = EnterAioEvent;
165
166impl EnterAioEvent {
167    /// Creates a new EnterAioEvent from gateway data.
168    pub fn new(event_id: Option<String>, data: &serde_json::Value) -> Self {
169        Self {
170            user_openid: data
171                .get("user_openid")
172                .and_then(|v| v.as_str())
173                .map(String::from),
174            from_source: data
175                .get("from_source")
176                .and_then(|v| v.as_str())
177                .map(String::from),
178            event_id,
179        }
180    }
181}
182
183/// Subscribe message status event data.
184#[derive(Debug, Clone, Serialize, Deserialize, Default)]
185pub struct SubscribeMessageStatusData {
186    /// Group OpenID, present for group subscription messages
187    pub group_openid: Option<String>,
188    /// User OpenID, present for C2C subscription messages
189    pub openid: Option<String>,
190    /// Template authorization results
191    #[serde(default)]
192    pub result: Vec<SubscribeMsgTemplateResult>,
193    /// Event ID
194    #[serde(skip)]
195    pub event_id: Option<String>,
196}
197
198impl SubscribeMessageStatusData {
199    /// Creates a subscribe status event from gateway data.
200    pub fn new(event_id: Option<String>, data: &serde_json::Value) -> Self {
201        let mut event = serde_json::from_value::<Self>(data.clone()).unwrap_or_default();
202        event.event_id = event_id;
203        event
204    }
205}
206
207/// Subscribe template authorization result.
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
209pub struct SubscribeMsgTemplateResult {
210    /// Official template ID
211    pub template_id: Option<i32>,
212    /// Custom template ID
213    pub custom_template_id: Option<String>,
214    /// Authorization operation, 1 allow and 2 reject
215    pub op: Option<u32>,
216    /// Subscription ID
217    pub subscribe_id: Option<String>,
218    /// Status update timestamp
219    pub update_ts: Option<u64>,
220}
221
222/// Management event type enumeration
223#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
224pub enum ManageEventType {
225    /// Group add robot event
226    GroupAddRobot,
227    /// Group delete robot event
228    GroupDelRobot,
229    /// Group message reject event
230    GroupMsgReject,
231    /// Group message receive event
232    GroupMsgReceive,
233    /// Friend add event
234    FriendAdd,
235    /// Friend delete event
236    FriendDel,
237    /// C2C message reject event
238    C2CMsgReject,
239    /// C2C message receive event
240    C2CMsgReceive,
241}
242
243impl ManageEventType {
244    /// Convert event type to string
245    pub fn as_str(&self) -> &'static str {
246        match self {
247            Self::GroupAddRobot => "group_add_robot",
248            Self::GroupDelRobot => "group_del_robot",
249            Self::GroupMsgReject => "group_msg_reject",
250            Self::GroupMsgReceive => "group_msg_receive",
251            Self::FriendAdd => "friend_add",
252            Self::FriendDel => "friend_del",
253            Self::C2CMsgReject => "c2c_msg_reject",
254            Self::C2CMsgReceive => "c2c_msg_receive",
255        }
256    }
257
258    /// Check if this is a group-related event
259    pub fn is_group_event(&self) -> bool {
260        matches!(
261            self,
262            Self::GroupAddRobot
263                | Self::GroupDelRobot
264                | Self::GroupMsgReject
265                | Self::GroupMsgReceive
266        )
267    }
268
269    /// Check if this is a C2C-related event
270    pub fn is_c2c_event(&self) -> bool {
271        matches!(
272            self,
273            Self::FriendAdd | Self::FriendDel | Self::C2CMsgReject | Self::C2CMsgReceive
274        )
275    }
276}
277
278impl FromStr for ManageEventType {
279    type Err = ();
280
281    fn from_str(s: &str) -> Result<Self, Self::Err> {
282        match s {
283            "group_add_robot" => Ok(Self::GroupAddRobot),
284            "group_del_robot" => Ok(Self::GroupDelRobot),
285            "group_msg_reject" => Ok(Self::GroupMsgReject),
286            "group_msg_receive" => Ok(Self::GroupMsgReceive),
287            "friend_add" => Ok(Self::FriendAdd),
288            "friend_del" => Ok(Self::FriendDel),
289            "c2c_msg_reject" => Ok(Self::C2CMsgReject),
290            "c2c_msg_receive" => Ok(Self::C2CMsgReceive),
291            _ => Err(()),
292        }
293    }
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_manage_event_type_from_str() {
302        assert_eq!(
303            "group_add_robot".parse::<ManageEventType>(),
304            Ok(ManageEventType::GroupAddRobot)
305        );
306        assert_eq!(
307            "friend_add".parse::<ManageEventType>(),
308            Ok(ManageEventType::FriendAdd)
309        );
310        assert_eq!("invalid".parse::<ManageEventType>(), Err(()));
311    }
312
313    #[test]
314    fn test_manage_event_type_as_str() {
315        assert_eq!(ManageEventType::GroupAddRobot.as_str(), "group_add_robot");
316        assert_eq!(ManageEventType::FriendAdd.as_str(), "friend_add");
317    }
318
319    #[test]
320    fn test_is_group_event() {
321        assert!(ManageEventType::GroupAddRobot.is_group_event());
322        assert!(!ManageEventType::FriendAdd.is_group_event());
323    }
324
325    #[test]
326    fn test_is_c2c_event() {
327        assert!(ManageEventType::FriendAdd.is_c2c_event());
328        assert!(!ManageEventType::GroupAddRobot.is_c2c_event());
329    }
330}