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 #[serde(default)]
108 pub info: Value,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct TaskerTaskDetail {
114 pub task_id: i64,
116 pub entry: String,
118 #[serde(default)]
120 pub uuid: String,
121 #[serde(default)]
123 pub hash: String,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct NextListItem {
129 pub name: String,
131 #[serde(default)]
133 pub jump_back: bool,
134 #[serde(default)]
136 pub anchor: bool,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct NodeNextListDetail {
142 pub task_id: i64,
144 pub name: String,
146 #[serde(default)]
148 pub list: Vec<NextListItem>,
149 #[serde(default)]
151 pub focus: Value,
152}
153
154#[derive(Debug, Clone, Serialize, Deserialize)]
156pub struct NodeRecognitionDetail {
157 pub task_id: i64,
159 pub reco_id: i64,
161 pub name: String,
163 #[serde(default)]
165 pub focus: Value,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct NodeActionDetail {
171 pub task_id: i64,
173 pub action_id: i64,
175 pub name: String,
177 #[serde(default)]
179 pub focus: Value,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct NodePipelineNodeDetail {
185 pub task_id: i64,
187 pub node_id: i64,
189 pub name: String,
191 #[serde(default)]
193 pub focus: Value,
194}
195
196pub mod msg {
200 pub const RESOURCE_LOADING_STARTING: &str = "Resource.Loading.Starting";
202 pub const RESOURCE_LOADING_SUCCEEDED: &str = "Resource.Loading.Succeeded";
203 pub const RESOURCE_LOADING_FAILED: &str = "Resource.Loading.Failed";
204
205 pub const CONTROLLER_ACTION_STARTING: &str = "Controller.Action.Starting";
207 pub const CONTROLLER_ACTION_SUCCEEDED: &str = "Controller.Action.Succeeded";
208 pub const CONTROLLER_ACTION_FAILED: &str = "Controller.Action.Failed";
209
210 pub const TASKER_TASK_STARTING: &str = "Tasker.Task.Starting";
212 pub const TASKER_TASK_SUCCEEDED: &str = "Tasker.Task.Succeeded";
213 pub const TASKER_TASK_FAILED: &str = "Tasker.Task.Failed";
214
215 pub const NODE_PIPELINE_NODE_STARTING: &str = "Node.PipelineNode.Starting";
217 pub const NODE_PIPELINE_NODE_SUCCEEDED: &str = "Node.PipelineNode.Succeeded";
218 pub const NODE_PIPELINE_NODE_FAILED: &str = "Node.PipelineNode.Failed";
219
220 pub const NODE_RECOGNITION_STARTING: &str = "Node.Recognition.Starting";
222 pub const NODE_RECOGNITION_SUCCEEDED: &str = "Node.Recognition.Succeeded";
223 pub const NODE_RECOGNITION_FAILED: &str = "Node.Recognition.Failed";
224
225 pub const NODE_ACTION_STARTING: &str = "Node.Action.Starting";
227 pub const NODE_ACTION_SUCCEEDED: &str = "Node.Action.Succeeded";
228 pub const NODE_ACTION_FAILED: &str = "Node.Action.Failed";
229
230 pub const NODE_NEXT_LIST_STARTING: &str = "Node.NextList.Starting";
232 pub const NODE_NEXT_LIST_SUCCEEDED: &str = "Node.NextList.Succeeded";
233 pub const NODE_NEXT_LIST_FAILED: &str = "Node.NextList.Failed";
234
235 pub const NODE_RECOGNITION_NODE_STARTING: &str = "Node.RecognitionNode.Starting";
237 pub const NODE_RECOGNITION_NODE_SUCCEEDED: &str = "Node.RecognitionNode.Succeeded";
238 pub const NODE_RECOGNITION_NODE_FAILED: &str = "Node.RecognitionNode.Failed";
239
240 pub const NODE_ACTION_NODE_STARTING: &str = "Node.ActionNode.Starting";
242 pub const NODE_ACTION_NODE_SUCCEEDED: &str = "Node.ActionNode.Succeeded";
243 pub const NODE_ACTION_NODE_FAILED: &str = "Node.ActionNode.Failed";
244}
245
246pub fn parse_type(msg: &str) -> NotificationType {
258 NotificationType::from(msg)
259}
260
261pub fn parse_resource_loading(details: &str) -> Option<ResourceLoadingDetail> {
263 serde_json::from_str(details).ok()
264}
265
266pub fn parse_controller_action(details: &str) -> Option<ControllerActionDetail> {
268 serde_json::from_str(details).ok()
269}
270
271pub fn parse_tasker_task(details: &str) -> Option<TaskerTaskDetail> {
273 serde_json::from_str(details).ok()
274}
275
276pub fn parse_node_next_list(details: &str) -> Option<NodeNextListDetail> {
278 serde_json::from_str(details).ok()
279}
280
281pub fn parse_node_recognition(details: &str) -> Option<NodeRecognitionDetail> {
283 serde_json::from_str(details).ok()
284}
285
286pub fn parse_node_action(details: &str) -> Option<NodeActionDetail> {
288 serde_json::from_str(details).ok()
289}
290
291pub fn parse_node_pipeline_node(details: &str) -> Option<NodePipelineNodeDetail> {
293 serde_json::from_str(details).ok()
294}
295
296#[derive(Debug, Clone)]
300pub enum ContextEvent {
301 NodeNextList(NotificationType, NodeNextListDetail),
302 NodeRecognition(NotificationType, NodeRecognitionDetail),
303 NodeAction(NotificationType, NodeActionDetail),
304 NodePipelineNode(NotificationType, NodePipelineNodeDetail),
305 NodeRecognitionNode(NotificationType, NodePipelineNodeDetail),
306 NodeActionNode(NotificationType, NodePipelineNodeDetail),
307 Unknown(String, Value),
308}
309
310impl ContextEvent {
311 pub fn from_notification(msg: &str, details: &str) -> Option<Self> {
313 let noti_type = NotificationType::from(msg);
314
315 let parse_json = || -> Option<Value> { serde_json::from_str(details).ok() };
316
317 if msg.starts_with("Node.NextList") {
318 let detail = parse_node_next_list(details)?;
319 return Some(ContextEvent::NodeNextList(noti_type, detail));
320 }
321
322 if msg.starts_with("Node.Recognition.") {
323 let detail = parse_node_recognition(details)?;
324 return Some(ContextEvent::NodeRecognition(noti_type, detail));
325 }
326
327 if msg.starts_with("Node.Action.") {
328 let detail = parse_node_action(details)?;
329 return Some(ContextEvent::NodeAction(noti_type, detail));
330 }
331
332 if msg.starts_with("Node.PipelineNode") {
333 let detail = parse_node_pipeline_node(details)?;
334 return Some(ContextEvent::NodePipelineNode(noti_type, detail));
335 }
336
337 if msg.starts_with("Node.RecognitionNode") {
338 let detail = parse_node_pipeline_node(details)?;
339 return Some(ContextEvent::NodeRecognitionNode(noti_type, detail));
340 }
341
342 if msg.starts_with("Node.ActionNode") {
343 let detail = parse_node_pipeline_node(details)?;
344 return Some(ContextEvent::NodeActionNode(noti_type, detail));
345 }
346
347 Some(ContextEvent::Unknown(
348 msg.to_string(),
349 parse_json().unwrap_or(Value::Null),
350 ))
351 }
352}
353
354pub trait ResourceEventHandler: Send + Sync {
358 fn on_resource_loading(&self, _noti_type: NotificationType, _detail: ResourceLoadingDetail) {}
360
361 fn on_unknown(&self, _msg: &str, _details: &Value) {}
363}
364
365pub trait ControllerEventHandler: Send + Sync {
367 fn on_controller_action(&self, _noti_type: NotificationType, _detail: ControllerActionDetail) {}
369
370 fn on_unknown(&self, _msg: &str, _details: &Value) {}
372}
373
374pub trait TaskerEventHandler: Send + Sync {
376 fn on_tasker_task(&self, _noti_type: NotificationType, _detail: TaskerTaskDetail) {}
378
379 fn on_unknown(&self, _msg: &str, _details: &Value) {}
381}
382
383pub trait ContextEventHandler: Send + Sync {
385 fn on_node_next_list(&self, _noti_type: NotificationType, _detail: NodeNextListDetail) {}
387
388 fn on_node_recognition(&self, _noti_type: NotificationType, _detail: NodeRecognitionDetail) {}
390
391 fn on_node_action(&self, _noti_type: NotificationType, _detail: NodeActionDetail) {}
393
394 fn on_node_pipeline_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {
396 }
397
398 fn on_node_recognition_node(
400 &self,
401 _noti_type: NotificationType,
402 _detail: NodePipelineNodeDetail,
403 ) {
404 }
405
406 fn on_node_action_node(&self, _noti_type: NotificationType, _detail: NodePipelineNodeDetail) {}
408
409 fn on_unknown(&self, _msg: &str, _details: &Value) {}
411}
412
413#[derive(Debug, Clone)]
425pub enum MaaEvent {
426 ResourceLoadingStarting(ResourceLoadingDetail),
429 ResourceLoadingSucceeded(ResourceLoadingDetail),
431 ResourceLoadingFailed(ResourceLoadingDetail),
433
434 ControllerActionStarting(ControllerActionDetail),
437 ControllerActionSucceeded(ControllerActionDetail),
439 ControllerActionFailed(ControllerActionDetail),
441
442 TaskerTaskStarting(TaskerTaskDetail),
445 TaskerTaskSucceeded(TaskerTaskDetail),
447 TaskerTaskFailed(TaskerTaskDetail),
449
450 NodePipelineNodeStarting(NodePipelineNodeDetail),
453 NodePipelineNodeSucceeded(NodePipelineNodeDetail),
455 NodePipelineNodeFailed(NodePipelineNodeDetail),
457
458 NodeRecognitionStarting(NodeRecognitionDetail),
461 NodeRecognitionSucceeded(NodeRecognitionDetail),
463 NodeRecognitionFailed(NodeRecognitionDetail),
465
466 NodeActionStarting(NodeActionDetail),
469 NodeActionSucceeded(NodeActionDetail),
471 NodeActionFailed(NodeActionDetail),
473
474 NodeNextListStarting(NodeNextListDetail),
477 NodeNextListSucceeded(NodeNextListDetail),
479 NodeNextListFailed(NodeNextListDetail),
481
482 NodeRecognitionNodeStarting(NodePipelineNodeDetail),
485 NodeRecognitionNodeSucceeded(NodePipelineNodeDetail),
487 NodeRecognitionNodeFailed(NodePipelineNodeDetail),
489
490 NodeActionNodeStarting(NodePipelineNodeDetail),
492 NodeActionNodeSucceeded(NodePipelineNodeDetail),
494 NodeActionNodeFailed(NodePipelineNodeDetail),
496
497 Unknown {
503 msg: String,
505 raw_json: String,
507 err: Option<String>,
508 },
509}
510
511impl MaaEvent {
512 pub fn from_json(msg: &str, details: &str) -> Self {
521 match msg {
522 msg::RESOURCE_LOADING_STARTING => match serde_json::from_str(details) {
524 Ok(d) => MaaEvent::ResourceLoadingStarting(d),
525 Err(e) => MaaEvent::Unknown {
526 msg: msg.to_string(),
527 raw_json: details.to_string(),
528 err: Some(e.to_string()),
529 },
530 },
531 msg::RESOURCE_LOADING_SUCCEEDED => match serde_json::from_str(details) {
532 Ok(d) => MaaEvent::ResourceLoadingSucceeded(d),
533 Err(e) => MaaEvent::Unknown {
534 msg: msg.to_string(),
535 raw_json: details.to_string(),
536 err: Some(e.to_string()),
537 },
538 },
539 msg::RESOURCE_LOADING_FAILED => match serde_json::from_str(details) {
540 Ok(d) => MaaEvent::ResourceLoadingFailed(d),
541 Err(e) => MaaEvent::Unknown {
542 msg: msg.to_string(),
543 raw_json: details.to_string(),
544 err: Some(e.to_string()),
545 },
546 },
547
548 msg::CONTROLLER_ACTION_STARTING => match serde_json::from_str(details) {
550 Ok(d) => MaaEvent::ControllerActionStarting(d),
551 Err(e) => MaaEvent::Unknown {
552 msg: msg.to_string(),
553 raw_json: details.to_string(),
554 err: Some(e.to_string()),
555 },
556 },
557 msg::CONTROLLER_ACTION_SUCCEEDED => match serde_json::from_str(details) {
558 Ok(d) => MaaEvent::ControllerActionSucceeded(d),
559 Err(e) => MaaEvent::Unknown {
560 msg: msg.to_string(),
561 raw_json: details.to_string(),
562 err: Some(e.to_string()),
563 },
564 },
565 msg::CONTROLLER_ACTION_FAILED => match serde_json::from_str(details) {
566 Ok(d) => MaaEvent::ControllerActionFailed(d),
567 Err(e) => MaaEvent::Unknown {
568 msg: msg.to_string(),
569 raw_json: details.to_string(),
570 err: Some(e.to_string()),
571 },
572 },
573
574 msg::TASKER_TASK_STARTING => match serde_json::from_str(details) {
576 Ok(d) => MaaEvent::TaskerTaskStarting(d),
577 Err(e) => MaaEvent::Unknown {
578 msg: msg.to_string(),
579 raw_json: details.to_string(),
580 err: Some(e.to_string()),
581 },
582 },
583 msg::TASKER_TASK_SUCCEEDED => match serde_json::from_str(details) {
584 Ok(d) => MaaEvent::TaskerTaskSucceeded(d),
585 Err(e) => MaaEvent::Unknown {
586 msg: msg.to_string(),
587 raw_json: details.to_string(),
588 err: Some(e.to_string()),
589 },
590 },
591 msg::TASKER_TASK_FAILED => match serde_json::from_str(details) {
592 Ok(d) => MaaEvent::TaskerTaskFailed(d),
593 Err(e) => MaaEvent::Unknown {
594 msg: msg.to_string(),
595 raw_json: details.to_string(),
596 err: Some(e.to_string()),
597 },
598 },
599
600 msg::NODE_PIPELINE_NODE_STARTING => match serde_json::from_str(details) {
602 Ok(d) => MaaEvent::NodePipelineNodeStarting(d),
603 Err(e) => MaaEvent::Unknown {
604 msg: msg.to_string(),
605 raw_json: details.to_string(),
606 err: Some(e.to_string()),
607 },
608 },
609 msg::NODE_PIPELINE_NODE_SUCCEEDED => match serde_json::from_str(details) {
610 Ok(d) => MaaEvent::NodePipelineNodeSucceeded(d),
611 Err(e) => MaaEvent::Unknown {
612 msg: msg.to_string(),
613 raw_json: details.to_string(),
614 err: Some(e.to_string()),
615 },
616 },
617 msg::NODE_PIPELINE_NODE_FAILED => match serde_json::from_str(details) {
618 Ok(d) => MaaEvent::NodePipelineNodeFailed(d),
619 Err(e) => MaaEvent::Unknown {
620 msg: msg.to_string(),
621 raw_json: details.to_string(),
622 err: Some(e.to_string()),
623 },
624 },
625
626 msg::NODE_RECOGNITION_STARTING => match serde_json::from_str(details) {
628 Ok(d) => MaaEvent::NodeRecognitionStarting(d),
629 Err(e) => MaaEvent::Unknown {
630 msg: msg.to_string(),
631 raw_json: details.to_string(),
632 err: Some(e.to_string()),
633 },
634 },
635 msg::NODE_RECOGNITION_SUCCEEDED => match serde_json::from_str(details) {
636 Ok(d) => MaaEvent::NodeRecognitionSucceeded(d),
637 Err(e) => MaaEvent::Unknown {
638 msg: msg.to_string(),
639 raw_json: details.to_string(),
640 err: Some(e.to_string()),
641 },
642 },
643 msg::NODE_RECOGNITION_FAILED => match serde_json::from_str(details) {
644 Ok(d) => MaaEvent::NodeRecognitionFailed(d),
645 Err(e) => MaaEvent::Unknown {
646 msg: msg.to_string(),
647 raw_json: details.to_string(),
648 err: Some(e.to_string()),
649 },
650 },
651
652 msg::NODE_ACTION_STARTING => match serde_json::from_str(details) {
654 Ok(d) => MaaEvent::NodeActionStarting(d),
655 Err(e) => MaaEvent::Unknown {
656 msg: msg.to_string(),
657 raw_json: details.to_string(),
658 err: Some(e.to_string()),
659 },
660 },
661 msg::NODE_ACTION_SUCCEEDED => match serde_json::from_str(details) {
662 Ok(d) => MaaEvent::NodeActionSucceeded(d),
663 Err(e) => MaaEvent::Unknown {
664 msg: msg.to_string(),
665 raw_json: details.to_string(),
666 err: Some(e.to_string()),
667 },
668 },
669 msg::NODE_ACTION_FAILED => match serde_json::from_str(details) {
670 Ok(d) => MaaEvent::NodeActionFailed(d),
671 Err(e) => MaaEvent::Unknown {
672 msg: msg.to_string(),
673 raw_json: details.to_string(),
674 err: Some(e.to_string()),
675 },
676 },
677
678 msg::NODE_NEXT_LIST_STARTING => match serde_json::from_str(details) {
680 Ok(d) => MaaEvent::NodeNextListStarting(d),
681 Err(e) => MaaEvent::Unknown {
682 msg: msg.to_string(),
683 raw_json: details.to_string(),
684 err: Some(e.to_string()),
685 },
686 },
687 msg::NODE_NEXT_LIST_SUCCEEDED => match serde_json::from_str(details) {
688 Ok(d) => MaaEvent::NodeNextListSucceeded(d),
689 Err(e) => MaaEvent::Unknown {
690 msg: msg.to_string(),
691 raw_json: details.to_string(),
692 err: Some(e.to_string()),
693 },
694 },
695 msg::NODE_NEXT_LIST_FAILED => match serde_json::from_str(details) {
696 Ok(d) => MaaEvent::NodeNextListFailed(d),
697 Err(e) => MaaEvent::Unknown {
698 msg: msg.to_string(),
699 raw_json: details.to_string(),
700 err: Some(e.to_string()),
701 },
702 },
703
704 msg::NODE_RECOGNITION_NODE_STARTING => match serde_json::from_str(details) {
706 Ok(d) => MaaEvent::NodeRecognitionNodeStarting(d),
707 Err(e) => MaaEvent::Unknown {
708 msg: msg.to_string(),
709 raw_json: details.to_string(),
710 err: Some(e.to_string()),
711 },
712 },
713 msg::NODE_RECOGNITION_NODE_SUCCEEDED => match serde_json::from_str(details) {
714 Ok(d) => MaaEvent::NodeRecognitionNodeSucceeded(d),
715 Err(e) => MaaEvent::Unknown {
716 msg: msg.to_string(),
717 raw_json: details.to_string(),
718 err: Some(e.to_string()),
719 },
720 },
721 msg::NODE_RECOGNITION_NODE_FAILED => match serde_json::from_str(details) {
722 Ok(d) => MaaEvent::NodeRecognitionNodeFailed(d),
723 Err(e) => MaaEvent::Unknown {
724 msg: msg.to_string(),
725 raw_json: details.to_string(),
726 err: Some(e.to_string()),
727 },
728 },
729
730 msg::NODE_ACTION_NODE_STARTING => match serde_json::from_str(details) {
732 Ok(d) => MaaEvent::NodeActionNodeStarting(d),
733 Err(e) => MaaEvent::Unknown {
734 msg: msg.to_string(),
735 raw_json: details.to_string(),
736 err: Some(e.to_string()),
737 },
738 },
739 msg::NODE_ACTION_NODE_SUCCEEDED => match serde_json::from_str(details) {
740 Ok(d) => MaaEvent::NodeActionNodeSucceeded(d),
741 Err(e) => MaaEvent::Unknown {
742 msg: msg.to_string(),
743 raw_json: details.to_string(),
744 err: Some(e.to_string()),
745 },
746 },
747 msg::NODE_ACTION_NODE_FAILED => match serde_json::from_str(details) {
748 Ok(d) => MaaEvent::NodeActionNodeFailed(d),
749 Err(e) => MaaEvent::Unknown {
750 msg: msg.to_string(),
751 raw_json: details.to_string(),
752 err: Some(e.to_string()),
753 },
754 },
755
756 _ => MaaEvent::Unknown {
757 msg: msg.to_string(),
758 raw_json: details.to_string(),
759 err: None,
760 },
761 }
762 }
763}
764
765#[cfg(test)]
768mod tests {
769 use super::*;
770 use serde_json::json;
771
772 #[test]
773 fn test_context_event_parsing_logic() {
774 let msg_reco = "Node.Recognition.Succeeded";
775 let detail_reco =
776 json!({ "task_id": 1, "reco_id": 100, "name": "R", "focus": null }).to_string();
777
778 if let Some(ContextEvent::NodeRecognition(t, _)) =
779 ContextEvent::from_notification(msg_reco, &detail_reco)
780 {
781 assert_eq!(t, NotificationType::Succeeded);
782 } else {
783 panic!("Node.Recognition parse failed");
784 }
785
786 let msg_node = "Node.RecognitionNode.Starting";
787 let detail_node =
788 json!({ "task_id": 1, "node_id": 200, "name": "N", "focus": null }).to_string();
789
790 if let Some(ContextEvent::NodeRecognitionNode(t, _)) =
791 ContextEvent::from_notification(msg_node, &detail_node)
792 {
793 assert_eq!(t, NotificationType::Starting);
794 } else {
795 panic!("Node.RecognitionNode parse failed");
796 }
797 }
798}