kotonebot 0.3.1__py3-none-any.whl → 0.5.0__py3-none-any.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.
- kotonebot/__init__.py +39 -39
- kotonebot/backend/bot.py +312 -302
- kotonebot/backend/color.py +525 -525
- kotonebot/backend/context/__init__.py +3 -3
- kotonebot/backend/context/context.py +49 -56
- kotonebot/backend/context/task_action.py +183 -175
- kotonebot/backend/core.py +129 -126
- kotonebot/backend/debug/entry.py +89 -89
- kotonebot/backend/debug/mock.py +78 -78
- kotonebot/backend/debug/server.py +222 -222
- kotonebot/backend/debug/vars.py +351 -351
- kotonebot/backend/dispatch.py +227 -227
- kotonebot/backend/flow_controller.py +196 -196
- kotonebot/backend/loop.py +12 -88
- kotonebot/backend/ocr.py +535 -529
- kotonebot/backend/preprocessor.py +103 -103
- kotonebot/client/__init__.py +9 -9
- kotonebot/client/device.py +528 -502
- kotonebot/client/fast_screenshot.py +377 -377
- kotonebot/client/host/__init__.py +43 -12
- kotonebot/client/host/adb_common.py +107 -94
- kotonebot/client/host/custom.py +118 -114
- kotonebot/client/host/leidian_host.py +196 -201
- kotonebot/client/host/mumu12_host.py +353 -358
- kotonebot/client/host/protocol.py +214 -213
- kotonebot/client/host/windows_common.py +58 -55
- kotonebot/client/implements/__init__.py +71 -7
- kotonebot/client/implements/adb.py +89 -85
- kotonebot/client/implements/adb_raw.py +162 -158
- kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
- kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
- kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
- kotonebot/client/implements/remote_windows.py +188 -192
- kotonebot/client/implements/uiautomator2.py +85 -81
- kotonebot/client/implements/windows.py +176 -168
- kotonebot/client/protocol.py +69 -69
- kotonebot/client/registration.py +24 -24
- kotonebot/config/base_config.py +96 -96
- kotonebot/config/manager.py +36 -36
- kotonebot/errors.py +76 -71
- kotonebot/interop/win/__init__.py +10 -0
- kotonebot/interop/win/_mouse.py +311 -0
- kotonebot/interop/win/message_box.py +313 -313
- kotonebot/interop/win/reg.py +37 -37
- kotonebot/interop/win/shortcut.py +43 -43
- kotonebot/interop/win/task_dialog.py +513 -469
- kotonebot/logging/__init__.py +2 -2
- kotonebot/logging/log.py +17 -17
- kotonebot/primitives/__init__.py +17 -17
- kotonebot/primitives/geometry.py +862 -290
- kotonebot/primitives/visual.py +63 -63
- kotonebot/tools/mirror.py +354 -354
- kotonebot/ui/file_host/sensio.py +36 -36
- kotonebot/ui/file_host/tmp_send.py +54 -54
- kotonebot/ui/pushkit/__init__.py +3 -3
- kotonebot/ui/pushkit/image_host.py +88 -87
- kotonebot/ui/pushkit/protocol.py +13 -13
- kotonebot/ui/pushkit/wxpusher.py +54 -53
- kotonebot/ui/user.py +148 -143
- kotonebot/util.py +436 -409
- {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -76
- kotonebot-0.5.0.dist-info/RECORD +71 -0
- {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
- kotonebot-0.3.1.dist-info/RECORD +0 -70
- {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
- {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .context import *
|
|
2
|
-
from .context import _c
|
|
3
|
-
from .task_action import task, action, task_registry, action_registry, current_callstack, Task, Action, tasks_from_id
|
|
1
|
+
from .context import *
|
|
2
|
+
from .context import _c
|
|
3
|
+
from .task_action import task, action, task_registry, action_registry, current_callstack, Task, Action, tasks_from_id
|
|
@@ -175,6 +175,8 @@ class ContextGlobalVars:
|
|
|
175
175
|
self.__vars = dict[str, Any]()
|
|
176
176
|
self.flow: FlowController = FlowController()
|
|
177
177
|
"""流程控制器,负责停止、暂停、恢复等操作"""
|
|
178
|
+
self.screenshot_data: MatLike | None = None
|
|
179
|
+
"""截图数据"""
|
|
178
180
|
|
|
179
181
|
def __getitem__(self, key: str) -> Any:
|
|
180
182
|
return self.__vars[key]
|
|
@@ -197,6 +199,7 @@ class ContextGlobalVars:
|
|
|
197
199
|
def clear(self):
|
|
198
200
|
self.__vars.clear()
|
|
199
201
|
self.flow.reset() # 重置流程控制器
|
|
202
|
+
self.screenshot_data = None
|
|
200
203
|
|
|
201
204
|
def check_flow_control():
|
|
202
205
|
"""
|
|
@@ -220,45 +223,40 @@ class ContextStackVars:
|
|
|
220
223
|
自动截图。即调用 `color`、`image`、`ocr` 上的方法时,会自动更新截图。
|
|
221
224
|
* `manual`
|
|
222
225
|
完全手动截图,不自动截图。如果在没有截图数据的情况下调用 `color` 等的方法,会抛出异常。
|
|
223
|
-
*
|
|
224
|
-
|
|
225
|
-
如果调用者没有截图数据,则继承的截图数据为空。
|
|
226
|
-
如果在没有截图数据的情况下调用 `color` 等的方法,会抛出异常。
|
|
226
|
+
* ~~`manual-inherit`~~:
|
|
227
|
+
已废弃。
|
|
227
228
|
"""
|
|
228
|
-
self._screenshot: MatLike | None = None
|
|
229
|
-
"""截图数据"""
|
|
230
|
-
self._inherit_screenshot: MatLike | None = None
|
|
231
|
-
"""继承的截图数据"""
|
|
232
229
|
|
|
233
230
|
@property
|
|
234
231
|
def screenshot(self) -> MatLike:
|
|
235
232
|
match self.screenshot_mode:
|
|
236
|
-
case 'manual':
|
|
237
|
-
if
|
|
238
|
-
raise ValueError("No screenshot data found.")
|
|
239
|
-
return
|
|
240
|
-
case 'manual-inherit':
|
|
241
|
-
# TODO: 这一部分要考虑和 device.screenshot() 合并
|
|
242
|
-
if self._inherit_screenshot is not None:
|
|
243
|
-
self._screenshot = self._inherit_screenshot
|
|
244
|
-
self._inherit_screenshot = None
|
|
245
|
-
if self._screenshot is None:
|
|
246
|
-
raise ValueError("No screenshot data found.")
|
|
247
|
-
return self._screenshot
|
|
233
|
+
case 'manual' | 'manual-inherit':
|
|
234
|
+
if vars.screenshot_data is None:
|
|
235
|
+
raise ValueError("No screenshot data found. Did you forget to call `device.screenshot()`?")
|
|
236
|
+
return vars.screenshot_data
|
|
248
237
|
case 'auto':
|
|
249
|
-
|
|
250
|
-
|
|
238
|
+
device.screenshot()
|
|
239
|
+
if vars.screenshot_data is None:
|
|
240
|
+
raise ValueError("No screenshot data found. Did you forget to call `device.screenshot()`?")
|
|
241
|
+
return vars.screenshot_data
|
|
251
242
|
case _:
|
|
252
243
|
raise ValueError(f"Invalid screenshot mode: {self.screenshot_mode}")
|
|
253
244
|
|
|
245
|
+
@property
|
|
246
|
+
@deprecated('Use `vars.screenshot_data` instead.')
|
|
247
|
+
def _screenshot(self) -> MatLike | None:
|
|
248
|
+
return vars.screenshot_data
|
|
249
|
+
|
|
250
|
+
@_screenshot.setter
|
|
251
|
+
@deprecated('Use `vars.screenshot_data` instead.')
|
|
252
|
+
def _screenshot(self, value: MatLike | None) -> None:
|
|
253
|
+
vars.screenshot_data = value
|
|
254
|
+
|
|
254
255
|
@staticmethod
|
|
255
256
|
def push(*, screenshot_mode: ScreenshotMode | None = None) -> 'ContextStackVars':
|
|
256
257
|
vars = ContextStackVars()
|
|
257
258
|
if screenshot_mode is not None:
|
|
258
259
|
vars.screenshot_mode = screenshot_mode
|
|
259
|
-
current = ContextStackVars.current()
|
|
260
|
-
if current and vars.screenshot_mode == 'manual-inherit':
|
|
261
|
-
vars._inherit_screenshot = current._screenshot
|
|
262
260
|
ContextStackVars.stack.append(vars)
|
|
263
261
|
return vars
|
|
264
262
|
|
|
@@ -266,7 +264,7 @@ class ContextStackVars:
|
|
|
266
264
|
def pop() -> 'ContextStackVars':
|
|
267
265
|
last = ContextStackVars.stack.pop()
|
|
268
266
|
return last
|
|
269
|
-
|
|
267
|
+
|
|
270
268
|
@staticmethod
|
|
271
269
|
def current() -> 'ContextStackVars | None':
|
|
272
270
|
if len(ContextStackVars.stack) == 0:
|
|
@@ -331,7 +329,7 @@ class ContextOcr:
|
|
|
331
329
|
)
|
|
332
330
|
self.context.device.last_find = ret.original_rect if ret else None
|
|
333
331
|
return ret
|
|
334
|
-
|
|
332
|
+
|
|
335
333
|
def find_all(
|
|
336
334
|
self,
|
|
337
335
|
patterns: Sequence[str | re.Pattern | StringMatchFunction],
|
|
@@ -366,7 +364,7 @@ class ContextOcr:
|
|
|
366
364
|
ret = engine.expect(ContextStackVars.ensure_current().screenshot, pattern, rect=rect, hint=hint)
|
|
367
365
|
self.context.device.last_find = ret.original_rect if ret else None
|
|
368
366
|
return ret
|
|
369
|
-
|
|
367
|
+
|
|
370
368
|
def expect_wait(
|
|
371
369
|
self,
|
|
372
370
|
pattern: str | re.Pattern | StringMatchFunction,
|
|
@@ -447,7 +445,7 @@ class ContextImage:
|
|
|
447
445
|
等待指定图像出现。
|
|
448
446
|
"""
|
|
449
447
|
is_manual = is_manual_screenshot_mode()
|
|
450
|
-
|
|
448
|
+
|
|
451
449
|
start_time = time.time()
|
|
452
450
|
while True:
|
|
453
451
|
if is_manual:
|
|
@@ -702,7 +700,7 @@ class ContextConfig(Generic[T]):
|
|
|
702
700
|
def current(self) -> UserConfig[T]:
|
|
703
701
|
"""
|
|
704
702
|
当前配置数据。
|
|
705
|
-
|
|
703
|
+
|
|
706
704
|
如果当前配置不存在,则使用默认值自动创建一个新配置。
|
|
707
705
|
(不推荐,建议在 UI 中启动前要求用户手动创建,或自行创建一个默认配置。)
|
|
708
706
|
"""
|
|
@@ -729,7 +727,7 @@ class Forwarded:
|
|
|
729
727
|
if self._FORWARD_getter is None:
|
|
730
728
|
raise ContextNotInitializedError(f"Forwarded object {self._FORWARD_name} called before initialization.")
|
|
731
729
|
return getattr(self._FORWARD_getter(), name)
|
|
732
|
-
|
|
730
|
+
|
|
733
731
|
def __setattr__(self, name: str, value: Any):
|
|
734
732
|
if name.startswith('_FORWARD_'):
|
|
735
733
|
return object.__setattr__(self, name, value)
|
|
@@ -761,25 +759,20 @@ class ContextDevice(Generic[T_Device], Device):
|
|
|
761
759
|
"""
|
|
762
760
|
check_flow_control()
|
|
763
761
|
global next_wait, last_screenshot_time, next_wait_time
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
last_screenshot_time = time.time()
|
|
779
|
-
next_wait_time = 0
|
|
780
|
-
next_wait = None
|
|
781
|
-
img = self._device.screenshot()
|
|
782
|
-
current._screenshot = img
|
|
762
|
+
ContextStackVars.ensure_current()
|
|
763
|
+
|
|
764
|
+
if self._screenshot_interval is not None:
|
|
765
|
+
self._screenshot_interval.wait()
|
|
766
|
+
|
|
767
|
+
if next_wait == 'screenshot':
|
|
768
|
+
delta = time.time() - last_screenshot_time
|
|
769
|
+
if delta < next_wait_time:
|
|
770
|
+
sleep(next_wait_time - delta)
|
|
771
|
+
last_screenshot_time = time.time()
|
|
772
|
+
next_wait_time = 0
|
|
773
|
+
next_wait = None
|
|
774
|
+
img = self._device.screenshot()
|
|
775
|
+
vars.screenshot_data = img
|
|
783
776
|
return img
|
|
784
777
|
|
|
785
778
|
def __getattribute__(self, name: str):
|
|
@@ -787,7 +780,7 @@ class ContextDevice(Generic[T_Device], Device):
|
|
|
787
780
|
return object.__getattribute__(self, name)
|
|
788
781
|
else:
|
|
789
782
|
return getattr(self._device, name)
|
|
790
|
-
|
|
783
|
+
|
|
791
784
|
def __setattr__(self, name: str, value: Any):
|
|
792
785
|
if name in ['_device', 'screenshot', 'of_android', 'of_windows']:
|
|
793
786
|
return object.__setattr__(self, name, value)
|
|
@@ -853,7 +846,7 @@ class Context(Generic[T]):
|
|
|
853
846
|
if vars is not None:
|
|
854
847
|
self.__vars = vars
|
|
855
848
|
if debug is not None:
|
|
856
|
-
self.__debug = debug
|
|
849
|
+
self.__debug = debug
|
|
857
850
|
if config is not None:
|
|
858
851
|
self.__config = config
|
|
859
852
|
|
|
@@ -864,7 +857,7 @@ class Context(Generic[T]):
|
|
|
864
857
|
@property
|
|
865
858
|
def ocr(self) -> 'ContextOcr':
|
|
866
859
|
return self.__ocr
|
|
867
|
-
|
|
860
|
+
|
|
868
861
|
@property
|
|
869
862
|
def image(self) -> 'ContextImage':
|
|
870
863
|
return self.__image
|
|
@@ -876,7 +869,7 @@ class Context(Generic[T]):
|
|
|
876
869
|
@property
|
|
877
870
|
def vars(self) -> 'ContextGlobalVars':
|
|
878
871
|
return self.__vars
|
|
879
|
-
|
|
872
|
+
|
|
880
873
|
@property
|
|
881
874
|
def debug(self) -> 'ContextDebug':
|
|
882
875
|
return self.__debug
|
|
@@ -895,7 +888,7 @@ def rect_expand(rect: Rect, left: int = 0, top: int = 0, right: int = 0, bottom:
|
|
|
895
888
|
def use_screenshot(*args: MatLike | None) -> MatLike:
|
|
896
889
|
for img in args:
|
|
897
890
|
if img is not None:
|
|
898
|
-
|
|
891
|
+
vars.screenshot_data = img
|
|
899
892
|
return img
|
|
900
893
|
return device.screenshot()
|
|
901
894
|
|
|
@@ -1006,4 +999,4 @@ def manual_context(screenshot_mode: ScreenshotMode = 'auto') -> ManualContextMan
|
|
|
1006
999
|
默认情况下,Context* 类仅允许在 @task/@action 函数中使用。
|
|
1007
1000
|
如果想要在其他地方使用,使用此函数手动创建一个上下文。
|
|
1008
1001
|
"""
|
|
1009
|
-
return ManualContextManager(screenshot_mode)
|
|
1002
|
+
return ManualContextManager(screenshot_mode)
|
|
@@ -1,176 +1,184 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
:param
|
|
63
|
-
:param
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
ContextStackVars.
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
:
|
|
114
|
-
:
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
action
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
action
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
1
|
+
import logging
|
|
2
|
+
import warnings
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing_extensions import deprecated
|
|
5
|
+
from typing import Callable, ParamSpec, TypeVar, overload, Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from .context import ContextStackVars, ScreenshotMode
|
|
9
|
+
from ...errors import TaskNotFoundError
|
|
10
|
+
|
|
11
|
+
P = ParamSpec('P')
|
|
12
|
+
R = TypeVar('R')
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
TaskRunAtType = Literal['pre', 'post', 'manual', 'regular'] | str
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Task:
|
|
20
|
+
name: str
|
|
21
|
+
id: str
|
|
22
|
+
description: str
|
|
23
|
+
func: Callable
|
|
24
|
+
priority: int
|
|
25
|
+
"""
|
|
26
|
+
任务优先级,数字越大优先级越高。
|
|
27
|
+
"""
|
|
28
|
+
run_at: TaskRunAtType = 'regular'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Action:
|
|
33
|
+
name: str
|
|
34
|
+
description: str
|
|
35
|
+
func: Callable
|
|
36
|
+
priority: int
|
|
37
|
+
"""
|
|
38
|
+
动作优先级,数字越大优先级越高。
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
task_registry: dict[str, Task] = {}
|
|
43
|
+
action_registry: dict[str, Action] = {}
|
|
44
|
+
current_callstack: list[Task|Action] = []
|
|
45
|
+
|
|
46
|
+
def _placeholder():
|
|
47
|
+
raise NotImplementedError('Placeholder function')
|
|
48
|
+
|
|
49
|
+
def task(
|
|
50
|
+
name: str,
|
|
51
|
+
task_id: str|None = None,
|
|
52
|
+
description: str|None = None,
|
|
53
|
+
*,
|
|
54
|
+
pass_through: bool = False,
|
|
55
|
+
priority: int = 0,
|
|
56
|
+
screenshot_mode: ScreenshotMode = 'auto',
|
|
57
|
+
run_at: TaskRunAtType = 'regular'
|
|
58
|
+
):
|
|
59
|
+
"""
|
|
60
|
+
`task` 装饰器,用于标记一个函数为任务函数。
|
|
61
|
+
|
|
62
|
+
:param name: 任务名称
|
|
63
|
+
:param task_id: 任务 ID。如果为 None,则使用函数名称作为 ID。
|
|
64
|
+
:param description: 任务描述。如果为 None,则使用函数的 docstring 作为描述。
|
|
65
|
+
:param pass_through:
|
|
66
|
+
默认情况下, @task 装饰器会包裹任务函数,跟踪其执行情况。
|
|
67
|
+
如果不想跟踪,则设置此参数为 False。
|
|
68
|
+
:param priority: 任务优先级,数字越大优先级越高。
|
|
69
|
+
:param run_at: 任务运行时间。
|
|
70
|
+
"""
|
|
71
|
+
# 设置 ID
|
|
72
|
+
# 获取 caller 信息
|
|
73
|
+
def _task_decorator(func: Callable[P, R]) -> Callable[P, R]:
|
|
74
|
+
nonlocal description, task_id
|
|
75
|
+
description = description or func.__doc__ or ''
|
|
76
|
+
# TODO: task_id 冲突检测
|
|
77
|
+
task_id = task_id or func.__name__
|
|
78
|
+
task = Task(name, task_id, description, _placeholder, priority, run_at)
|
|
79
|
+
task_registry[name] = task
|
|
80
|
+
logger.debug(f'Task "{name}" registered.')
|
|
81
|
+
if pass_through:
|
|
82
|
+
return func
|
|
83
|
+
else:
|
|
84
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
85
|
+
current_callstack.append(task)
|
|
86
|
+
vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
|
|
87
|
+
ret = func(*args, **kwargs)
|
|
88
|
+
ContextStackVars.pop()
|
|
89
|
+
current_callstack.pop()
|
|
90
|
+
return ret
|
|
91
|
+
task.func = _wrapper
|
|
92
|
+
return _wrapper
|
|
93
|
+
return _task_decorator
|
|
94
|
+
|
|
95
|
+
@overload
|
|
96
|
+
def action(func: Callable[P, R]) -> Callable[P, R]: ...
|
|
97
|
+
|
|
98
|
+
@deprecated('Use `action` with screenshot_mode=`manual` instead.')
|
|
99
|
+
@overload
|
|
100
|
+
def action(
|
|
101
|
+
name: str,
|
|
102
|
+
*,
|
|
103
|
+
description: str|None = None,
|
|
104
|
+
pass_through: bool = False,
|
|
105
|
+
priority: int = 0,
|
|
106
|
+
screenshot_mode: Literal['manual-inherit'],
|
|
107
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
|
|
108
|
+
|
|
109
|
+
@overload
|
|
110
|
+
def action(
|
|
111
|
+
name: str,
|
|
112
|
+
*,
|
|
113
|
+
description: str|None = None,
|
|
114
|
+
pass_through: bool = False,
|
|
115
|
+
priority: int = 0,
|
|
116
|
+
screenshot_mode: ScreenshotMode | None = None,
|
|
117
|
+
) -> Callable[[Callable[P, R]], Callable[P, R]]:
|
|
118
|
+
"""
|
|
119
|
+
`action` 装饰器,用于标记一个函数为动作函数。
|
|
120
|
+
|
|
121
|
+
:param name: 动作名称。如果为 None,则使用函数的名称作为名称。
|
|
122
|
+
:param description: 动作描述。如果为 None,则使用函数的 docstring 作为描述。
|
|
123
|
+
:param pass_through:
|
|
124
|
+
默认情况下, @action 装饰器会包裹动作函数,跟踪其执行情况。
|
|
125
|
+
如果不想跟踪,则设置此参数为 False。
|
|
126
|
+
:param priority: 动作优先级,数字越大优先级越高。
|
|
127
|
+
:param screenshot_mode: 截图模式。
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
def action(*args, **kwargs):
|
|
132
|
+
def _register(func: Callable, name: str, description: str|None = None, priority: int = 0) -> Action:
|
|
133
|
+
description = description or func.__doc__ or ''
|
|
134
|
+
action = Action(name, description, func, priority)
|
|
135
|
+
action_registry[name] = action
|
|
136
|
+
logger.debug(f'Action "{name}" registered.')
|
|
137
|
+
return action
|
|
138
|
+
|
|
139
|
+
if len(args) == 1 and isinstance(args[0], Callable):
|
|
140
|
+
func = args[0]
|
|
141
|
+
action = _register(_placeholder, func.__name__, func.__doc__)
|
|
142
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs):
|
|
143
|
+
current_callstack.append(action)
|
|
144
|
+
vars = ContextStackVars.push()
|
|
145
|
+
ret = func(*args, **kwargs)
|
|
146
|
+
ContextStackVars.pop()
|
|
147
|
+
current_callstack.pop()
|
|
148
|
+
return ret
|
|
149
|
+
action.func = _wrapper
|
|
150
|
+
return _wrapper
|
|
151
|
+
else:
|
|
152
|
+
name = args[0]
|
|
153
|
+
description = kwargs.get('description', None)
|
|
154
|
+
pass_through = kwargs.get('pass_through', False)
|
|
155
|
+
priority = kwargs.get('priority', 0)
|
|
156
|
+
screenshot_mode = kwargs.get('screenshot_mode', None)
|
|
157
|
+
if screenshot_mode == 'manual-inherit':
|
|
158
|
+
warnings.warn('`screenshot_mode=manual-inherit` is deprecated. Use `screenshot_mode=manual` instead.')
|
|
159
|
+
def _action_decorator(func: Callable):
|
|
160
|
+
nonlocal pass_through
|
|
161
|
+
action = _register(_placeholder, name, description)
|
|
162
|
+
pass_through = kwargs.get('pass_through', False)
|
|
163
|
+
if pass_through:
|
|
164
|
+
return func
|
|
165
|
+
else:
|
|
166
|
+
def _wrapper(*args: P.args, **kwargs: P.kwargs):
|
|
167
|
+
current_callstack.append(action)
|
|
168
|
+
vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
|
|
169
|
+
ret = func(*args, **kwargs)
|
|
170
|
+
ContextStackVars.pop()
|
|
171
|
+
current_callstack.pop()
|
|
172
|
+
return ret
|
|
173
|
+
action.func = _wrapper
|
|
174
|
+
return _wrapper
|
|
175
|
+
return _action_decorator
|
|
176
|
+
|
|
177
|
+
def tasks_from_id(task_ids: list[str]) -> list[Task]:
|
|
178
|
+
result = []
|
|
179
|
+
for tid in task_ids:
|
|
180
|
+
target = next(task for task in task_registry.values() if task.id == tid)
|
|
181
|
+
if target is None:
|
|
182
|
+
raise TaskNotFoundError(f'Task "{tid}" not found.')
|
|
183
|
+
result.append(target)
|
|
176
184
|
return result
|