MaaFw 2.1.0__py3-none-manylinux2014_x86_64.whl → 5.4.0b1__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.
maa/tasker.py CHANGED
@@ -1,34 +1,39 @@
1
1
  import ctypes
2
+ import dataclasses
2
3
  import json
3
- import time
4
4
  from pathlib import Path
5
- from typing import Any, Dict, Optional, Union
5
+ from typing import Dict, Optional, Union
6
+
7
+ import numpy
6
8
 
7
9
  from .define import *
8
10
  from .library import Library
9
11
  from .buffer import ImageListBuffer, RectBuffer, StringBuffer, ImageBuffer
10
12
  from .job import Job, JobWithResult
11
- from .notification_handler import NotificationHandler
13
+ from .event_sink import EventSink, NotificationType
12
14
  from .resource import Resource
13
15
  from .controller import Controller
16
+ from .pipeline import JRecognitionParam, JActionParam, JRecognitionType, JActionType
14
17
 
15
18
 
16
19
  class Tasker:
17
- _notification_handler: Optional[NotificationHandler]
18
20
  _handle: MaaTaskerHandle
19
- _own: bool = False
21
+ _own: bool
20
22
 
21
23
  ### public ###
22
24
 
23
25
  def __init__(
24
26
  self,
25
- notification_handler: Optional[NotificationHandler] = None,
26
27
  handle: Optional[MaaTaskerHandle] = None,
27
28
  ):
28
- if not Library.initialized:
29
- raise RuntimeError(
30
- "Library not initialized, please call `library.open()` first."
31
- )
29
+ """创建实例 / Create instance
30
+
31
+ Args:
32
+ handle: 可选的外部句柄 / Optional external handle
33
+
34
+ Raises:
35
+ RuntimeError: 如果创建失败
36
+ """
32
37
 
33
38
  self._set_api_properties()
34
39
 
@@ -36,10 +41,7 @@ class Tasker:
36
41
  self._handle = handle
37
42
  self._own = False
38
43
  else:
39
- self._notification_handler = notification_handler
40
- self._handle = Library.framework.MaaTaskerCreate(
41
- *NotificationHandler._gen_c_param(self._notification_handler)
42
- )
44
+ self._handle = Library.framework().MaaTaskerCreate()
43
45
  self._own = True
44
46
 
45
47
  if not self._handle:
@@ -47,22 +49,41 @@ class Tasker:
47
49
 
48
50
  def __del__(self):
49
51
  if self._handle and self._own:
50
- Library.framework.MaaTaskerDestroy(self._handle)
52
+ Library.framework().MaaTaskerDestroy(self._handle)
51
53
 
52
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
+ """
53
64
  # avoid gc
54
65
  self._resource_holder = resource
55
66
  self._controller_holder = controller
56
67
 
57
68
  return bool(
58
- Library.framework.MaaTaskerBindResource(self._handle, resource._handle)
69
+ Library.framework().MaaTaskerBindResource(self._handle, resource._handle)
59
70
  ) and bool(
60
- Library.framework.MaaTaskerBindController(self._handle, controller._handle)
71
+ Library.framework().MaaTaskerBindController(
72
+ self._handle, controller._handle
73
+ )
61
74
  )
62
75
 
63
76
  @property
64
77
  def resource(self) -> Resource:
65
- resource_handle = Library.framework.MaaTaskerGetResource(self._handle)
78
+ """获取关联的资源 / Get bound resource
79
+
80
+ Returns:
81
+ Resource: 资源对象 / Resource object
82
+
83
+ Raises:
84
+ RuntimeError: 如果获取失败
85
+ """
86
+ resource_handle = Library.framework().MaaTaskerGetResource(self._handle)
66
87
  if not resource_handle:
67
88
  raise RuntimeError("Failed to get resource.")
68
89
 
@@ -70,7 +91,15 @@ class Tasker:
70
91
 
71
92
  @property
72
93
  def controller(self) -> Controller:
73
- controller_handle = Library.framework.MaaTaskerGetController(self._handle)
94
+ """获取关联的控制器 / Get bound controller
95
+
96
+ Returns:
97
+ Controller: 控制器对象 / Controller object
98
+
99
+ Raises:
100
+ RuntimeError: 如果获取失败
101
+ """
102
+ controller_handle = Library.framework().MaaTaskerGetController(self._handle)
74
103
  if not controller_handle:
75
104
  raise RuntimeError("Failed to get controller.")
76
105
 
@@ -78,27 +107,135 @@ class Tasker:
78
107
 
79
108
  @property
80
109
  def inited(self) -> bool:
81
- return bool(Library.framework.MaaTaskerInited(self._handle))
110
+ """判断是否正确初始化 / Check if initialized correctly
111
+
112
+ Returns:
113
+ bool: 是否已正确初始化 / Whether correctly initialized
114
+ """
115
+ return bool(Library.framework().MaaTaskerInited(self._handle))
116
+
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
82
122
 
83
- def post_pipeline(self, entry: str, pipeline_override: Dict = {}) -> JobWithResult:
84
- taskid = Library.framework.MaaTaskerPostPipeline(
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
+ """
130
+ taskid = Library.framework().MaaTaskerPostTask(
85
131
  self._handle,
86
132
  *Tasker._gen_post_param(entry, pipeline_override),
87
133
  )
88
134
  return self._gen_task_job(taskid)
89
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
+
90
195
  @property
91
196
  def running(self) -> bool:
92
- return bool(Library.framework.MaaTaskerRunning(self._handle))
197
+ """判断实例是否还在运行 / Check if instance is still running
198
+
199
+ Returns:
200
+ bool: 是否正在运行 / Whether running
201
+ """
202
+ return bool(Library.framework().MaaTaskerRunning(self._handle))
93
203
 
94
204
  def post_stop(self) -> Job:
95
- Library.framework.MaaTaskerPostStop(self._handle)
96
- return Job(MaaId(0), self._stop_status, self._stop_wait)
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
+ """
215
+ taskid = Library.framework().MaaTaskerPostStop(self._handle)
216
+ return self._gen_task_job(taskid)
217
+
218
+ @property
219
+ def stopping(self) -> bool:
220
+ """判断实例是否正在停止中(尚未停止) / Check if instance is stopping (not yet stopped)
221
+
222
+ Returns:
223
+ bool: 是否正在停止 / Whether stopping
224
+ """
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
- Library.framework.MaaTaskerGetLatestNode(
238
+ Library.framework().MaaTaskerGetLatestNode(
102
239
  self._handle,
103
240
  name.encode(),
104
241
  ctypes.pointer(c_node_id),
@@ -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:
113
- return bool(Library.framework.MaaTaskerClearCache(self._handle))
250
+ """清理所有可查询的信息 / Clear all queryable information
114
251
 
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
- )
252
+ Returns:
253
+ bool: 是否成功 / Whether successful
254
+ """
255
+ return bool(Library.framework().MaaTaskerClearCache(self._handle))
125
256
 
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
- )
257
+ _sink_holder: Dict[int, "EventSink"] = {}
136
258
 
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
- )
259
+ def add_sink(self, sink: "TaskerEventSink") -> Optional[int]:
260
+ """添加实例事件监听器 / Add instance event listener
147
261
 
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),
156
- )
157
- )
262
+ Args:
263
+ sink: 事件监听器 / Event sink
158
264
 
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),
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)
167
271
  )
168
272
  )
273
+ if sink_id == MaaInvalidId:
274
+ return None
169
275
 
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),
276
+ self._sink_holder[sink_id] = sink
277
+ return sink_id
278
+
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:
@@ -196,30 +342,29 @@ class Tasker:
196
342
  )
197
343
 
198
344
  def _task_status(self, id: int) -> ctypes.c_int32:
199
- return Library.framework.MaaTaskerStatus(self._handle, id)
345
+ return Library.framework().MaaTaskerStatus(self._handle, id)
200
346
 
201
347
  def _task_wait(self, id: int) -> ctypes.c_int32:
202
- return Library.framework.MaaTaskerWait(self._handle, id)
348
+ return Library.framework().MaaTaskerWait(self._handle, id)
203
349
 
204
- def _stop_status(self, id: int) -> MaaStatusEnum:
205
- return MaaStatusEnum.succeeded if not self.running else MaaStatusEnum.running
350
+ def get_recognition_detail(self, reco_id: int) -> Optional[RecognitionDetail]:
351
+ """获取识别信息 / Get recognition info
206
352
 
207
- def _stop_wait(self, id: int) -> MaaStatusEnum:
208
- # TODO: 这个应该由 callback 来处理
209
- while self.running:
210
- time.sleep(0.1)
211
- return MaaStatusEnum.succeeded
353
+ Args:
354
+ reco_id: 识别号 / Recognition id
212
355
 
213
- def get_recognition_detail(self, reco_id: int) -> Optional[RecognitionDetail]:
356
+ Returns:
357
+ Optional[RecognitionDetail]: 识别详情,如果不存在则返回 None / Recognition detail, or None if not exists
358
+ """
214
359
  name = StringBuffer()
215
- algorithm = StringBuffer()
360
+ algorithm = StringBuffer() # type: ignore
216
361
  hit = MaaBool()
217
362
  box = RectBuffer()
218
363
  detail_json = StringBuffer()
219
364
  raw = ImageBuffer()
220
365
  draws = ImageListBuffer()
221
366
  ret = bool(
222
- Library.framework.MaaTaskerGetRecognitionDetail(
367
+ Library.framework().MaaTaskerGetRecognitionDetail(
223
368
  self._handle,
224
369
  MaaRecoId(reco_id),
225
370
  name._handle,
@@ -235,33 +380,98 @@ class Tasker:
235
380
  return None
236
381
 
237
382
  raw_detail = json.loads(detail_json.get())
238
- algorithm: AlgorithmEnum = AlgorithmEnum(algorithm.get())
239
- 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
240
390
 
241
391
  return RecognitionDetail(
242
392
  reco_id=reco_id,
243
393
  name=name.get(),
244
- algorithm=algorithm,
394
+ algorithm=algorithm_enum,
395
+ hit=bool(hit),
245
396
  box=bool(hit) and box.get() or None,
246
397
  all_results=parsed_detail[0],
247
- filterd_results=parsed_detail[1],
398
+ filtered_results=parsed_detail[1],
248
399
  best_result=parsed_detail[2],
249
400
  raw_detail=raw_detail,
250
401
  raw_image=raw.get(),
251
402
  draw_images=draws.get(),
252
403
  )
253
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
+
254
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
+ """
255
463
  name = StringBuffer()
256
464
  c_reco_id = MaaRecoId()
465
+ c_action_id = MaaActId()
257
466
  c_completed = MaaBool()
258
467
 
259
468
  ret = bool(
260
- Library.framework.MaaTaskerGetNodeDetail(
469
+ Library.framework().MaaTaskerGetNodeDetail(
261
470
  self._handle,
262
471
  MaaNodeId(node_id),
263
472
  name._handle,
264
473
  ctypes.pointer(c_reco_id),
474
+ ctypes.pointer(c_action_id),
265
475
  ctypes.pointer(c_completed),
266
476
  )
267
477
  )
@@ -269,27 +479,45 @@ class Tasker:
269
479
  if not ret:
270
480
  return None
271
481
 
272
- recognition = self.get_recognition_detail(int(c_reco_id.value))
273
- if not recognition:
274
- 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
+ )
275
492
 
276
493
  return NodeDetail(
277
494
  node_id=node_id,
278
495
  name=name.get(),
279
496
  recognition=recognition,
497
+ action=action,
280
498
  completed=bool(c_completed),
281
499
  )
282
500
 
283
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
+ """
284
510
  size = MaaSize()
285
511
  entry = StringBuffer()
512
+ status = MaaStatus()
286
513
  ret = bool(
287
- Library.framework.MaaTaskerGetTaskDetail(
514
+ Library.framework().MaaTaskerGetTaskDetail(
288
515
  self._handle,
289
516
  MaaTaskId(task_id),
290
517
  entry._handle,
291
518
  None,
292
519
  ctypes.pointer(size),
520
+ ctypes.pointer(status),
293
521
  )
294
522
  )
295
523
  if not ret:
@@ -297,12 +525,13 @@ class Tasker:
297
525
 
298
526
  c_node_id_list = (MaaNodeId * size.value)()
299
527
  ret = bool(
300
- Library.framework.MaaTaskerGetTaskDetail(
528
+ Library.framework().MaaTaskerGetTaskDetail(
301
529
  self._handle,
302
530
  MaaTaskId(task_id),
303
531
  entry._handle,
304
532
  c_node_id_list,
305
533
  ctypes.pointer(size),
534
+ ctypes.pointer(status),
306
535
  )
307
536
  )
308
537
  if not ret:
@@ -313,35 +542,243 @@ class Tasker:
313
542
  detail = self.get_node_detail(int(c_node_id_list[i]))
314
543
  nodes.append(detail)
315
544
 
316
- return TaskDetail(task_id=task_id, entry=entry.get(), nodes=nodes)
545
+ return TaskDetail(
546
+ task_id=task_id, entry=entry.get(), nodes=nodes, status=Status(status)
547
+ )
317
548
 
318
- _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
+ )
319
567
 
320
568
  @staticmethod
321
- 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):
322
717
  if not raw_detail:
323
718
  return [], [], None
324
719
 
325
- ResultType = AlgorithmResultDict[algorithm]
720
+ try:
721
+ algorithm_enum = AlgorithmEnum(algorithm)
722
+ except ValueError:
723
+ return [], [], None
724
+
725
+ ResultType = AlgorithmResultDict.get(algorithm_enum)
326
726
  if not ResultType:
327
727
  return [], [], None
328
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
+
329
742
  all_results: List[RecognitionResult] = []
330
- filterd_results: List[RecognitionResult] = []
743
+ filtered_results: List[RecognitionResult] = []
331
744
  best_result: Optional[RecognitionResult] = None
332
745
 
333
746
  raw_all_results = raw_detail.get("all", [])
334
- raw_filterd_results = raw_detail.get("filtered", [])
747
+ raw_filtered_results = raw_detail.get("filtered", [])
335
748
  raw_best_result = raw_detail.get("best", None)
336
749
 
337
750
  for raw_result in raw_all_results:
338
751
  all_results.append(ResultType(**raw_result))
339
- for raw_result in raw_filterd_results:
340
- filterd_results.append(ResultType(**raw_result))
752
+ for raw_result in raw_filtered_results:
753
+ filtered_results.append(ResultType(**raw_result))
341
754
  if raw_best_result:
342
755
  best_result = ResultType(**raw_best_result)
343
756
 
344
- 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
345
782
 
346
783
  @staticmethod
347
784
  def _set_api_properties():
@@ -349,63 +786,92 @@ class Tasker:
349
786
  return
350
787
  Tasker._api_properties_initialized = True
351
788
 
352
- Library.framework.MaaTaskerCreate.restype = MaaTaskerHandle
353
- Library.framework.MaaTaskerCreate.argtypes = [
354
- MaaNotificationCallback,
355
- 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,
356
799
  ]
357
800
 
358
- Library.framework.MaaTaskerDestroy.restype = None
359
- Library.framework.MaaTaskerDestroy.argtypes = [MaaTaskerHandle]
801
+ Library.framework().MaaTaskerCreate.restype = MaaTaskerHandle
802
+ Library.framework().MaaTaskerCreate.argtypes = []
803
+
804
+ Library.framework().MaaTaskerDestroy.restype = None
805
+ Library.framework().MaaTaskerDestroy.argtypes = [MaaTaskerHandle]
360
806
 
361
- Library.framework.MaaTaskerBindResource.restype = MaaBool
362
- Library.framework.MaaTaskerBindResource.argtypes = [
807
+ Library.framework().MaaTaskerBindResource.restype = MaaBool
808
+ Library.framework().MaaTaskerBindResource.argtypes = [
363
809
  MaaTaskerHandle,
364
810
  MaaResourceHandle,
365
811
  ]
366
812
 
367
- Library.framework.MaaTaskerBindController.restype = MaaBool
368
- Library.framework.MaaTaskerBindController.argtypes = [
813
+ Library.framework().MaaTaskerBindController.restype = MaaBool
814
+ Library.framework().MaaTaskerBindController.argtypes = [
369
815
  MaaTaskerHandle,
370
816
  MaaControllerHandle,
371
817
  ]
372
818
 
373
- Library.framework.MaaTaskerInited.restype = MaaBool
374
- Library.framework.MaaTaskerInited.argtypes = [MaaTaskerHandle]
819
+ Library.framework().MaaTaskerInited.restype = MaaBool
820
+ Library.framework().MaaTaskerInited.argtypes = [MaaTaskerHandle]
821
+
822
+ Library.framework().MaaTaskerPostTask.restype = MaaId
823
+ Library.framework().MaaTaskerPostTask.argtypes = [
824
+ MaaTaskerHandle,
825
+ ctypes.c_char_p,
826
+ ctypes.c_char_p,
827
+ ]
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
+ ]
375
836
 
376
- Library.framework.MaaTaskerPostPipeline.restype = MaaId
377
- Library.framework.MaaTaskerPostPipeline.argtypes = [
837
+ Library.framework().MaaTaskerPostAction.restype = MaaId
838
+ Library.framework().MaaTaskerPostAction.argtypes = [
378
839
  MaaTaskerHandle,
379
840
  ctypes.c_char_p,
380
841
  ctypes.c_char_p,
842
+ MaaRectHandle,
843
+ ctypes.c_char_p,
381
844
  ]
382
845
 
383
- Library.framework.MaaTaskerStatus.restype = MaaStatus
384
- Library.framework.MaaTaskerStatus.argtypes = [
846
+ Library.framework().MaaTaskerStatus.restype = MaaStatus
847
+ Library.framework().MaaTaskerStatus.argtypes = [
385
848
  MaaTaskerHandle,
386
849
  MaaTaskId,
387
850
  ]
388
851
 
389
- Library.framework.MaaTaskerWait.restype = MaaStatus
390
- Library.framework.MaaTaskerWait.argtypes = [
852
+ Library.framework().MaaTaskerWait.restype = MaaStatus
853
+ Library.framework().MaaTaskerWait.argtypes = [
391
854
  MaaTaskerHandle,
392
855
  MaaTaskId,
393
856
  ]
394
857
 
395
- Library.framework.MaaTaskerRunning.restype = MaaBool
396
- Library.framework.MaaTaskerRunning.argtypes = [MaaTaskerHandle]
858
+ Library.framework().MaaTaskerRunning.restype = MaaBool
859
+ Library.framework().MaaTaskerRunning.argtypes = [MaaTaskerHandle]
397
860
 
398
- Library.framework.MaaTaskerPostStop.restype = MaaBool
399
- Library.framework.MaaTaskerPostStop.argtypes = [MaaTaskerHandle]
861
+ Library.framework().MaaTaskerPostStop.restype = MaaTaskId
862
+ Library.framework().MaaTaskerPostStop.argtypes = [MaaTaskerHandle]
400
863
 
401
- Library.framework.MaaTaskerGetResource.restype = MaaResourceHandle
402
- Library.framework.MaaTaskerGetResource.argtypes = [MaaTaskerHandle]
864
+ Library.framework().MaaTaskerStopping.restype = MaaBool
865
+ Library.framework().MaaTaskerStopping.argtypes = [MaaTaskerHandle]
403
866
 
404
- Library.framework.MaaTaskerGetController.restype = MaaControllerHandle
405
- Library.framework.MaaTaskerGetController.argtypes = [MaaTaskerHandle]
867
+ Library.framework().MaaTaskerGetResource.restype = MaaResourceHandle
868
+ Library.framework().MaaTaskerGetResource.argtypes = [MaaTaskerHandle]
406
869
 
407
- Library.framework.MaaTaskerGetRecognitionDetail.restype = MaaBool
408
- Library.framework.MaaTaskerGetRecognitionDetail.argtypes = [
870
+ Library.framework().MaaTaskerGetController.restype = MaaControllerHandle
871
+ Library.framework().MaaTaskerGetController.argtypes = [MaaTaskerHandle]
872
+
873
+ Library.framework().MaaTaskerGetRecognitionDetail.restype = MaaBool
874
+ Library.framework().MaaTaskerGetRecognitionDetail.argtypes = [
409
875
  MaaTaskerHandle,
410
876
  MaaRecoId,
411
877
  MaaStringBufferHandle,
@@ -417,39 +883,113 @@ class Tasker:
417
883
  MaaImageListBufferHandle,
418
884
  ]
419
885
 
420
- Library.framework.MaaTaskerGetNodeDetail.restype = MaaBool
421
- Library.framework.MaaTaskerGetNodeDetail.argtypes = [
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
+
897
+ Library.framework().MaaTaskerGetNodeDetail.restype = MaaBool
898
+ Library.framework().MaaTaskerGetNodeDetail.argtypes = [
422
899
  MaaTaskerHandle,
423
900
  MaaNodeId,
424
901
  MaaStringBufferHandle,
425
902
  ctypes.POINTER(MaaRecoId),
903
+ ctypes.POINTER(MaaActId),
426
904
  ctypes.POINTER(MaaBool),
427
905
  ]
428
906
 
429
- Library.framework.MaaTaskerGetTaskDetail.restype = MaaBool
430
- Library.framework.MaaTaskerGetTaskDetail.argtypes = [
907
+ Library.framework().MaaTaskerGetTaskDetail.restype = MaaBool
908
+ Library.framework().MaaTaskerGetTaskDetail.argtypes = [
431
909
  MaaTaskerHandle,
432
910
  MaaTaskId,
433
911
  MaaStringBufferHandle,
434
912
  ctypes.POINTER(MaaRecoId),
435
913
  ctypes.POINTER(MaaSize),
914
+ ctypes.POINTER(MaaStatus),
436
915
  ]
437
916
 
438
- Library.framework.MaaTaskerGetLatestNode.restype = MaaBool
439
- Library.framework.MaaTaskerGetLatestNode.argtypes = [
917
+ Library.framework().MaaTaskerGetLatestNode.restype = MaaBool
918
+ Library.framework().MaaTaskerGetLatestNode.argtypes = [
440
919
  MaaTaskerHandle,
441
920
  ctypes.c_char_p,
442
921
  ctypes.POINTER(MaaRecoId),
443
922
  ]
444
923
 
445
- Library.framework.MaaTaskerClearCache.restype = MaaBool
446
- Library.framework.MaaTaskerClearCache.argtypes = [
924
+ Library.framework().MaaTaskerClearCache.restype = MaaBool
925
+ Library.framework().MaaTaskerClearCache.argtypes = [
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,
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,
455
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)