botx_api/bot/models/
command_request.rs

1use serde::{Serialize, Deserialize};
2use uuid::Uuid;
3
4use crate::api::models::AsyncFile;
5
6use super::*;
7
8// #[derive(Debug, Serialize, Deserialize)]
9// pub struct CommandRequest {
10//     /// идентификатор сообщения в системе Express
11//     pub sync_id: Uuid,
12
13//     /// (Default: null) - идентификатор исходного сообщения (сообщения в котором находились элементы интерфейса) в системе Express
14//     pub source_sync_id: Option<Uuid>,
15
16//     pub command: Command,
17
18//     /// вложения, переданные в сообщении. Например: изображения, видео, файлы, ссылки, геолокации, контакты 
19//     pub attachments: Vec<Attachment>,
20
21//     pub from: From,
22
23//     /// метаданные файлов для отложенной обработки 
24//     pub async_files: Vec<AsyncFile>,
25
26//     /// идентификатор бота в системе Express
27//     pub bot_id: Uuid,
28
29//     /// версия протокола (BotX -> Bot) используемая при отправке команды
30//     pub proto_version: u16,
31
32//     /// особые сущности переданные в сообщение. Например: меншны, хэштеги, ссылки, форварды 
33//     pub entities: Vec<CommandEntities>,
34// }
35
36#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
37pub struct CommandRequest<TData, TMetaData> {
38    /// идентификатор сообщения в системе Express
39    pub sync_id: Uuid,
40
41    /// (Default: null) - идентификатор исходного сообщения (сообщения в котором находились элементы интерфейса) в системе Express
42    pub source_sync_id: Option<Uuid>,
43
44    #[serde(bound(deserialize = "for<'a> Command<TData, TMetaData>: Deserialize<'a>"))]
45    pub command: Command<TData, TMetaData>,
46
47    /// вложения, переданные в сообщении. Например: изображения, видео, файлы, ссылки, геолокации, контакты 
48    pub attachments: Vec<Attachment>,
49
50    pub from: From,
51
52    /// метаданные файлов для отложенной обработки 
53    pub async_files: Vec<AsyncFile>,
54
55    /// идентификатор бота в системе Express
56    pub bot_id: Uuid,
57
58    /// версия протокола (BotX -> Bot) используемая при отправке команды
59    pub proto_version: u16,
60
61    /// особые сущности переданные в сообщение. Например: меншны, хэштеги, ссылки, форварды 
62    pub entities: Vec<CommandEntities>,
63}
64
65#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
66#[serde(untagged)]
67pub enum Command<TData, TMetaData> {
68    /// Событие отправляется при создании чата пользователем
69    #[serde(rename(serialize = "system:chat_created", deserialize = "system:chat_created"))]
70    #[serde(bound(deserialize = "for<'a> ChatCreatedCommand<TMetaData>: Deserialize<'a>"))]
71    ChatCreated(ChatCreatedCommand<TMetaData>),
72
73    /// Событие отправляется при удалении чата пользователем
74    #[serde(rename(serialize = "system:system:chat_deleted_by_user", deserialize = "system:system:chat_deleted_by_user"))]
75    #[serde(bound(deserialize = "for<'a> ChatDeletedByUserCommand<TMetaData>: Deserialize<'a>"))]
76    ChatDeletedByUser(ChatDeletedByUserCommand<TMetaData>),
77
78    /// Событие отправляется при добавление мемберов в чат
79    #[serde(rename(serialize = "system:added_to_chat", deserialize = "system:added_to_chat"))]
80    #[serde(bound(deserialize = "for<'a> AddedToChatCommand<TMetaData>: Deserialize<'a>"))]
81    AddedToChat(AddedToChatCommand<TMetaData>),
82
83    /// Событие отправляется при удалении администратором участников чата
84    #[serde(rename(serialize = "system:deleted_from_chat", deserialize = "system:deleted_from_chat"))]
85    #[serde(bound(deserialize = "for<'a> DeletedFromChatCommand<TMetaData>: Deserialize<'a>"))]
86    DeletedFromChat(DeletedFromChatCommand<TMetaData>),
87
88    /// Событие отправляется при выходе участников из чата
89    #[serde(rename(serialize = "system:left_from_chat", deserialize = "system:left_from_chat"))]
90    #[serde(bound(deserialize = "for<'a> LeftFromChatCommand<TMetaData>: Deserialize<'a>"))]
91    LeftFromChat(LeftFromChatCommand<TMetaData>),
92
93    /// Событие отправляется при редактировании сообщения пользователем
94    #[serde(rename(serialize = "system:event_edit", deserialize = "system:event_edit"))]
95    #[serde(bound(deserialize = "for<'a> EventEditCommand<TMetaData>: Deserialize<'a>"))]
96    EventEdit(EventEditCommand<TMetaData>),
97
98    /// Событие отправляется клиентом при взаимодействии со smartapp приложением
99    #[serde(rename(serialize = "system:smartapp_event", deserialize = "system:smartapp_event"))]
100    #[serde(bound(deserialize = "for<'a> SmartappEventCommand<TMetaData>: Deserialize<'a>"))]
101    SmartappEvent(SmartappEventCommand<TMetaData>),
102
103    /// Событие отправляется ботом при взаимодействие с другими ботами
104    #[serde(rename(serialize = "system:internal_bot_notification", deserialize = "system:internal_bot_notification"))]
105    #[serde(bound(deserialize = "for<'a> InternalBotNotificationCommand<TMetaData>: Deserialize<'a>"))]
106    InternalBotNotification(InternalBotNotificationCommand<TMetaData>),
107
108    /// Событие отправляется при успешном логине пользователя на CTS
109    #[serde(rename(serialize = "system:cts_login", deserialize = "system:cts_login"))]
110    #[serde(bound(deserialize = "for<'a> CtsLoginCommand<TMetaData>: Deserialize<'a>"))]
111    CtsLogin(CtsLoginCommand<TMetaData>),
112
113    /// Событие отправляется при успешном выходе пользователя с CTS
114    #[serde(rename(serialize = "system:cts_logout", deserialize = "system:cts_logout"))]
115    #[serde(bound(deserialize = "for<'a> CtsLogoutCommand<TMetaData>: Deserialize<'a>"))]
116    CtsLogout(CtsLogoutCommand<TMetaData>),
117
118    #[serde(bound(deserialize = "for<'a> MessageCommand<TData, TMetaData>: Deserialize<'a>"))]
119    Message(MessageCommand<TData, TMetaData>),
120}
121
122#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
123#[serde(tag = "body")]
124pub struct ChatCreatedCommand<TMetaData> {
125    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
126    pub data: ChatCreatedCommandData,
127
128    /// метаданные заложенные в объекте сообщения от бота
129    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
130    pub metadata: TMetaData,
131
132    /// тип команды (user|system)
133    pub command_type: CommandType,
134}
135
136#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
137#[serde(tag = "body")]
138pub struct ChatDeletedByUserCommand<TMetaData> {
139    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
140    pub data: ChatDeletedByUserCommandData,
141
142    /// метаданные заложенные в объекте сообщения от бота
143    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
144    pub metadata: TMetaData,
145
146    /// тип команды (user|system)
147    pub command_type: CommandType,
148}
149
150#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
151#[serde(tag = "body")]
152pub struct AddedToChatCommand<TMetaData> {
153    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
154    pub data: AddedToChatCommandData,
155
156    /// метаданные заложенные в объекте сообщения от бота
157    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
158    pub metadata: TMetaData,
159
160    /// тип команды (user|system)
161    pub command_type: CommandType,
162}
163
164#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
165#[serde(tag = "body")]
166pub struct DeletedFromChatCommand<TMetaData> {
167    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
168    pub data: DeletedFromChatCommandData,
169
170    /// метаданные заложенные в объекте сообщения от бота
171    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
172    pub metadata: TMetaData,
173
174    /// тип команды (user|system)
175    pub command_type: CommandType,
176}
177
178#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
179#[serde(tag = "body")]
180pub struct LeftFromChatCommand<TMetaData> {
181    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
182    pub data: LeftFromChatCommandData,
183
184    /// метаданные заложенные в объекте сообщения от бота
185    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
186    pub metadata: TMetaData,
187
188    /// тип команды (user|system)
189    pub command_type: CommandType,
190}
191
192#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
193#[serde(tag = "body")]
194pub struct EventEditCommand<TMetaData> {
195    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
196    pub data: EventEditCommandData,
197
198    /// метаданные заложенные в объекте сообщения от бота
199    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
200    pub metadata: TMetaData,
201
202    /// тип команды (user|system)
203    pub command_type: CommandType,
204}
205
206#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
207#[serde(tag = "body")]
208pub struct SmartappEventCommand<TMetaData> {
209    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
210    pub data: SmartappEventCommandData,
211
212    /// метаданные заложенные в объекте сообщения от бота
213    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
214    pub metadata: TMetaData,
215
216    /// тип команды (user|system)
217    pub command_type: CommandType,
218}
219
220#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
221#[serde(tag = "body")]
222pub struct InternalBotNotificationCommand<TMetaData> {
223    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
224    pub data: InternalBotNotificationCommandData,
225
226    /// метаданные заложенные в объекте сообщения от бота
227    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
228    pub metadata: TMetaData,
229
230    /// тип команды (user|system)
231    pub command_type: CommandType,
232}
233
234#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
235#[serde(tag = "body")]
236pub struct CtsLoginCommand<TMetaData> {
237    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
238    pub data: CtsLoginCommandData,
239
240    /// метаданные заложенные в объекте сообщения от бота
241    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
242    pub metadata: TMetaData,
243
244    /// тип команды (user|system)
245    pub command_type: CommandType,
246}
247
248#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
249#[serde(tag = "body")]
250pub struct CtsLogoutCommand<TMetaData> {
251    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
252    pub data: CtsLogoutCommandData,
253
254    /// метаданные заложенные в объекте сообщения от бота
255    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
256    pub metadata: TMetaData,
257
258    /// тип команды (user|system)
259    pub command_type: CommandType,
260}
261
262#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
263pub struct MessageCommand<TData, TMetaData> {
264    /// тело команды
265    pub body: String,
266
267    /// данные команды полученные на основе введеных пользователем данных через элементы UI или при нажатие на кнопку
268    #[serde(bound(deserialize = "for<'a> TData: Deserialize<'a>"))]
269    pub data: TData,
270
271    /// метаданные заложенные в объекте сообщения от бота
272    #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
273    pub metadata: TMetaData,
274
275    /// тип команды (user|system)
276    pub command_type: CommandType,
277}
278
279#[cfg(test)]
280mod tests {
281    use std::str::FromStr;
282
283    use crate::api::models::{Mention, MentionType, MentionData, PersonMentionData};
284
285    use super::*;
286
287    #[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
288    struct MetaData {
289        pub account_id: u32,
290    }
291
292    #[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
293    struct EmptyData { }
294
295    #[derive(Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
296    struct EmptyMetaData { }
297
298    #[test]
299    fn deserialize_message_command_ok() {
300        let request_str = r#"{
301            "sync_id": "a465f0f3-1354-491c-8f11-f400164295cb",
302            "command": {
303              "body": "/doit #6",
304              "command_type": "user",
305              "data": {},
306              "metadata": {"account_id": 94}
307            },
308            "attachments": [
309              {
310                "type": "image",
311                "data": {
312                  "content": "data:image/jpg;base64,eDnXAc1FEUB0VFEFctII3lRlRBcetROeFfduPmXxE/8=",
313                  "file_name": "image.jpg"
314                }
315              }
316            ],
317            "async_files": [],
318            "from": {
319              "user_huid": "ab103983-6001-44e9-889e-d55feb295494",
320              "group_chat_id": "8dada2c8-67a6-4434-9dec-570d244e78ee",
321              "ad_login": "example_login",
322              "ad_domain": "example.com",
323              "username": "Bob",
324              "is_admin": true,
325              "is_creator": true,
326              "chat_type": "group_chat",
327              "host": "cts.ccteam.ru",
328              "app_version": "1.21.11",
329              "device": "Chrome 92.0",
330              "device_software": "macOS 10.15.7",
331              "device_meta": {
332                "permissions": {"microphone": true, "notifications": true},
333                "pushes": false,
334                "timezone": "Europe/Samara"
335              },
336              "platform": "web",
337              "locale": "en",
338              "manufacturer": "Google",
339              "platform_package_id": "ru.unlimitedtech.express"  
340            },
341            "bot_id": "dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4",
342            "proto_version": 4,
343            "entities": [
344              {
345                "type": "mention",
346                "data": {
347                  "mention_type": "contact",
348                  "mention_id": "c06a96fa-7881-0bb6-0e0b-0af72fe3683f",
349                  "mention_data": {
350                    "user_huid": "ab103983-6001-44e9-889e-d55feb295494",
351                    "name": "Вася Иванов",
352                    "conn_type": "cts"
353                  }
354                }
355              }
356            ]
357          }"#;
358
359        let command_request: CommandRequest::<EmptyData, MetaData> = serde_json::from_str(request_str).unwrap();
360
361        let expected_command_request = CommandRequest::<EmptyData, MetaData> {
362            sync_id: uuid::Uuid::from_str("a465f0f3-1354-491c-8f11-f400164295cb").unwrap(),
363            source_sync_id: None,
364            command: Command::Message(MessageCommand {
365                body: "/doit #6".to_string(),
366                command_type: CommandType::User,
367                data: EmptyData{},
368                metadata: MetaData {
369                    account_id: 94,
370                },
371            }),
372            attachments: vec![Attachment::Image(ImageAttachment {
373                content: "data:image/jpg;base64,eDnXAc1FEUB0VFEFctII3lRlRBcetROeFfduPmXxE/8=".to_string(),
374                file_name: Some("image.jpg".to_string()),
375            })],
376            async_files: vec![],
377            from: From {
378                user_huid: Some(uuid::Uuid::from_str("ab103983-6001-44e9-889e-d55feb295494").unwrap()),
379                group_chat_id: Some(uuid::Uuid::from_str("8dada2c8-67a6-4434-9dec-570d244e78ee").unwrap()),
380                ad_login: Some("example_login".to_string()),
381                ad_domain: Some("example.com".to_string()),
382                username: Some("Bob".to_string()),
383                is_admin: Some(true),
384                is_creator: Some(true),
385                chat_type: Some(ChatType::GroupChat),
386                host: Some("cts.ccteam.ru".to_string()),
387                app_version: Some("1.21.11".to_string()),
388                device: Some("Chrome 92.0".to_string()),
389                device_software: Some("macOS 10.15.7".to_string()),
390                device_meta: Some(DeviceMeta {
391                    permissions: Some(Permissions {
392                        microphone: Some(true),
393                        notifications: Some(true),
394                        contacts: None,
395                        storage: None,
396                    }),
397                    pushes: Some(false),
398                    timezone: Some("Europe/Samara".to_string())
399                }),
400                platform: Some(Platform::Web),
401                locale: Some("en".to_string()),
402                manufacturer: Some("Google".to_string()),
403                platform_package_id: Some("ru.unlimitedtech.express".to_string()),
404            },
405            bot_id: uuid::Uuid::from_str("dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4").unwrap(),
406            proto_version: 4,
407            entities: vec![CommandEntities::Mention(Mention {
408              mention_type: Some(MentionType::Contact),
409              mention_id: uuid::Uuid::from_str("c06a96fa-7881-0bb6-0e0b-0af72fe3683f").unwrap(),
410              mention_data: Some(MentionData::PersonMention(PersonMentionData {
411                user_huid: uuid::Uuid::from_str("ab103983-6001-44e9-889e-d55feb295494").unwrap(),
412                name: "Вася Иванов".to_string(),
413                conn_type: "cts".to_string(),
414              })),
415            })],
416        };
417
418        assert_eq!(command_request, expected_command_request)
419    }
420
421    #[test]
422    fn deserialize_chat_created_command_ok() {
423        let request_str = r#"{
424            "sync_id": "a465f0f3-1354-491c-8f11-f400164295cb",
425            "command": {
426              "body": "system:chat_created",
427              "data": {
428                "group_chat_id": "8dada2c8-67a6-4434-9dec-570d244e78ee",
429                "chat_type": "group_chat",
430                "name": "Meeting Room",
431                "creator": "ab103983-6001-44e9-889e-d55feb295494",
432                "members": [
433                  {
434                    "huid": "ab103983-6001-44e9-889e-d55feb295494",
435                    "name": null,
436                    "user_kind": "user",
437                    "admin": true
438                  },
439                {
440                    "huid": "dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4",
441                    "name": "Funny Bot",
442                    "user_kind": "botx",
443                    "admin": false
444                  }
445                ]
446              },
447              "command_type": "system",
448              "metadata": {}
449            },
450            "async_files": [],
451            "attachments": [],
452            "entities": [],
453            "from": {
454              "user_huid": null,
455              "group_chat_id": "8dada2c8-67a6-4434-9dec-570d244e78ee",
456              "ad_login": null,
457              "ad_domain": null,
458              "username": null,
459              "chat_type": "group_chat",
460              "manufacturer": null,
461              "device": null,
462              "device_software": null,
463              "device_meta": {},
464              "platform": null,
465              "platform_package_id": null,
466              "is_admin": null,
467              "is_creator": null,
468              "app_version": null,
469              "locale": "en",
470              "host": "cts.ccteam.ru"
471            },
472            "bot_id": "dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4",
473            "proto_version": 4,
474            "source_sync_id": null
475          }"#;
476
477        let command_request: CommandRequest::<EmptyData, EmptyMetaData> = serde_json::from_str(request_str).unwrap();
478
479        let expected_command_request = CommandRequest::<EmptyData, EmptyMetaData> {
480            sync_id: uuid::Uuid::from_str("a465f0f3-1354-491c-8f11-f400164295cb").unwrap(),
481            command: Command::ChatCreated(ChatCreatedCommand {
482                data: ChatCreatedCommandData {
483                    group_chat_id: uuid::Uuid::from_str("8dada2c8-67a6-4434-9dec-570d244e78ee").unwrap(),
484                    chat_type: ChatType::GroupChat,
485                    name: "Meeting Room".to_string(),
486                    creator: uuid::Uuid::from_str("ab103983-6001-44e9-889e-d55feb295494").unwrap(),
487                    members: vec![
488                        ChatCreatedCommandMembers {
489                            huid: uuid::Uuid::from_str("ab103983-6001-44e9-889e-d55feb295494").unwrap(),
490                            name: None,
491                            user_kind: UserKind::User,
492                            admin: true
493                        },
494                        ChatCreatedCommandMembers {
495                            huid: uuid::Uuid::from_str("dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4").unwrap(),
496                            name: Some("Funny Bot".to_string()),
497                            user_kind: UserKind::Botx,
498                            admin: false
499                        }
500                    ]
501                },
502                command_type: CommandType::System,
503                metadata: EmptyMetaData { },
504            }),
505            async_files: vec![],
506            attachments: vec![],
507            entities: vec![],
508            from: From {
509                user_huid: None,
510                group_chat_id: Some(uuid::Uuid::from_str("8dada2c8-67a6-4434-9dec-570d244e78ee").unwrap()),
511                ad_login: None,
512                ad_domain: None,
513                username: None,
514                chat_type: Some(ChatType::GroupChat),
515                manufacturer: None,
516                device: None,
517                device_software: None,
518                device_meta: Some(DeviceMeta {
519                    pushes: None,
520                    timezone: None,
521                    permissions: None
522                }),
523                platform: None,
524                platform_package_id: None,
525                is_admin: None,
526                is_creator: None,
527                app_version: None,
528                locale: Some("en".to_string()),
529                host: Some("cts.ccteam.ru".to_string()),
530            },
531            bot_id: uuid::Uuid::from_str("dcfa5a7c-7cc4-4c89-b6c0-80325604f9f4").unwrap(),
532            proto_version: 4,
533            source_sync_id: None,
534        };
535    
536        assert_eq!(command_request, expected_command_request)
537    }
538}