MaaFw 4.5.5__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/context.py CHANGED
@@ -1,13 +1,16 @@
1
1
  import ctypes
2
2
  import json
3
- from typing import Dict, Optional, Tuple
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, Optional, Tuple
4
5
 
5
6
  import numpy
6
7
 
8
+ from .event_sink import EventSink, NotificationType
7
9
  from .buffer import ImageBuffer, RectBuffer, StringBuffer, StringListBuffer
8
10
  from .define import *
9
11
  from .library import Library
10
12
  from .tasker import Tasker
13
+ from .pipeline import JPipelineData, JPipelineParser, JNodeAttr
11
14
  from .job import JobWithResult
12
15
 
13
16
 
@@ -32,6 +35,15 @@ class Context:
32
35
  def run_task(
33
36
  self, entry: str, pipeline_override: Dict = {}
34
37
  ) -> Optional[TaskDetail]:
38
+ """同步执行任务 / Synchronously execute task
39
+
40
+ Args:
41
+ entry: 任务入口 / Task entry
42
+ pipeline_override: 用于覆盖的 json / JSON for overriding
43
+
44
+ Returns:
45
+ Optional[TaskDetail]: 任务详情,执行失败则返回 None / Task detail, or None if execution failed
46
+ """
35
47
  task_id = int(
36
48
  Library.framework().MaaContextRunTask(
37
49
  self._handle, *Context._gen_post_param(entry, pipeline_override)
@@ -43,8 +55,28 @@ class Context:
43
55
  return self.tasker.get_task_detail(task_id)
44
56
 
45
57
  def run_recognition(
46
- self, entry: str, image: numpy.ndarray, pipeline_override: Dict = {}
58
+ self,
59
+ entry: str,
60
+ image: numpy.ndarray,
61
+ pipeline_override: Dict = {},
47
62
  ) -> Optional[RecognitionDetail]:
63
+ """同步执行识别逻辑 / Synchronously execute recognition logic
64
+
65
+ 不会执行后续操作, 不会执行后续 next
66
+ Will not execute subsequent operations or next steps
67
+
68
+ Args:
69
+ entry: 任务名 / Task name
70
+ image: 前序截图 / Previous screenshot
71
+ pipeline_override: 用于覆盖的 json / JSON for overriding
72
+
73
+ Returns:
74
+ Optional[RecognitionDetail]: 识别结果。无论是否命中,只要尝试进行了识别,就会返回;
75
+ 请通过 RecognitionDetail.hit 判断是否命中。只在未能启动识别流程时(如 entry 不存在、node disabled、image 为空等),才可能返回 None。
76
+ Recognition detail. It always returns as long as recognition was attempted;
77
+ use RecognitionDetail.hit to determine hit. Only return None if the recognition process fails to start
78
+ (e.g., entry does not exist, node is disabled, image is empty).
79
+ """
48
80
  image_buffer = ImageBuffer()
49
81
  image_buffer.set(image)
50
82
  reco_id = int(
@@ -65,11 +97,29 @@ class Context:
65
97
  box: RectType = (0, 0, 0, 0),
66
98
  reco_detail: str = "",
67
99
  pipeline_override: Dict = {},
68
- ) -> Optional[NodeDetail]:
100
+ ) -> Optional[ActionDetail]:
101
+ """同步执行操作逻辑 / Synchronously execute action logic
102
+
103
+ 不会执行后续 next
104
+ Will not execute subsequent next steps
105
+
106
+ Args:
107
+ entry: 任务名 / Task name
108
+ box: 前序识别位置 / Previous recognition position
109
+ reco_detail: 前序识别详情 / Previous recognition details
110
+ pipeline_override: 用于覆盖的 json / JSON for overriding
111
+
112
+ Returns:
113
+ Optional[ActionDetail]: 操作结果。无论动作是否成功,只要尝试执行了动作,就会返回;
114
+ 请通过 ActionDetail.success 判断是否执行成功。只在未能启动动作流程时(如 entry 不存在、node disabled 等),才可能返回 None。
115
+ Action detail. It always returns as long as the action was attempted;
116
+ use ActionDetail.success to determine success. Only return None if the action flow fails to start
117
+ (e.g., entry does not exist, node is disabled, etc.).
118
+ """
69
119
  rect = RectBuffer()
70
120
  rect.set(box)
71
121
 
72
- node_id = int(
122
+ act_id = int(
73
123
  Library.framework().MaaContextRunAction(
74
124
  self._handle,
75
125
  *Context._gen_post_param(entry, pipeline_override),
@@ -78,20 +128,42 @@ class Context:
78
128
  )
79
129
  )
80
130
 
81
- if not node_id:
131
+ if not act_id:
82
132
  return None
83
133
 
84
- return self.tasker.get_node_detail(node_id)
134
+ return self.tasker.get_action_detail(act_id)
85
135
 
86
136
  def override_pipeline(self, pipeline_override: Dict) -> bool:
137
+ """覆盖 pipeline / Override pipeline_override
138
+
139
+ Args:
140
+ pipeline_override: 用于覆盖的 json / JSON for overriding
141
+
142
+ Returns:
143
+ bool: 是否成功 / Whether successful
144
+ """
145
+ pipeline_json = json.dumps(pipeline_override, ensure_ascii=False)
146
+
87
147
  return bool(
88
148
  Library.framework().MaaContextOverridePipeline(
89
149
  self._handle,
90
- json.dumps(pipeline_override, ensure_ascii=False).encode(),
150
+ pipeline_json.encode(),
91
151
  )
92
152
  )
93
153
 
94
154
  def override_next(self, name: str, next_list: List[str]) -> bool:
155
+ """覆盖任务的 next 列表 / Override the next list of task
156
+
157
+ 如果节点不存在,此方法会失败
158
+ This method will fail if the node does not exist
159
+
160
+ Args:
161
+ name: 任务名 / Task name
162
+ next_list: next 列表 / Next list
163
+
164
+ Returns:
165
+ bool: 成功返回 True,如果节点不存在则返回 False / Returns True on success, False if node does not exist
166
+ """
95
167
  list_buffer = StringListBuffer()
96
168
  list_buffer.set(next_list)
97
169
 
@@ -101,7 +173,34 @@ class Context:
101
173
  )
102
174
  )
103
175
 
104
- def get_node_data(self, name: str) -> Optional[dict]:
176
+ def override_image(self, image_name: str, image: numpy.ndarray) -> bool:
177
+ """覆盖图片 / Override the image corresponding to image_name
178
+
179
+ Args:
180
+ image_name: 图片名 / Image name
181
+ image: 图片数据 / Image data
182
+
183
+ Returns:
184
+ bool: 是否成功 / Whether successful
185
+ """
186
+ image_buffer = ImageBuffer()
187
+ image_buffer.set(image)
188
+
189
+ return bool(
190
+ Library.framework().MaaContextOverrideImage(
191
+ self._handle, image_name.encode(), image_buffer._handle
192
+ )
193
+ )
194
+
195
+ def get_node_data(self, name: str) -> Optional[Dict]:
196
+ """获取任务当前的定义 / Get the current definition of task
197
+
198
+ Args:
199
+ name: 任务名 / Task name
200
+
201
+ Returns:
202
+ Optional[Dict]: 任务定义字典,如果不存在则返回 None / Task definition dict, or None if not exists
203
+ """
105
204
  string_buffer = StringBuffer()
106
205
  if not Library.framework().MaaContextGetNodeData(
107
206
  self._handle, name.encode(), string_buffer._handle
@@ -117,11 +216,40 @@ class Context:
117
216
  except json.JSONDecodeError:
118
217
  return None
119
218
 
219
+ def get_node_object(self, name: str) -> Optional[JPipelineData]:
220
+ """获取任务当前的定义(解析为对象) / Get the current definition of task (parsed as object)
221
+
222
+ Args:
223
+ name: 任务名 / Task name
224
+
225
+ Returns:
226
+ Optional[JPipelineData]: 任务定义对象,如果不存在则返回 None / Task definition object, or None if not exists
227
+ """
228
+ node_data = self.get_node_data(name)
229
+
230
+ if not node_data:
231
+ return None
232
+
233
+ return JPipelineParser.parse_pipeline_data(node_data)
234
+
120
235
  @property
121
236
  def tasker(self) -> Tasker:
237
+ """获取实例 / Get instance
238
+
239
+ Returns:
240
+ Tasker: 实例对象 / Instance object
241
+ """
122
242
  return self._tasker
123
243
 
124
244
  def get_task_job(self) -> JobWithResult:
245
+ """获取对应任务号的任务作业 / Get task job for corresponding task id
246
+
247
+ Returns:
248
+ JobWithResult: 任务作业对象 / Task job object
249
+
250
+ Raises:
251
+ ValueError: 如果任务 id 为 None
252
+ """
125
253
  task_id = Library.framework().MaaContextGetTaskId(self._handle)
126
254
  if not task_id:
127
255
  raise ValueError("task_id is None")
@@ -129,12 +257,84 @@ class Context:
129
257
  return self.tasker._gen_task_job(task_id)
130
258
 
131
259
  def clone(self) -> "Context":
260
+ """复制上下文 / Clone context
261
+
262
+ Returns:
263
+ Context: 复制的上下文对象 / Cloned context object
264
+
265
+ Raises:
266
+ ValueError: 如果克隆失败
267
+ """
132
268
  cloned_handle = Library.framework().MaaContextClone(self._handle)
133
269
  if not cloned_handle:
134
270
  raise ValueError("cloned_handle is None")
135
271
 
136
272
  return Context(cloned_handle)
137
273
 
274
+ def set_anchor(self, anchor_name: str, node_name: str) -> bool:
275
+ """设置锚点 / Set anchor
276
+
277
+ Args:
278
+ anchor_name: 锚点名称 / Anchor name
279
+ node_name: 节点名称 / Node name
280
+
281
+ Returns:
282
+ bool: 是否成功 / Whether successful
283
+ """
284
+ return bool(
285
+ Library.framework().MaaContextSetAnchor(
286
+ self._handle, anchor_name.encode(), node_name.encode()
287
+ )
288
+ )
289
+
290
+ def get_anchor(self, anchor_name: str) -> Optional[str]:
291
+ """获取锚点对应的节点名 / Get node name for anchor
292
+
293
+ Args:
294
+ anchor_name: 锚点名称 / Anchor name
295
+
296
+ Returns:
297
+ Optional[str]: 节点名称,如果不存在则返回 None / Node name, or None if not exists
298
+ """
299
+ string_buffer = StringBuffer()
300
+ if not Library.framework().MaaContextGetAnchor(
301
+ self._handle, anchor_name.encode(), string_buffer._handle
302
+ ):
303
+ return None
304
+
305
+ return string_buffer.get()
306
+
307
+ def get_hit_count(self, node_name: str) -> int:
308
+ """获取节点命中计数 / Get hit count for node
309
+
310
+ Args:
311
+ node_name: 节点名称 / Node name
312
+
313
+ Returns:
314
+ int: 命中计数 / Hit count
315
+ """
316
+ count = ctypes.c_uint64()
317
+ if not Library.framework().MaaContextGetHitCount(
318
+ self._handle, node_name.encode(), ctypes.byref(count)
319
+ ):
320
+ return 0
321
+ return count.value
322
+
323
+ def clear_hit_count(self, node_name: str) -> bool:
324
+ """清除节点命中计数 / Clear hit count for node
325
+
326
+ Args:
327
+ node_name: 节点名称 / Node name
328
+
329
+ Returns:
330
+ bool: 是否成功 / Whether successful
331
+ """
332
+ return bool(
333
+ Library.framework().MaaContextClearHitCount(
334
+ self._handle, node_name.encode()
335
+ )
336
+ )
337
+
138
338
  ### private ###
139
339
 
140
340
  def _init_tasker(self):
@@ -145,9 +345,11 @@ class Context:
145
345
 
146
346
  @staticmethod
147
347
  def _gen_post_param(entry: str, pipeline_override: Dict) -> Tuple[bytes, bytes]:
348
+ pipeline_json = json.dumps(pipeline_override, ensure_ascii=False)
349
+
148
350
  return (
149
351
  entry.encode(),
150
- json.dumps(pipeline_override, ensure_ascii=False).encode(),
352
+ pipeline_json.encode(),
151
353
  )
152
354
 
153
355
  _api_properties_initialized: bool = False
@@ -174,7 +376,7 @@ class Context:
174
376
  MaaImageBufferHandle,
175
377
  ]
176
378
 
177
- Library.framework().MaaContextRunAction.restype = MaaNodeId
379
+ Library.framework().MaaContextRunAction.restype = MaaActId
178
380
  Library.framework().MaaContextRunAction.argtypes = [
179
381
  MaaContextHandle,
180
382
  ctypes.c_char_p,
@@ -196,6 +398,13 @@ class Context:
196
398
  MaaStringListBufferHandle,
197
399
  ]
198
400
 
401
+ Library.framework().MaaContextOverrideImage.restype = MaaBool
402
+ Library.framework().MaaContextOverrideImage.argtypes = [
403
+ MaaContextHandle,
404
+ ctypes.c_char_p,
405
+ MaaImageBufferHandle,
406
+ ]
407
+
199
408
  Library.framework().MaaContextGetNodeData.restype = MaaBool
200
409
  Library.framework().MaaContextGetNodeData.argtypes = [
201
410
  MaaContextHandle,
@@ -217,3 +426,186 @@ class Context:
217
426
  Library.framework().MaaContextClone.argtypes = [
218
427
  MaaContextHandle,
219
428
  ]
429
+
430
+ Library.framework().MaaContextSetAnchor.restype = MaaBool
431
+ Library.framework().MaaContextSetAnchor.argtypes = [
432
+ MaaContextHandle,
433
+ ctypes.c_char_p,
434
+ ctypes.c_char_p,
435
+ ]
436
+
437
+ Library.framework().MaaContextGetAnchor.restype = MaaBool
438
+ Library.framework().MaaContextGetAnchor.argtypes = [
439
+ MaaContextHandle,
440
+ ctypes.c_char_p,
441
+ MaaStringBufferHandle,
442
+ ]
443
+
444
+ Library.framework().MaaContextGetHitCount.restype = MaaBool
445
+ Library.framework().MaaContextGetHitCount.argtypes = [
446
+ MaaContextHandle,
447
+ ctypes.c_char_p,
448
+ ctypes.POINTER(ctypes.c_uint64),
449
+ ]
450
+
451
+ Library.framework().MaaContextClearHitCount.restype = MaaBool
452
+ Library.framework().MaaContextClearHitCount.argtypes = [
453
+ MaaContextHandle,
454
+ ctypes.c_char_p,
455
+ ]
456
+
457
+
458
+ class ContextEventSink(EventSink):
459
+ @dataclass
460
+ class NodeNextListDetail:
461
+ task_id: int
462
+ name: str
463
+ next_list: list[JNodeAttr]
464
+ focus: Any
465
+
466
+ def on_node_next_list(
467
+ self,
468
+ context: Context,
469
+ noti_type: NotificationType,
470
+ detail: NodeNextListDetail,
471
+ ):
472
+ pass
473
+
474
+ @dataclass
475
+ class NodeRecognitionDetail:
476
+ task_id: int
477
+ reco_id: int
478
+ name: str
479
+ focus: Any
480
+
481
+ def on_node_recognition(
482
+ self,
483
+ context: Context,
484
+ noti_type: NotificationType,
485
+ detail: NodeRecognitionDetail,
486
+ ):
487
+ pass
488
+
489
+ @dataclass
490
+ class NodeActionDetail:
491
+ task_id: int
492
+ action_id: int
493
+ name: str
494
+ focus: Any
495
+
496
+ def on_node_action(
497
+ self, context: Context, noti_type: NotificationType, detail: NodeActionDetail
498
+ ):
499
+ pass
500
+
501
+ @dataclass
502
+ class NodePipelineNodeDetail:
503
+ task_id: int
504
+ node_id: int
505
+ name: str
506
+ focus: Any
507
+
508
+ def on_node_pipeline_node(
509
+ self,
510
+ context: Context,
511
+ noti_type: NotificationType,
512
+ detail: NodePipelineNodeDetail,
513
+ ):
514
+ pass
515
+
516
+ @dataclass
517
+ class NodeRecognitionNodeDetail:
518
+ task_id: int
519
+ node_id: int
520
+ name: str
521
+ focus: Any
522
+
523
+ def on_node_recognition_node(
524
+ self,
525
+ context: Context,
526
+ noti_type: NotificationType,
527
+ detail: NodeRecognitionNodeDetail,
528
+ ):
529
+ pass
530
+
531
+ @dataclass
532
+ class NodeActionNodeDetail:
533
+ task_id: int
534
+ node_id: int
535
+ name: str
536
+ focus: Any
537
+
538
+ def on_node_action_node(
539
+ self,
540
+ context: Context,
541
+ noti_type: NotificationType,
542
+ detail: NodeActionNodeDetail,
543
+ ):
544
+ pass
545
+
546
+ def on_raw_notification(self, context: Context, msg: str, details: dict):
547
+ pass
548
+
549
+ def _on_raw_notification(self, handle: ctypes.c_void_p, msg: str, details: dict):
550
+
551
+ context = Context(handle=handle)
552
+ self.on_raw_notification(context, msg, details)
553
+
554
+ noti_type = EventSink._notification_type(msg)
555
+ if msg.startswith("Node.NextList"):
556
+ next_list = JPipelineParser._parse_node_attr_list(details["list"])
557
+ detail = self.NodeNextListDetail(
558
+ task_id=details["task_id"],
559
+ name=details["name"],
560
+ next_list=next_list,
561
+ focus=details["focus"],
562
+ )
563
+ self.on_node_next_list(context, noti_type, detail)
564
+
565
+ elif msg.startswith("Node.PipelineNode"):
566
+ detail = self.NodePipelineNodeDetail(
567
+ task_id=details["task_id"],
568
+ node_id=details["node_id"],
569
+ name=details["name"],
570
+ focus=details["focus"],
571
+ )
572
+ self.on_node_pipeline_node(context, noti_type, detail)
573
+
574
+ elif msg.startswith("Node.RecognitionNode"):
575
+ detail = self.NodeRecognitionNodeDetail(
576
+ task_id=details["task_id"],
577
+ node_id=details["node_id"],
578
+ name=details["name"],
579
+ focus=details["focus"],
580
+ )
581
+ self.on_node_recognition_node(context, noti_type, detail)
582
+
583
+ elif msg.startswith("Node.ActionNode"):
584
+ detail = self.NodeActionNodeDetail(
585
+ task_id=details["task_id"],
586
+ node_id=details["node_id"],
587
+ name=details["name"],
588
+ focus=details["focus"],
589
+ )
590
+ self.on_node_action_node(context, noti_type, detail)
591
+
592
+ elif msg.startswith("Node.Recognition"):
593
+ detail = self.NodeRecognitionDetail(
594
+ task_id=details["task_id"],
595
+ reco_id=details["reco_id"],
596
+ name=details["name"],
597
+ focus=details["focus"],
598
+ )
599
+ self.on_node_recognition(context, noti_type, detail)
600
+
601
+ elif msg.startswith("Node.Action"):
602
+ detail = self.NodeActionDetail(
603
+ task_id=details["task_id"],
604
+ action_id=details["action_id"],
605
+ name=details["name"],
606
+ focus=details["focus"],
607
+ )
608
+ self.on_node_action(context, noti_type, detail)
609
+
610
+ else:
611
+ self.on_unknown_notification(context, msg, details)