1use serde::{Deserialize, Serialize};
28use serde_json::Value;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum NotificationType {
35 Starting,
37 Succeeded,
39 Failed,
41 Unknown,
43}
44
45impl NotificationType {
46 pub fn is_starting(&self) -> bool {
48 matches!(self, Self::Starting)
49 }
50
51 pub fn is_succeeded(&self) -> bool {
53 matches!(self, Self::Succeeded)
54 }
55
56 pub fn is_failed(&self) -> bool {
58 matches!(self, Self::Failed)
59 }
60
61 pub fn is_complete(&self) -> bool {
63 matches!(self, Self::Succeeded | Self::Failed)
64 }
65}
66
67impl From<&str> for NotificationType {
68 fn from(s: &str) -> Self {
69 if s.ends_with(".Starting") {
70 NotificationType::Starting
71 } else if s.ends_with(".Succeeded") {
72 NotificationType::Succeeded
73 } else if s.ends_with(".Failed") {
74 NotificationType::Failed
75 } else {
76 NotificationType::Unknown
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct ResourceLoadingDetail {
86 pub res_id: i64,
88 pub hash: String,
90 pub path: String,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct ControllerActionDetail {
97 pub ctrl_id: i64,
99 pub uuid: String,
101 pub action: String,
103 #[serde(default)]
105 pub param: Value,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct TaskerTaskDetail {
111 pub task_id: i64,
113 pub entry: String,
115 #[serde(default)]
117 pub uuid: String,
118 #[serde(default)]
120 pub hash: String,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct NextListItem {
126 pub name: String,
128 #[serde(default)]
130 pub jump_back: bool,
131 #[serde(default)]
133 pub anchor: bool,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct NodeNextListDetail {
139 pub task_id: i64,
141 pub name: String,
143 #[serde(default)]
145 pub list: Vec<NextListItem>,
146 #[serde(default)]
148 pub focus: Value,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct NodeRecognitionDetail {
154 pub task_id: i64,
156 pub reco_id: i64,
158 pub name: String,
160 #[serde(default)]
162 pub focus: Value,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct NodeActionDetail {
168 pub task_id: i64,
170 pub action_id: i64,
172 pub name: String,
174 #[serde(default)]
176 pub focus: Value,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct NodePipelineNodeDetail {
182 pub task_id: i64,
184 pub node_id: i64,
186 pub name: String,
188 #[serde(default)]
190 pub focus: Value,
191}
192
193pub mod msg {
197 pub const RESOURCE_LOADING_STARTING: &str = "Resource.Loading.Starting";
199 pub const RESOURCE_LOADING_SUCCEEDED: &str = "Resource.Loading.Succeeded";
200 pub const RESOURCE_LOADING_FAILED: &str = "Resource.Loading.Failed";
201
202 pub const CONTROLLER_ACTION_STARTING: &str = "Controller.Action.Starting";
204 pub const CONTROLLER_ACTION_SUCCEEDED: &str = "Controller.Action.Succeeded";
205 pub const CONTROLLER_ACTION_FAILED: &str = "Controller.Action.Failed";
206
207 pub const TASKER_TASK_STARTING: &str = "Tasker.Task.Starting";
209 pub const TASKER_TASK_SUCCEEDED: &str = "Tasker.Task.Succeeded";
210 pub const TASKER_TASK_FAILED: &str = "Tasker.Task.Failed";
211
212 pub const NODE_PIPELINE_NODE_STARTING: &str = "Node.PipelineNode.Starting";
214 pub const NODE_PIPELINE_NODE_SUCCEEDED: &str = "Node.PipelineNode.Succeeded";
215 pub const NODE_PIPELINE_NODE_FAILED: &str = "Node.PipelineNode.Failed";
216
217 pub const NODE_RECOGNITION_STARTING: &str = "Node.Recognition.Starting";
219 pub const NODE_RECOGNITION_SUCCEEDED: &str = "Node.Recognition.Succeeded";
220 pub const NODE_RECOGNITION_FAILED: &str = "Node.Recognition.Failed";
221
222 pub const NODE_ACTION_STARTING: &str = "Node.Action.Starting";
224 pub const NODE_ACTION_SUCCEEDED: &str = "Node.Action.Succeeded";
225 pub const NODE_ACTION_FAILED: &str = "Node.Action.Failed";
226
227 pub const NODE_NEXT_LIST_STARTING: &str = "Node.NextList.Starting";
229 pub const NODE_NEXT_LIST_SUCCEEDED: &str = "Node.NextList.Succeeded";
230 pub const NODE_NEXT_LIST_FAILED: &str = "Node.NextList.Failed";
231
232 pub const NODE_RECOGNITION_NODE_STARTING: &str = "Node.RecognitionNode.Starting";
234 pub const NODE_RECOGNITION_NODE_SUCCEEDED: &str = "Node.RecognitionNode.Succeeded";
235 pub const NODE_RECOGNITION_NODE_FAILED: &str = "Node.RecognitionNode.Failed";
236
237 pub const NODE_ACTION_NODE_STARTING: &str = "Node.ActionNode.Starting";
239 pub const NODE_ACTION_NODE_SUCCEEDED: &str = "Node.ActionNode.Succeeded";
240 pub const NODE_ACTION_NODE_FAILED: &str = "Node.ActionNode.Failed";
241}
242
243pub fn parse_type(msg: &str) -> NotificationType {
255 NotificationType::from(msg)
256}
257
258pub fn parse_resource_loading(details: &str) -> Option<ResourceLoadingDetail> {
260 serde_json::from_str(details).ok()
261}
262
263pub fn parse_controller_action(details: &str) -> Option<ControllerActionDetail> {
265 serde_json::from_str(details).ok()
266}
267
268pub fn parse_tasker_task(details: &str) -> Option<TaskerTaskDetail> {
270 serde_json::from_str(details).ok()
271}
272
273pub fn parse_node_next_list(details: &str) -> Option<NodeNextListDetail> {
275 serde_json::from_str(details).ok()
276}
277
278pub fn parse_node_recognition(details: &str) -> Option<NodeRecognitionDetail> {
280 serde_json::from_str(details).ok()
281}
282
283pub fn parse_node_action(details: &str) -> Option<NodeActionDetail> {
285 serde_json::from_str(details).ok()
286}
287
288pub fn parse_node_pipeline_node(details: &str) -> Option<NodePipelineNodeDetail> {
290 serde_json::from_str(details).ok()
291}
292
293#[derive(Debug, Clone)]
297pub enum ContextEvent {
298 NodeNextList(NotificationType, NodeNextListDetail),
299 NodeRecognition(NotificationType, NodeRecognitionDetail),
300 NodeAction(NotificationType, NodeActionDetail),
301 NodePipelineNode(NotificationType, NodePipelineNodeDetail),
302 NodeRecognitionNode(NotificationType, NodePipelineNodeDetail),
303 NodeActionNode(NotificationType, NodePipelineNodeDetail),
304 Unknown(String, Value),
305}
306
307impl ContextEvent {
308 pub fn from_notification(msg: &str, details: &str) -> Option<Self> {
310 let noti_type = NotificationType::from(msg);
311
312 let parse_json = || -> Option<Value> { serde_json::from_str(details).ok() };
313
314 if msg.starts_with("Node.NextList") {
315 let detail = parse_node_next_list(details)?;
316 return Some(ContextEvent::NodeNextList(noti_type, detail));
317 }
318
319 if msg.starts_with("Node.Recognition.") {
320 let detail = parse_node_recognition(details)?;
321 return Some(ContextEvent::NodeRecognition(noti_type, detail));
322 }
323
324 if msg.starts_with("Node.Action.") {
325 let detail = parse_node_action(details)?;
326 return Some(ContextEvent::NodeAction(noti_type, detail));
327 }
328
329 if msg.starts_with("Node.PipelineNode") {
330 let detail = parse_node_pipeline_node(details)?;
331 return Some(ContextEvent::NodePipelineNode(noti_type, detail));
332 }
333
334 if msg.starts_with("Node.RecognitionNode") {
335 let detail = parse_node_pipeline_node(details)?;
336 return Some(ContextEvent::NodeRecognitionNode(noti_type, detail));
337 }
338
339 if msg.starts_with("Node.ActionNode") {
340 let detail = parse_node_pipeline_node(details)?;
341 return Some(ContextEvent::NodeActionNode(noti_type, detail));
342 }
343
344 Some(ContextEvent::Unknown(
345 msg.to_string(),
346 parse_json().unwrap_or(Value::Null),
347 ))
348 }
349}
350
351pub trait ResourceEventHandler: Send + Sync {
355 fn on_resource_loading(&self, _noti_type: NotificationType, _detail: ResourceLoadingDetail) {}
357
358 fn on_unknown(&self, _msg: &str, _details: &Value) {}
360}
361
362pub trait ControllerEventHandler: Send + Sync {
364 fn on_controller_action(&self, _noti_type: NotificationType, _detail: ControllerActionDetail) {}
366
367 fn on_unknown(&self, _msg: &str, _details: &Value) {}
369}
370
371pub trait TaskerEventHandler: Send + Sync {
373 fn on_tasker_task(&self, _noti_type: NotificationType, _detail: TaskerTaskDetail) {}
375
376 fn on_unknown(&self, _msg: &str, _details: &Value) {}
378}
379
380pub trait ContextEventHandler: Send + Sync {
382 fn on_node_next_list(&self, _noti_type: NotificationType, _detail: NodeNextListDetail) {}
384
385 fn on_node_recognition(&self, _noti_type: NotificationType, _detail: NodeRecognitionDetail) {}
387
388 fn on_node_action(&self, _noti_type: NotificationType, _detail: NodeActionDetail) {}
390
391 fn on_node_pipeline_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {
393 }
394
395 fn on_node_recognition_node(
397 &self,
398 _noti_type: NotificationType,
399 _detail: NodePipelineNodeDetail,
400 ) {
401 }
402
403 fn on_node_action_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {}
405
406 fn on_unknown(&self, _msg: &str, _details: &Value) {}
408}
409
410#[derive(Debug, Clone)]
422pub enum MaaEvent {
423 ResourceLoadingStarting(ResourceLoadingDetail),
426 ResourceLoadingSucceeded(ResourceLoadingDetail),
428 ResourceLoadingFailed(ResourceLoadingDetail),
430
431 ControllerActionStarting(ControllerActionDetail),
434 ControllerActionSucceeded(ControllerActionDetail),
436 ControllerActionFailed(ControllerActionDetail),
438
439 TaskerTaskStarting(TaskerTaskDetail),
442 TaskerTaskSucceeded(TaskerTaskDetail),
444 TaskerTaskFailed(TaskerTaskDetail),
446
447 NodePipelineNodeStarting(NodePipelineNodeDetail),
450 NodePipelineNodeSucceeded(NodePipelineNodeDetail),
452 NodePipelineNodeFailed(NodePipelineNodeDetail),
454
455 NodeRecognitionStarting(NodeRecognitionDetail),
458 NodeRecognitionSucceeded(NodeRecognitionDetail),
460 NodeRecognitionFailed(NodeRecognitionDetail),
462
463 NodeActionStarting(NodeActionDetail),
466 NodeActionSucceeded(NodeActionDetail),
468 NodeActionFailed(NodeActionDetail),
470
471 NodeNextListStarting(NodeNextListDetail),
474 NodeNextListSucceeded(NodeNextListDetail),
476 NodeNextListFailed(NodeNextListDetail),
478
479 NodeRecognitionNodeStarting(NodePipelineNodeDetail),
482 NodeRecognitionNodeSucceeded(NodePipelineNodeDetail),
484 NodeRecognitionNodeFailed(NodePipelineNodeDetail),
486
487 NodeActionNodeStarting(NodePipelineNodeDetail),
489 NodeActionNodeSucceeded(NodePipelineNodeDetail),
491 NodeActionNodeFailed(NodePipelineNodeDetail),
493
494 Unknown {
500 msg: String,
502 raw_json: String,
504 err: Option<String>,
505 },
506}
507
508impl MaaEvent {
509 pub fn from_json(msg: &str, details: &str) -> Self {
518 match msg {
519 msg::RESOURCE_LOADING_STARTING => match serde_json::from_str(details) {
521 Ok(d) => MaaEvent::ResourceLoadingStarting(d),
522 Err(e) => MaaEvent::Unknown {
523 msg: msg.to_string(),
524 raw_json: details.to_string(),
525 err: Some(e.to_string()),
526 },
527 },
528 msg::RESOURCE_LOADING_SUCCEEDED => match serde_json::from_str(details) {
529 Ok(d) => MaaEvent::ResourceLoadingSucceeded(d),
530 Err(e) => MaaEvent::Unknown {
531 msg: msg.to_string(),
532 raw_json: details.to_string(),
533 err: Some(e.to_string()),
534 },
535 },
536 msg::RESOURCE_LOADING_FAILED => match serde_json::from_str(details) {
537 Ok(d) => MaaEvent::ResourceLoadingFailed(d),
538 Err(e) => MaaEvent::Unknown {
539 msg: msg.to_string(),
540 raw_json: details.to_string(),
541 err: Some(e.to_string()),
542 },
543 },
544
545 msg::CONTROLLER_ACTION_STARTING => match serde_json::from_str(details) {
547 Ok(d) => MaaEvent::ControllerActionStarting(d),
548 Err(e) => MaaEvent::Unknown {
549 msg: msg.to_string(),
550 raw_json: details.to_string(),
551 err: Some(e.to_string()),
552 },
553 },
554 msg::CONTROLLER_ACTION_SUCCEEDED => match serde_json::from_str(details) {
555 Ok(d) => MaaEvent::ControllerActionSucceeded(d),
556 Err(e) => MaaEvent::Unknown {
557 msg: msg.to_string(),
558 raw_json: details.to_string(),
559 err: Some(e.to_string()),
560 },
561 },
562 msg::CONTROLLER_ACTION_FAILED => match serde_json::from_str(details) {
563 Ok(d) => MaaEvent::ControllerActionFailed(d),
564 Err(e) => MaaEvent::Unknown {
565 msg: msg.to_string(),
566 raw_json: details.to_string(),
567 err: Some(e.to_string()),
568 },
569 },
570
571 msg::TASKER_TASK_STARTING => match serde_json::from_str(details) {
573 Ok(d) => MaaEvent::TaskerTaskStarting(d),
574 Err(e) => MaaEvent::Unknown {
575 msg: msg.to_string(),
576 raw_json: details.to_string(),
577 err: Some(e.to_string()),
578 },
579 },
580 msg::TASKER_TASK_SUCCEEDED => match serde_json::from_str(details) {
581 Ok(d) => MaaEvent::TaskerTaskSucceeded(d),
582 Err(e) => MaaEvent::Unknown {
583 msg: msg.to_string(),
584 raw_json: details.to_string(),
585 err: Some(e.to_string()),
586 },
587 },
588 msg::TASKER_TASK_FAILED => match serde_json::from_str(details) {
589 Ok(d) => MaaEvent::TaskerTaskFailed(d),
590 Err(e) => MaaEvent::Unknown {
591 msg: msg.to_string(),
592 raw_json: details.to_string(),
593 err: Some(e.to_string()),
594 },
595 },
596
597 msg::NODE_PIPELINE_NODE_STARTING => match serde_json::from_str(details) {
599 Ok(d) => MaaEvent::NodePipelineNodeStarting(d),
600 Err(e) => MaaEvent::Unknown {
601 msg: msg.to_string(),
602 raw_json: details.to_string(),
603 err: Some(e.to_string()),
604 },
605 },
606 msg::NODE_PIPELINE_NODE_SUCCEEDED => match serde_json::from_str(details) {
607 Ok(d) => MaaEvent::NodePipelineNodeSucceeded(d),
608 Err(e) => MaaEvent::Unknown {
609 msg: msg.to_string(),
610 raw_json: details.to_string(),
611 err: Some(e.to_string()),
612 },
613 },
614 msg::NODE_PIPELINE_NODE_FAILED => match serde_json::from_str(details) {
615 Ok(d) => MaaEvent::NodePipelineNodeFailed(d),
616 Err(e) => MaaEvent::Unknown {
617 msg: msg.to_string(),
618 raw_json: details.to_string(),
619 err: Some(e.to_string()),
620 },
621 },
622
623 msg::NODE_RECOGNITION_STARTING => match serde_json::from_str(details) {
625 Ok(d) => MaaEvent::NodeRecognitionStarting(d),
626 Err(e) => MaaEvent::Unknown {
627 msg: msg.to_string(),
628 raw_json: details.to_string(),
629 err: Some(e.to_string()),
630 },
631 },
632 msg::NODE_RECOGNITION_SUCCEEDED => match serde_json::from_str(details) {
633 Ok(d) => MaaEvent::NodeRecognitionSucceeded(d),
634 Err(e) => MaaEvent::Unknown {
635 msg: msg.to_string(),
636 raw_json: details.to_string(),
637 err: Some(e.to_string()),
638 },
639 },
640 msg::NODE_RECOGNITION_FAILED => match serde_json::from_str(details) {
641 Ok(d) => MaaEvent::NodeRecognitionFailed(d),
642 Err(e) => MaaEvent::Unknown {
643 msg: msg.to_string(),
644 raw_json: details.to_string(),
645 err: Some(e.to_string()),
646 },
647 },
648
649 msg::NODE_ACTION_STARTING => match serde_json::from_str(details) {
651 Ok(d) => MaaEvent::NodeActionStarting(d),
652 Err(e) => MaaEvent::Unknown {
653 msg: msg.to_string(),
654 raw_json: details.to_string(),
655 err: Some(e.to_string()),
656 },
657 },
658 msg::NODE_ACTION_SUCCEEDED => match serde_json::from_str(details) {
659 Ok(d) => MaaEvent::NodeActionSucceeded(d),
660 Err(e) => MaaEvent::Unknown {
661 msg: msg.to_string(),
662 raw_json: details.to_string(),
663 err: Some(e.to_string()),
664 },
665 },
666 msg::NODE_ACTION_FAILED => match serde_json::from_str(details) {
667 Ok(d) => MaaEvent::NodeActionFailed(d),
668 Err(e) => MaaEvent::Unknown {
669 msg: msg.to_string(),
670 raw_json: details.to_string(),
671 err: Some(e.to_string()),
672 },
673 },
674
675 msg::NODE_NEXT_LIST_STARTING => match serde_json::from_str(details) {
677 Ok(d) => MaaEvent::NodeNextListStarting(d),
678 Err(e) => MaaEvent::Unknown {
679 msg: msg.to_string(),
680 raw_json: details.to_string(),
681 err: Some(e.to_string()),
682 },
683 },
684 msg::NODE_NEXT_LIST_SUCCEEDED => match serde_json::from_str(details) {
685 Ok(d) => MaaEvent::NodeNextListSucceeded(d),
686 Err(e) => MaaEvent::Unknown {
687 msg: msg.to_string(),
688 raw_json: details.to_string(),
689 err: Some(e.to_string()),
690 },
691 },
692 msg::NODE_NEXT_LIST_FAILED => match serde_json::from_str(details) {
693 Ok(d) => MaaEvent::NodeNextListFailed(d),
694 Err(e) => MaaEvent::Unknown {
695 msg: msg.to_string(),
696 raw_json: details.to_string(),
697 err: Some(e.to_string()),
698 },
699 },
700
701 msg::NODE_RECOGNITION_NODE_STARTING => match serde_json::from_str(details) {
703 Ok(d) => MaaEvent::NodeRecognitionNodeStarting(d),
704 Err(e) => MaaEvent::Unknown {
705 msg: msg.to_string(),
706 raw_json: details.to_string(),
707 err: Some(e.to_string()),
708 },
709 },
710 msg::NODE_RECOGNITION_NODE_SUCCEEDED => match serde_json::from_str(details) {
711 Ok(d) => MaaEvent::NodeRecognitionNodeSucceeded(d),
712 Err(e) => MaaEvent::Unknown {
713 msg: msg.to_string(),
714 raw_json: details.to_string(),
715 err: Some(e.to_string()),
716 },
717 },
718 msg::NODE_RECOGNITION_NODE_FAILED => match serde_json::from_str(details) {
719 Ok(d) => MaaEvent::NodeRecognitionNodeFailed(d),
720 Err(e) => MaaEvent::Unknown {
721 msg: msg.to_string(),
722 raw_json: details.to_string(),
723 err: Some(e.to_string()),
724 },
725 },
726
727 msg::NODE_ACTION_NODE_STARTING => match serde_json::from_str(details) {
729 Ok(d) => MaaEvent::NodeActionNodeStarting(d),
730 Err(e) => MaaEvent::Unknown {
731 msg: msg.to_string(),
732 raw_json: details.to_string(),
733 err: Some(e.to_string()),
734 },
735 },
736 msg::NODE_ACTION_NODE_SUCCEEDED => match serde_json::from_str(details) {
737 Ok(d) => MaaEvent::NodeActionNodeSucceeded(d),
738 Err(e) => MaaEvent::Unknown {
739 msg: msg.to_string(),
740 raw_json: details.to_string(),
741 err: Some(e.to_string()),
742 },
743 },
744 msg::NODE_ACTION_NODE_FAILED => match serde_json::from_str(details) {
745 Ok(d) => MaaEvent::NodeActionNodeFailed(d),
746 Err(e) => MaaEvent::Unknown {
747 msg: msg.to_string(),
748 raw_json: details.to_string(),
749 err: Some(e.to_string()),
750 },
751 },
752
753 _ => MaaEvent::Unknown {
754 msg: msg.to_string(),
755 raw_json: details.to_string(),
756 err: None,
757 },
758 }
759 }
760}
761
762#[cfg(test)]
765mod tests {
766 use super::*;
767 use serde_json::json;
768
769 #[test]
770 fn test_context_event_parsing_logic() {
771 let msg_reco = "Node.Recognition.Succeeded";
772 let detail_reco =
773 json!({ "task_id": 1, "reco_id": 100, "name": "R", "focus": null }).to_string();
774
775 if let Some(ContextEvent::NodeRecognition(t, _)) =
776 ContextEvent::from_notification(msg_reco, &detail_reco)
777 {
778 assert_eq!(t, NotificationType::Succeeded);
779 } else {
780 panic!("Node.Recognition parse failed");
781 }
782
783 let msg_node = "Node.RecognitionNode.Starting";
784 let detail_node =
785 json!({ "task_id": 1, "node_id": 200, "name": "N", "focus": null }).to_string();
786
787 if let Some(ContextEvent::NodeRecognitionNode(t, _)) =
788 ContextEvent::from_notification(msg_node, &detail_node)
789 {
790 assert_eq!(t, NotificationType::Starting);
791 } else {
792 panic!("Node.RecognitionNode parse failed");
793 }
794 }
795}