1use serde::{Serialize, Deserialize};
2use uuid::Uuid;
3
4use crate::api::models::AsyncFile;
5
6use super::*;
7
8#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
37pub struct CommandRequest<TData, TMetaData> {
38 pub sync_id: Uuid,
40
41 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 pub attachments: Vec<Attachment>,
49
50 pub from: From,
51
52 pub async_files: Vec<AsyncFile>,
54
55 pub bot_id: Uuid,
57
58 pub proto_version: u16,
60
61 pub entities: Vec<CommandEntities>,
63}
64
65#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
66#[serde(untagged)]
67pub enum Command<TData, TMetaData> {
68 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 pub data: ChatCreatedCommandData,
127
128 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
130 pub metadata: TMetaData,
131
132 pub command_type: CommandType,
134}
135
136#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
137#[serde(tag = "body")]
138pub struct ChatDeletedByUserCommand<TMetaData> {
139 pub data: ChatDeletedByUserCommandData,
141
142 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
144 pub metadata: TMetaData,
145
146 pub command_type: CommandType,
148}
149
150#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
151#[serde(tag = "body")]
152pub struct AddedToChatCommand<TMetaData> {
153 pub data: AddedToChatCommandData,
155
156 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
158 pub metadata: TMetaData,
159
160 pub command_type: CommandType,
162}
163
164#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
165#[serde(tag = "body")]
166pub struct DeletedFromChatCommand<TMetaData> {
167 pub data: DeletedFromChatCommandData,
169
170 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
172 pub metadata: TMetaData,
173
174 pub command_type: CommandType,
176}
177
178#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
179#[serde(tag = "body")]
180pub struct LeftFromChatCommand<TMetaData> {
181 pub data: LeftFromChatCommandData,
183
184 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
186 pub metadata: TMetaData,
187
188 pub command_type: CommandType,
190}
191
192#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
193#[serde(tag = "body")]
194pub struct EventEditCommand<TMetaData> {
195 pub data: EventEditCommandData,
197
198 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
200 pub metadata: TMetaData,
201
202 pub command_type: CommandType,
204}
205
206#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
207#[serde(tag = "body")]
208pub struct SmartappEventCommand<TMetaData> {
209 pub data: SmartappEventCommandData,
211
212 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
214 pub metadata: TMetaData,
215
216 pub command_type: CommandType,
218}
219
220#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
221#[serde(tag = "body")]
222pub struct InternalBotNotificationCommand<TMetaData> {
223 pub data: InternalBotNotificationCommandData,
225
226 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
228 pub metadata: TMetaData,
229
230 pub command_type: CommandType,
232}
233
234#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
235#[serde(tag = "body")]
236pub struct CtsLoginCommand<TMetaData> {
237 pub data: CtsLoginCommandData,
239
240 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
242 pub metadata: TMetaData,
243
244 pub command_type: CommandType,
246}
247
248#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
249#[serde(tag = "body")]
250pub struct CtsLogoutCommand<TMetaData> {
251 pub data: CtsLogoutCommandData,
253
254 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
256 pub metadata: TMetaData,
257
258 pub command_type: CommandType,
260}
261
262#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
263pub struct MessageCommand<TData, TMetaData> {
264 pub body: String,
266
267 #[serde(bound(deserialize = "for<'a> TData: Deserialize<'a>"))]
269 pub data: TData,
270
271 #[serde(bound(deserialize = "for<'a> TMetaData: Deserialize<'a>"))]
273 pub metadata: TMetaData,
274
275 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}