MaaFw 4.4.0b1__py3-none-manylinux2014_x86_64.whl → 5.4.0__py3-none-manylinux2014_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MaaFw might be problematic. Click here for more details.

maa/tasker.py CHANGED
@@ -1,19 +1,22 @@
1
1
  import ctypes
2
+ import dataclasses
2
3
  import json
3
4
  from pathlib import Path
4
- from typing import Any, Dict, Optional, Union
5
+ from typing import Dict, Optional, Union
6
+
7
+ import numpy
5
8
 
6
9
  from .define import *
7
10
  from .library import Library
8
11
  from .buffer import ImageListBuffer, RectBuffer, StringBuffer, ImageBuffer
9
12
  from .job import Job, JobWithResult
10
- from .notification_handler import NotificationHandler
13
+ from .event_sink import EventSink, NotificationType
11
14
  from .resource import Resource
12
15
  from .controller import Controller
16
+ from .pipeline import JRecognitionParam, JActionParam, JRecognitionType, JActionType
13
17
 
14
18
 
15
19
  class Tasker:
16
- _notification_handler: Optional[NotificationHandler]
17
20
  _handle: MaaTaskerHandle
18
21
  _own: bool
19
22
 
@@ -21,19 +24,24 @@ class Tasker:
21
24
 
22
25
  def __init__(
23
26
  self,
24
- notification_handler: Optional[NotificationHandler] = None,
25
27
  handle: Optional[MaaTaskerHandle] = None,
26
28
  ):
29
+ """创建实例 / Create instance
30
+
31
+ Args:
32
+ handle: 可选的外部句柄 / Optional external handle
33
+
34
+ Raises:
35
+ RuntimeError: 如果创建失败
36
+ """
37
+
27
38
  self._set_api_properties()
28
39
 
29
40
  if handle:
30
41
  self._handle = handle
31
42
  self._own = False
32
43
  else:
33
- self._notification_handler = notification_handler
34
- self._handle = Library.framework().MaaTaskerCreate(
35
- *NotificationHandler._gen_c_param(self._notification_handler)
36
- )
44
+ self._handle = Library.framework().MaaTaskerCreate()
37
45
  self._own = True
38
46
 
39
47
  if not self._handle:
@@ -44,6 +52,15 @@ class Tasker:
44
52
  Library.framework().MaaTaskerDestroy(self._handle)
45
53
 
46
54
  def bind(self, resource: Resource, controller: Controller) -> bool:
55
+ """关联资源和控制器 / Bind resource and controller
56
+
57
+ Args:
58
+ resource: 资源对象 / Resource object
59
+ controller: 控制器对象 / Controller object
60
+
61
+ Returns:
62
+ bool: 是否成功 / Whether successful
63
+ """
47
64
  # avoid gc
48
65
  self._resource_holder = resource
49
66
  self._controller_holder = controller
@@ -58,6 +75,14 @@ class Tasker:
58
75
 
59
76
  @property
60
77
  def resource(self) -> Resource:
78
+ """获取关联的资源 / Get bound resource
79
+
80
+ Returns:
81
+ Resource: 资源对象 / Resource object
82
+
83
+ Raises:
84
+ RuntimeError: 如果获取失败
85
+ """
61
86
  resource_handle = Library.framework().MaaTaskerGetResource(self._handle)
62
87
  if not resource_handle:
63
88
  raise RuntimeError("Failed to get resource.")
@@ -66,6 +91,14 @@ class Tasker:
66
91
 
67
92
  @property
68
93
  def controller(self) -> Controller:
94
+ """获取关联的控制器 / Get bound controller
95
+
96
+ Returns:
97
+ Controller: 控制器对象 / Controller object
98
+
99
+ Raises:
100
+ RuntimeError: 如果获取失败
101
+ """
69
102
  controller_handle = Library.framework().MaaTaskerGetController(self._handle)
70
103
  if not controller_handle:
71
104
  raise RuntimeError("Failed to get controller.")
@@ -74,28 +107,132 @@ class Tasker:
74
107
 
75
108
  @property
76
109
  def inited(self) -> bool:
110
+ """判断是否正确初始化 / Check if initialized correctly
111
+
112
+ Returns:
113
+ bool: 是否已正确初始化 / Whether correctly initialized
114
+ """
77
115
  return bool(Library.framework().MaaTaskerInited(self._handle))
78
116
 
79
117
  def post_task(self, entry: str, pipeline_override: Dict = {}) -> JobWithResult:
118
+ """异步执行任务 / Asynchronously execute task
119
+
120
+ 这是一个异步操作,会立即返回一个 Job 对象
121
+ This is an asynchronous operation that immediately returns a Job object
122
+
123
+ Args:
124
+ entry: 任务入口 / Task entry
125
+ pipeline_override: 用于覆盖的 json / JSON for overriding
126
+
127
+ Returns:
128
+ JobWithResult: 任务作业对象,可通过 status/wait 查询状态,通过 get() 获取结果 / Task job object, can query status via status/wait, get result via get()
129
+ """
80
130
  taskid = Library.framework().MaaTaskerPostTask(
81
131
  self._handle,
82
132
  *Tasker._gen_post_param(entry, pipeline_override),
83
133
  )
84
134
  return self._gen_task_job(taskid)
85
135
 
136
+ def post_recognition(
137
+ self,
138
+ reco_type: JRecognitionType,
139
+ reco_param: JRecognitionParam,
140
+ image: numpy.ndarray,
141
+ ) -> JobWithResult:
142
+ """异步执行识别 / Asynchronously execute recognition
143
+
144
+ Args:
145
+ reco_type: 识别类型 / Recognition type
146
+ reco_param: 识别参数 / Recognition parameters
147
+ image: 前序截图 / Previous screenshot
148
+
149
+ Returns:
150
+ JobWithResult: 任务作业对象 / Task job object
151
+ """
152
+ img_buffer = ImageBuffer()
153
+ img_buffer.set(image)
154
+ reco_param_json = json.dumps(dataclasses.asdict(reco_param), ensure_ascii=False)
155
+ taskid = Library.framework().MaaTaskerPostRecognition(
156
+ self._handle,
157
+ reco_type.encode(),
158
+ reco_param_json.encode(),
159
+ img_buffer._handle,
160
+ )
161
+ return self._gen_task_job(taskid)
162
+
163
+ def post_action(
164
+ self,
165
+ action_type: JActionType,
166
+ action_param: JActionParam,
167
+ box: Rect = Rect(0, 0, 0, 0),
168
+ reco_detail: str = "",
169
+ ) -> JobWithResult:
170
+ """异步执行操作 / Asynchronously execute action
171
+
172
+ Args:
173
+ action_type: 操作类型 / Action type
174
+ action_param: 操作参数 / Action parameters
175
+ box: 前序识别位置 / Previous recognition position
176
+ reco_detail: 前序识别详情 / Previous recognition details
177
+
178
+ Returns:
179
+ JobWithResult: 任务作业对象 / Task job object
180
+ """
181
+ rect_buffer = RectBuffer()
182
+ rect_buffer.set(box)
183
+ action_param_json = json.dumps(
184
+ dataclasses.asdict(action_param), ensure_ascii=False
185
+ )
186
+ taskid = Library.framework().MaaTaskerPostAction(
187
+ self._handle,
188
+ action_type.encode(),
189
+ action_param_json.encode(),
190
+ rect_buffer._handle,
191
+ reco_detail.encode(),
192
+ )
193
+ return self._gen_task_job(taskid)
194
+
86
195
  @property
87
196
  def running(self) -> bool:
197
+ """判断实例是否还在运行 / Check if instance is still running
198
+
199
+ Returns:
200
+ bool: 是否正在运行 / Whether running
201
+ """
88
202
  return bool(Library.framework().MaaTaskerRunning(self._handle))
89
203
 
90
204
  def post_stop(self) -> Job:
205
+ """异步停止实例 / Asynchronously stop instance
206
+
207
+ 这是一个异步操作,会立即返回一个 Job 对象
208
+ 停止操作会中断当前运行的任务,并停止资源加载和控制器操作
209
+ This is an asynchronous operation that immediately returns a Job object
210
+ The stop operation will interrupt the currently running task and stop resource loading and controller operations
211
+
212
+ Returns:
213
+ Job: 作业对象,可通过 status/wait 查询状态 / Job object, can query status via status/wait
214
+ """
91
215
  taskid = Library.framework().MaaTaskerPostStop(self._handle)
92
216
  return self._gen_task_job(taskid)
93
217
 
94
218
  @property
95
219
  def stopping(self) -> bool:
220
+ """判断实例是否正在停止中(尚未停止) / Check if instance is stopping (not yet stopped)
221
+
222
+ Returns:
223
+ bool: 是否正在停止 / Whether stopping
224
+ """
96
225
  return bool(Library.framework().MaaTaskerStopping(self._handle))
97
226
 
98
227
  def get_latest_node(self, name: str) -> Optional[NodeDetail]:
228
+ """获取任务的最新节点号 / Get latest node id for task
229
+
230
+ Args:
231
+ name: 任务名 / Task name
232
+
233
+ Returns:
234
+ Optional[NodeDetail]: 节点详情,如果不存在则返回 None / Node detail, or None if not exists
235
+ """
99
236
  c_node_id = MaaNodeId()
100
237
  ret = bool(
101
238
  Library.framework().MaaTaskerGetLatestNode(
@@ -110,81 +247,90 @@ class Tasker:
110
247
  return self.get_node_detail(int(c_node_id.value))
111
248
 
112
249
  def clear_cache(self) -> bool:
250
+ """清理所有可查询的信息 / Clear all queryable information
251
+
252
+ Returns:
253
+ bool: 是否成功 / Whether successful
254
+ """
113
255
  return bool(Library.framework().MaaTaskerClearCache(self._handle))
114
256
 
115
- @staticmethod
116
- def set_log_dir(path: Union[Path, str]) -> bool:
117
- strpath = str(path)
118
- return bool(
119
- Library.framework().MaaSetGlobalOption(
120
- MaaOption(MaaGlobalOptionEnum.LogDir),
121
- strpath.encode(),
122
- len(strpath),
123
- )
124
- )
257
+ _sink_holder: Dict[int, "EventSink"] = {}
125
258
 
126
- @staticmethod
127
- def set_save_draw(save_draw: bool) -> bool:
128
- cbool = ctypes.c_bool(save_draw)
129
- return bool(
130
- Library.framework().MaaSetGlobalOption(
131
- MaaOption(MaaGlobalOptionEnum.SaveDraw),
132
- ctypes.pointer(cbool),
133
- ctypes.sizeof(ctypes.c_bool),
134
- )
135
- )
259
+ def add_sink(self, sink: "TaskerEventSink") -> Optional[int]:
260
+ """添加实例事件监听器 / Add instance event listener
136
261
 
137
- @staticmethod
138
- def set_recording(recording: bool) -> bool:
139
- cbool = ctypes.c_bool(recording)
140
- return bool(
141
- Library.framework().MaaSetGlobalOption(
142
- MaaOption(MaaGlobalOptionEnum.Recording),
143
- ctypes.pointer(cbool),
144
- ctypes.sizeof(ctypes.c_bool),
145
- )
146
- )
262
+ Args:
263
+ sink: 事件监听器 / Event sink
147
264
 
148
- @staticmethod
149
- def set_stdout_level(level: LoggingLevelEnum) -> bool:
150
- clevel = MaaLoggingLevel(level)
151
- return bool(
152
- Library.framework().MaaSetGlobalOption(
153
- MaaOption(MaaGlobalOptionEnum.StdoutLevel),
154
- ctypes.pointer(clevel),
155
- ctypes.sizeof(MaaLoggingLevel),
265
+ Returns:
266
+ Optional[int]: 监听器 id,失败返回 None / Listener id, or None if failed
267
+ """
268
+ sink_id = int(
269
+ Library.framework().MaaTaskerAddSink(
270
+ self._handle, *EventSink._gen_c_param(sink)
156
271
  )
157
272
  )
273
+ if sink_id == MaaInvalidId:
274
+ return None
158
275
 
159
- @staticmethod
160
- def set_show_hit_draw(show_hit_draw: bool) -> bool:
161
- cbool = ctypes.c_bool(show_hit_draw)
162
- return bool(
163
- Library.framework().MaaSetGlobalOption(
164
- MaaOption(MaaGlobalOptionEnum.ShowHitDraw),
165
- ctypes.pointer(cbool),
166
- ctypes.sizeof(ctypes.c_bool),
167
- )
168
- )
276
+ self._sink_holder[sink_id] = sink
277
+ return sink_id
169
278
 
170
- @staticmethod
171
- def set_debug_mode(debug_mode: bool) -> bool:
172
- cbool = ctypes.c_bool(debug_mode)
173
- return bool(
174
- Library.framework().MaaSetGlobalOption(
175
- MaaOption(MaaGlobalOptionEnum.DebugMode),
176
- ctypes.pointer(cbool),
177
- ctypes.sizeof(ctypes.c_bool),
279
+ def remove_sink(self, sink_id: int) -> None:
280
+ """移除实例事件监听器 / Remove instance event listener
281
+
282
+ Args:
283
+ sink_id: 监听器 id / Listener id
284
+ """
285
+ Library.framework().MaaTaskerRemoveSink(self._handle, sink_id)
286
+ self._sink_holder.pop(sink_id)
287
+
288
+ def clear_sinks(self) -> None:
289
+ """清除所有实例事件监听器 / Clear all instance event listeners"""
290
+ Library.framework().MaaTaskerClearSinks(self._handle)
291
+
292
+ def add_context_sink(self, sink: "ContextEventSink") -> Optional[int]:
293
+ """添加上下文事件监听器 / Add context event listener
294
+
295
+ Args:
296
+ sink: 上下文事件监听器 / Context event sink
297
+
298
+ Returns:
299
+ Optional[int]: 监听器 id,失败返回 None / Listener id, or None if failed
300
+ """
301
+ sink_id = int(
302
+ Library.framework().MaaTaskerAddContextSink(
303
+ self._handle, *EventSink._gen_c_param(sink)
178
304
  )
179
305
  )
306
+ if sink_id == MaaInvalidId:
307
+ return None
308
+
309
+ self._sink_holder[sink_id] = sink
310
+ return sink_id
311
+
312
+ def remove_context_sink(self, sink_id: int) -> None:
313
+ """移除上下文事件监听器 / Remove context event listener
314
+
315
+ Args:
316
+ sink_id: 监听器 id / Listener id
317
+ """
318
+ Library.framework().MaaTaskerRemoveContextSink(self._handle, sink_id)
319
+ self._sink_holder.pop(sink_id)
320
+
321
+ def clear_context_sinks(self) -> None:
322
+ """清除所有上下文事件监听器 / Clear all context event listeners"""
323
+ Library.framework().MaaTaskerClearContextSinks(self._handle)
180
324
 
181
325
  ### private ###
182
326
 
183
327
  @staticmethod
184
328
  def _gen_post_param(entry: str, pipeline_override: Dict) -> Tuple[bytes, bytes]:
329
+ pipeline_json = json.dumps(pipeline_override, ensure_ascii=False)
330
+
185
331
  return (
186
332
  entry.encode(),
187
- json.dumps(pipeline_override, ensure_ascii=False).encode(),
333
+ pipeline_json.encode(),
188
334
  )
189
335
 
190
336
  def _gen_task_job(self, taskid: MaaTaskId) -> JobWithResult:
@@ -202,8 +348,16 @@ class Tasker:
202
348
  return Library.framework().MaaTaskerWait(self._handle, id)
203
349
 
204
350
  def get_recognition_detail(self, reco_id: int) -> Optional[RecognitionDetail]:
351
+ """获取识别信息 / Get recognition info
352
+
353
+ Args:
354
+ reco_id: 识别号 / Recognition id
355
+
356
+ Returns:
357
+ Optional[RecognitionDetail]: 识别详情,如果不存在则返回 None / Recognition detail, or None if not exists
358
+ """
205
359
  name = StringBuffer()
206
- algorithm = StringBuffer()
360
+ algorithm = StringBuffer() # type: ignore
207
361
  hit = MaaBool()
208
362
  box = RectBuffer()
209
363
  detail_json = StringBuffer()
@@ -226,25 +380,89 @@ class Tasker:
226
380
  return None
227
381
 
228
382
  raw_detail = json.loads(detail_json.get())
229
- algorithm: AlgorithmEnum = AlgorithmEnum(algorithm.get())
230
- parsed_detail = Tasker._parse_recognition_raw_detail(algorithm, raw_detail)
383
+ algorithm_str = algorithm.get()
384
+ parsed_detail = self._parse_recognition_raw_detail(algorithm_str, raw_detail)
385
+
386
+ try:
387
+ algorithm_enum = AlgorithmEnum(algorithm_str)
388
+ except ValueError:
389
+ algorithm_enum = algorithm_str # type: ignore
231
390
 
232
391
  return RecognitionDetail(
233
392
  reco_id=reco_id,
234
393
  name=name.get(),
235
- algorithm=algorithm,
394
+ algorithm=algorithm_enum,
395
+ hit=bool(hit),
236
396
  box=bool(hit) and box.get() or None,
237
397
  all_results=parsed_detail[0],
238
- filterd_results=parsed_detail[1],
398
+ filtered_results=parsed_detail[1],
239
399
  best_result=parsed_detail[2],
240
400
  raw_detail=raw_detail,
241
401
  raw_image=raw.get(),
242
402
  draw_images=draws.get(),
243
403
  )
244
404
 
405
+ def get_action_detail(self, action_id: int) -> Optional[ActionDetail]:
406
+ """获取操作信息 / Get action info
407
+
408
+ Args:
409
+ action_id: 操作号 / Action id
410
+
411
+ Returns:
412
+ Optional[ActionDetail]: 操作详情,如果不存在则返回 None / Action detail, or None if not exists
413
+ """
414
+ name = StringBuffer()
415
+ action = StringBuffer()
416
+ box = RectBuffer()
417
+ c_success = MaaBool()
418
+ detail_json = StringBuffer()
419
+
420
+ ret = bool(
421
+ Library.framework().MaaTaskerGetActionDetail(
422
+ self._handle,
423
+ MaaActId(action_id),
424
+ name._handle,
425
+ action._handle,
426
+ box._handle,
427
+ ctypes.pointer(c_success),
428
+ detail_json._handle,
429
+ )
430
+ )
431
+
432
+ if not ret:
433
+ return None
434
+
435
+ raw_detail = json.loads(detail_json.get())
436
+ action_str = action.get()
437
+ parsed_result = Tasker._parse_action_raw_detail(action_str, raw_detail)
438
+
439
+ try:
440
+ action_enum = ActionEnum(action_str)
441
+ except ValueError:
442
+ action_enum = action_str # type: ignore
443
+
444
+ return ActionDetail(
445
+ action_id=action_id,
446
+ name=name.get(),
447
+ action=action_enum,
448
+ box=box.get(),
449
+ success=bool(c_success),
450
+ result=parsed_result,
451
+ raw_detail=raw_detail,
452
+ )
453
+
245
454
  def get_node_detail(self, node_id: int) -> Optional[NodeDetail]:
455
+ """获取节点信息 / Get node info
456
+
457
+ Args:
458
+ node_id: 节点号 / Node id
459
+
460
+ Returns:
461
+ Optional[NodeDetail]: 节点详情,如果不存在则返回 None / Node detail, or None if not exists
462
+ """
246
463
  name = StringBuffer()
247
464
  c_reco_id = MaaRecoId()
465
+ c_action_id = MaaActId()
248
466
  c_completed = MaaBool()
249
467
 
250
468
  ret = bool(
@@ -253,6 +471,7 @@ class Tasker:
253
471
  MaaNodeId(node_id),
254
472
  name._handle,
255
473
  ctypes.pointer(c_reco_id),
474
+ ctypes.pointer(c_action_id),
256
475
  ctypes.pointer(c_completed),
257
476
  )
258
477
  )
@@ -260,18 +479,34 @@ class Tasker:
260
479
  if not ret:
261
480
  return None
262
481
 
263
- recognition = self.get_recognition_detail(int(c_reco_id.value))
264
- if not recognition:
265
- return None
482
+ recognition = (
483
+ self.get_recognition_detail(int(c_reco_id.value))
484
+ if c_reco_id.value != 0
485
+ else None
486
+ )
487
+ action = (
488
+ self.get_action_detail(int(c_action_id.value))
489
+ if c_action_id.value != 0
490
+ else None
491
+ )
266
492
 
267
493
  return NodeDetail(
268
494
  node_id=node_id,
269
495
  name=name.get(),
270
496
  recognition=recognition,
497
+ action=action,
271
498
  completed=bool(c_completed),
272
499
  )
273
500
 
274
501
  def get_task_detail(self, task_id: int) -> Optional[TaskDetail]:
502
+ """获取任务信息 / Get task info
503
+
504
+ Args:
505
+ task_id: 任务号 / Task id
506
+
507
+ Returns:
508
+ Optional[TaskDetail]: 任务详情,如果不存在则返回 None / Task detail, or None if not exists
509
+ """
275
510
  size = MaaSize()
276
511
  entry = StringBuffer()
277
512
  status = MaaStatus()
@@ -311,33 +546,239 @@ class Tasker:
311
546
  task_id=task_id, entry=entry.get(), nodes=nodes, status=Status(status)
312
547
  )
313
548
 
314
- _api_properties_initialized: bool = False
549
+ @staticmethod
550
+ def set_log_dir(path: Union[Path, str]) -> bool:
551
+ """设置日志路径 / Set the log path
552
+
553
+ Args:
554
+ path: 日志路径 / Log path
555
+
556
+ Returns:
557
+ bool: 是否成功 / Whether successful
558
+ """
559
+ strpath = str(path)
560
+ return bool(
561
+ Library.framework().MaaGlobalSetOption(
562
+ MaaOption(MaaGlobalOptionEnum.LogDir),
563
+ strpath.encode(),
564
+ len(strpath),
565
+ )
566
+ )
315
567
 
316
568
  @staticmethod
317
- def _parse_recognition_raw_detail(algorithm: AlgorithmEnum, raw_detail: Dict):
569
+ def set_save_draw(save_draw: bool) -> bool:
570
+ """设置是否将识别保存到日志路径/vision中 / Set whether to save recognition results to log path/vision
571
+
572
+ 开启后 RecoDetail 将可以获取到 draws / When enabled, RecoDetail can retrieve draws
573
+
574
+ Args:
575
+ save_draw: 是否保存 / Whether to save
576
+
577
+ Returns:
578
+ bool: 是否成功 / Whether successful
579
+ """
580
+ cbool = ctypes.c_bool(save_draw)
581
+ return bool(
582
+ Library.framework().MaaGlobalSetOption(
583
+ MaaOption(MaaGlobalOptionEnum.SaveDraw),
584
+ ctypes.pointer(cbool),
585
+ ctypes.sizeof(ctypes.c_bool),
586
+ )
587
+ )
588
+
589
+ @staticmethod
590
+ def set_recording(recording: bool) -> bool:
591
+ """
592
+ Deprecated
593
+ """
594
+ return False
595
+
596
+ @staticmethod
597
+ def set_stdout_level(level: LoggingLevelEnum) -> bool:
598
+ """设置日志输出到 stdout 中的级别 / Set the log output level to stdout
599
+
600
+ Args:
601
+ level: 日志级别 / Logging level
602
+
603
+ Returns:
604
+ bool: 是否成功 / Whether successful
605
+ """
606
+ clevel = MaaLoggingLevel(level)
607
+ return bool(
608
+ Library.framework().MaaGlobalSetOption(
609
+ MaaOption(MaaGlobalOptionEnum.StdoutLevel),
610
+ ctypes.pointer(clevel),
611
+ ctypes.sizeof(MaaLoggingLevel),
612
+ )
613
+ )
614
+
615
+ @staticmethod
616
+ def set_debug_mode(debug_mode: bool) -> bool:
617
+ """设置是否启用调试模式 / Set whether to enable debug mode
618
+
619
+ 调试模式下, RecoDetail 将可以获取到 raw/draws; 所有任务都会被视为 focus 而产生回调
620
+ In debug mode, RecoDetail can retrieve raw/draws; all tasks are treated as focus and produce callbacks
621
+
622
+ Args:
623
+ debug_mode: 是否启用调试模式 / Whether to enable debug mode
624
+
625
+ Returns:
626
+ bool: 是否成功 / Whether successful
627
+ """
628
+ cbool = ctypes.c_bool(debug_mode)
629
+ return bool(
630
+ Library.framework().MaaGlobalSetOption(
631
+ MaaOption(MaaGlobalOptionEnum.DebugMode),
632
+ ctypes.pointer(cbool),
633
+ ctypes.sizeof(ctypes.c_bool),
634
+ )
635
+ )
636
+
637
+ @staticmethod
638
+ def set_save_on_error(save_on_error: bool) -> bool:
639
+ """设置是否在错误时保存截图到日志路径/on_error中 / Set whether to save screenshot on error to log path/on_error
640
+
641
+ Args:
642
+ save_on_error: 是否保存 / Whether to save
643
+
644
+ Returns:
645
+ bool: 是否成功 / Whether successful
646
+ """
647
+ cbool = ctypes.c_bool(save_on_error)
648
+ return bool(
649
+ Library.framework().MaaGlobalSetOption(
650
+ MaaOption(MaaGlobalOptionEnum.SaveOnError),
651
+ ctypes.pointer(cbool),
652
+ ctypes.sizeof(ctypes.c_bool),
653
+ )
654
+ )
655
+
656
+ @staticmethod
657
+ def set_draw_quality(quality: int) -> bool:
658
+ """设置识别可视化图像的 JPEG 质量 / Set the JPEG quality for recognition visualization images
659
+
660
+ Args:
661
+ quality: JPEG 质量(0-100),默认 85 / JPEG quality (0-100), default 85
662
+
663
+ Returns:
664
+ bool: 是否成功 / Whether successful
665
+ """
666
+ cquality = ctypes.c_int(quality)
667
+ return bool(
668
+ Library.framework().MaaGlobalSetOption(
669
+ MaaOption(MaaGlobalOptionEnum.DrawQuality),
670
+ ctypes.pointer(cquality),
671
+ ctypes.sizeof(ctypes.c_int),
672
+ )
673
+ )
674
+
675
+ @staticmethod
676
+ def set_reco_image_cache_limit(limit: int) -> bool:
677
+ """设置识别图像缓存数量限制 / Set the recognition image cache limit
678
+
679
+ Args:
680
+ limit: 缓存数量限制,默认 4096 / Cache limit, default 4096
681
+
682
+ Returns:
683
+ bool: 是否成功 / Whether successful
684
+ """
685
+ climit = ctypes.c_size_t(limit)
686
+ return bool(
687
+ Library.framework().MaaGlobalSetOption(
688
+ MaaOption(MaaGlobalOptionEnum.RecoImageCacheLimit),
689
+ ctypes.pointer(climit),
690
+ ctypes.sizeof(ctypes.c_size_t),
691
+ )
692
+ )
693
+
694
+ @staticmethod
695
+ def load_plugin(path: Union[Path, str]) -> bool:
696
+ """加载插件 / Load plugin
697
+
698
+ 可以使用完整路径或仅使用名称, 仅使用名称时会在系统目录和当前目录中搜索. 也可以递归搜索目录中的插件
699
+ Can use full path or name only. When using name only, will search in system directory and current directory. Can also recursively search for plugins in a directory
700
+
701
+ Args:
702
+ path: 插件库路径或名称 / Plugin library path or name
703
+
704
+ Returns:
705
+ bool: 是否成功 / Whether successful
706
+ """
707
+ strpath = str(path)
708
+ return bool(
709
+ Library.framework().MaaGlobalLoadPlugin(
710
+ strpath.encode(),
711
+ )
712
+ )
713
+
714
+ _api_properties_initialized: bool = False
715
+
716
+ def _parse_recognition_raw_detail(self, algorithm: str, raw_detail):
318
717
  if not raw_detail:
319
718
  return [], [], None
320
719
 
321
- ResultType = AlgorithmResultDict[algorithm]
720
+ try:
721
+ algorithm_enum = AlgorithmEnum(algorithm)
722
+ except ValueError:
723
+ return [], [], None
724
+
725
+ ResultType = AlgorithmResultDict.get(algorithm_enum)
322
726
  if not ResultType:
323
727
  return [], [], None
324
728
 
729
+ # And/Or 的 detail 是子识别结果数组,递归获取完整的 RecognitionDetail
730
+ if algorithm_enum in (AlgorithmEnum.And, AlgorithmEnum.Or):
731
+ sub_results = []
732
+ for sub in raw_detail:
733
+ reco_id = sub.get("reco_id")
734
+ if not reco_id:
735
+ continue
736
+ sub_detail = self.get_recognition_detail(reco_id)
737
+ if sub_detail:
738
+ sub_results.append(sub_detail)
739
+ result = ResultType(sub_results=sub_results)
740
+ return [result], [result], result
741
+
325
742
  all_results: List[RecognitionResult] = []
326
- filterd_results: List[RecognitionResult] = []
743
+ filtered_results: List[RecognitionResult] = []
327
744
  best_result: Optional[RecognitionResult] = None
328
745
 
329
746
  raw_all_results = raw_detail.get("all", [])
330
- raw_filterd_results = raw_detail.get("filtered", [])
747
+ raw_filtered_results = raw_detail.get("filtered", [])
331
748
  raw_best_result = raw_detail.get("best", None)
332
749
 
333
750
  for raw_result in raw_all_results:
334
751
  all_results.append(ResultType(**raw_result))
335
- for raw_result in raw_filterd_results:
336
- filterd_results.append(ResultType(**raw_result))
752
+ for raw_result in raw_filtered_results:
753
+ filtered_results.append(ResultType(**raw_result))
337
754
  if raw_best_result:
338
755
  best_result = ResultType(**raw_best_result)
339
756
 
340
- return all_results, filterd_results, best_result
757
+ return all_results, filtered_results, best_result
758
+
759
+ @staticmethod
760
+ def _parse_action_raw_detail(
761
+ action: str, raw_detail: Dict
762
+ ) -> Optional[ActionResult]:
763
+ if not raw_detail:
764
+ return None
765
+
766
+ try:
767
+ action_enum = ActionEnum(action)
768
+ except ValueError:
769
+ return None
770
+
771
+ ResultType = ActionResultDict.get(action_enum)
772
+ if not ResultType:
773
+ return None
774
+
775
+ try:
776
+ # cv::Point 在 JSON 中是数组 [x, y],不需要转换
777
+ # 直接使用 raw_detail 创建结果对象
778
+ return ResultType(**raw_detail)
779
+ except (TypeError, KeyError):
780
+ # 如果解析失败,返回 None
781
+ return None
341
782
 
342
783
  @staticmethod
343
784
  def _set_api_properties():
@@ -345,12 +786,21 @@ class Tasker:
345
786
  return
346
787
  Tasker._api_properties_initialized = True
347
788
 
348
- Library.framework().MaaTaskerCreate.restype = MaaTaskerHandle
349
- Library.framework().MaaTaskerCreate.argtypes = [
350
- MaaNotificationCallback,
351
- ctypes.c_void_p,
789
+ Library.framework().MaaGlobalSetOption.restype = MaaBool
790
+ Library.framework().MaaGlobalSetOption.argtypes = [
791
+ MaaGlobalOption,
792
+ MaaOptionValue,
793
+ MaaOptionValueSize,
794
+ ]
795
+
796
+ Library.framework().MaaGlobalLoadPlugin.restype = MaaBool
797
+ Library.framework().MaaGlobalLoadPlugin.argtypes = [
798
+ ctypes.c_char_p,
352
799
  ]
353
800
 
801
+ Library.framework().MaaTaskerCreate.restype = MaaTaskerHandle
802
+ Library.framework().MaaTaskerCreate.argtypes = []
803
+
354
804
  Library.framework().MaaTaskerDestroy.restype = None
355
805
  Library.framework().MaaTaskerDestroy.argtypes = [MaaTaskerHandle]
356
806
 
@@ -376,6 +826,23 @@ class Tasker:
376
826
  ctypes.c_char_p,
377
827
  ]
378
828
 
829
+ Library.framework().MaaTaskerPostRecognition.restype = MaaId
830
+ Library.framework().MaaTaskerPostRecognition.argtypes = [
831
+ MaaTaskerHandle,
832
+ ctypes.c_char_p,
833
+ ctypes.c_char_p,
834
+ MaaImageBufferHandle,
835
+ ]
836
+
837
+ Library.framework().MaaTaskerPostAction.restype = MaaId
838
+ Library.framework().MaaTaskerPostAction.argtypes = [
839
+ MaaTaskerHandle,
840
+ ctypes.c_char_p,
841
+ ctypes.c_char_p,
842
+ MaaRectHandle,
843
+ ctypes.c_char_p,
844
+ ]
845
+
379
846
  Library.framework().MaaTaskerStatus.restype = MaaStatus
380
847
  Library.framework().MaaTaskerStatus.argtypes = [
381
848
  MaaTaskerHandle,
@@ -416,12 +883,24 @@ class Tasker:
416
883
  MaaImageListBufferHandle,
417
884
  ]
418
885
 
886
+ Library.framework().MaaTaskerGetActionDetail.restype = MaaBool
887
+ Library.framework().MaaTaskerGetActionDetail.argtypes = [
888
+ MaaTaskerHandle,
889
+ MaaActId,
890
+ MaaStringBufferHandle,
891
+ MaaStringBufferHandle,
892
+ MaaRectHandle,
893
+ ctypes.POINTER(MaaBool),
894
+ MaaStringBufferHandle,
895
+ ]
896
+
419
897
  Library.framework().MaaTaskerGetNodeDetail.restype = MaaBool
420
898
  Library.framework().MaaTaskerGetNodeDetail.argtypes = [
421
899
  MaaTaskerHandle,
422
900
  MaaNodeId,
423
901
  MaaStringBufferHandle,
424
902
  ctypes.POINTER(MaaRecoId),
903
+ ctypes.POINTER(MaaActId),
425
904
  ctypes.POINTER(MaaBool),
426
905
  ]
427
906
 
@@ -447,9 +926,70 @@ class Tasker:
447
926
  MaaTaskerHandle,
448
927
  ]
449
928
 
450
- Library.framework().MaaSetGlobalOption.restype = MaaBool
451
- Library.framework().MaaSetGlobalOption.argtypes = [
452
- MaaGlobalOption,
453
- MaaOptionValue,
454
- MaaOptionValueSize,
929
+ Library.framework().MaaTaskerAddSink.restype = MaaSinkId
930
+ Library.framework().MaaTaskerAddSink.argtypes = [
931
+ MaaTaskerHandle,
932
+ MaaEventCallback,
933
+ ctypes.c_void_p,
934
+ ]
935
+
936
+ Library.framework().MaaTaskerRemoveSink.restype = None
937
+ Library.framework().MaaTaskerRemoveSink.argtypes = [
938
+ MaaTaskerHandle,
939
+ MaaSinkId,
455
940
  ]
941
+
942
+ Library.framework().MaaTaskerClearSinks.restype = None
943
+ Library.framework().MaaTaskerClearSinks.argtypes = [MaaTaskerHandle]
944
+
945
+ Library.framework().MaaTaskerAddContextSink.restype = MaaSinkId
946
+ Library.framework().MaaTaskerAddContextSink.argtypes = [
947
+ MaaTaskerHandle,
948
+ MaaEventCallback,
949
+ ctypes.c_void_p,
950
+ ]
951
+
952
+ Library.framework().MaaTaskerRemoveContextSink.restype = None
953
+ Library.framework().MaaTaskerRemoveContextSink.argtypes = [
954
+ MaaTaskerHandle,
955
+ MaaSinkId,
956
+ ]
957
+
958
+ Library.framework().MaaTaskerClearContextSinks.restype = None
959
+ Library.framework().MaaTaskerClearContextSinks.argtypes = [MaaTaskerHandle]
960
+
961
+
962
+ class TaskerEventSink(EventSink):
963
+
964
+ @dataclass
965
+ class TaskerTaskDetail:
966
+ task_id: int
967
+ entry: str
968
+ uuid: str
969
+ hash: str
970
+
971
+ def on_tasker_task(
972
+ self, tasker: Tasker, noti_type: NotificationType, detail: TaskerTaskDetail
973
+ ):
974
+ pass
975
+
976
+ def on_raw_notification(self, tasker: Tasker, msg: str, details: dict):
977
+ pass
978
+
979
+ def _on_raw_notification(self, handle: ctypes.c_void_p, msg: str, details: dict):
980
+
981
+ tasker = Tasker(handle=handle)
982
+ self.on_raw_notification(tasker, msg, details)
983
+
984
+ noti_type = EventSink._notification_type(msg)
985
+ if msg.startswith("Tasker.Task"):
986
+ detail = self.TaskerTaskDetail(
987
+ task_id=details["task_id"],
988
+ entry=details["entry"],
989
+ uuid=details["uuid"],
990
+ hash=details["hash"],
991
+ )
992
+ self.on_tasker_task(tasker, noti_type, detail)
993
+
994
+ else:
995
+ self.on_unknown_notification(tasker, msg, details)