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,197 +1,197 @@
|
|
|
1
|
-
import time
|
|
2
|
-
import logging
|
|
3
|
-
import threading
|
|
4
|
-
from typing import Literal
|
|
5
|
-
|
|
6
|
-
logger = logging.getLogger(__name__)
|
|
7
|
-
|
|
8
|
-
class FlowController:
|
|
9
|
-
"""
|
|
10
|
-
一个用于控制任务执行流程(如停止、暂停、恢复)的类。
|
|
11
|
-
|
|
12
|
-
这个类是线程安全的,提供了以下功能:
|
|
13
|
-
|
|
14
|
-
* 停止任务执行(通过中断信号)
|
|
15
|
-
* 暂停/恢复任务执行
|
|
16
|
-
* 可中断和可暂停的 sleep 功能
|
|
17
|
-
* 流程状态检查
|
|
18
|
-
|
|
19
|
-
使用方法::
|
|
20
|
-
|
|
21
|
-
controller = FlowController()
|
|
22
|
-
|
|
23
|
-
# 在任务的关键路径上调用检查
|
|
24
|
-
controller.check()
|
|
25
|
-
|
|
26
|
-
# 使用可控制的 sleep
|
|
27
|
-
controller.sleep(1.0)
|
|
28
|
-
|
|
29
|
-
# 外部控制
|
|
30
|
-
controller.request_pause() # 暂停
|
|
31
|
-
controller.request_resume() # 恢复
|
|
32
|
-
controller.request_stop() # 停止
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def __init__(self):
|
|
36
|
-
self.interrupt_event: threading.Event = threading.Event()
|
|
37
|
-
"""中断事件,用于停止任务"""
|
|
38
|
-
|
|
39
|
-
self.paused: bool = False
|
|
40
|
-
"""暂停标志"""
|
|
41
|
-
|
|
42
|
-
self.pause_condition: threading.Condition = threading.Condition()
|
|
43
|
-
"""暂停条件变量,用于线程间同步"""
|
|
44
|
-
|
|
45
|
-
def check(self) -> None:
|
|
46
|
-
"""
|
|
47
|
-
检查当前流程状态。
|
|
48
|
-
|
|
49
|
-
如果收到停止请求,则抛出 KeyboardInterrupt 异常。
|
|
50
|
-
如果收到暂停请求,则阻塞直到恢复。
|
|
51
|
-
|
|
52
|
-
这是核心的检查点方法,应在任务的关键路径上(如循环或等待前)调用。
|
|
53
|
-
|
|
54
|
-
:raises KeyboardInterrupt: 当收到停止请求时
|
|
55
|
-
"""
|
|
56
|
-
# 优先检查中断信号
|
|
57
|
-
if self.interrupt_event.is_set():
|
|
58
|
-
raise KeyboardInterrupt("User requested interrupt.")
|
|
59
|
-
|
|
60
|
-
# 检查暂停状态
|
|
61
|
-
with self.pause_condition:
|
|
62
|
-
while self.paused:
|
|
63
|
-
self.pause_condition.wait()
|
|
64
|
-
|
|
65
|
-
def sleep(self, seconds: float) -> None:
|
|
66
|
-
"""
|
|
67
|
-
一个可被中断和暂停的 sleep 方法。
|
|
68
|
-
|
|
69
|
-
与标准的 time.sleep() 不同,这个方法会响应停止和暂停请求。
|
|
70
|
-
在暂停状态下,计时器会暂停,恢复后继续计时。
|
|
71
|
-
|
|
72
|
-
:param seconds: 睡眠时间(秒)
|
|
73
|
-
:raises KeyboardInterrupt: 当收到停止请求时
|
|
74
|
-
"""
|
|
75
|
-
with self.pause_condition:
|
|
76
|
-
end_time = time.time() + seconds
|
|
77
|
-
while True:
|
|
78
|
-
self.check() # 每次循环都检查状态
|
|
79
|
-
remaining = end_time - time.time()
|
|
80
|
-
if remaining <= 0:
|
|
81
|
-
break
|
|
82
|
-
# 等待指定时间或直到被唤醒
|
|
83
|
-
self.pause_condition.wait(timeout=remaining)
|
|
84
|
-
|
|
85
|
-
# 结束后再次检查状态
|
|
86
|
-
self.check()
|
|
87
|
-
|
|
88
|
-
def request_interrupt(self) -> None:
|
|
89
|
-
"""
|
|
90
|
-
请求停止任务。
|
|
91
|
-
|
|
92
|
-
设置中断信号,所有正在执行的任务将在下一个检查点停止。
|
|
93
|
-
停止的优先级高于暂停。
|
|
94
|
-
"""
|
|
95
|
-
logger.info('Interrupt requested.')
|
|
96
|
-
self.interrupt_event.set()
|
|
97
|
-
|
|
98
|
-
def request_pause(self, *, wait_resume: bool = False) -> None:
|
|
99
|
-
"""
|
|
100
|
-
请求暂停任务。
|
|
101
|
-
|
|
102
|
-
设置暂停标志,所有正在执行的任务将在下一个检查点暂停。
|
|
103
|
-
如果任务已经暂停,此操作无效果。
|
|
104
|
-
"""
|
|
105
|
-
with self.pause_condition:
|
|
106
|
-
if not self.paused:
|
|
107
|
-
logger.info('Pause requested.')
|
|
108
|
-
self.paused = True
|
|
109
|
-
if wait_resume:
|
|
110
|
-
self.check()
|
|
111
|
-
|
|
112
|
-
def request_resume(self) -> None:
|
|
113
|
-
"""
|
|
114
|
-
请求恢复任务。
|
|
115
|
-
|
|
116
|
-
清除暂停标志并通知所有等待的线程恢复执行。
|
|
117
|
-
如果任务没有暂停,此操作无效果。
|
|
118
|
-
"""
|
|
119
|
-
with self.pause_condition:
|
|
120
|
-
if self.paused:
|
|
121
|
-
logger.info('Resume requested.')
|
|
122
|
-
self.paused = False
|
|
123
|
-
self.pause_condition.notify_all()
|
|
124
|
-
|
|
125
|
-
def toggle_pause(self) -> bool:
|
|
126
|
-
"""
|
|
127
|
-
切换暂停/恢复状态。
|
|
128
|
-
|
|
129
|
-
:returns: 操作后的暂停状态。True 表示已暂停,False 表示已恢复。
|
|
130
|
-
"""
|
|
131
|
-
with self.pause_condition:
|
|
132
|
-
logger.info('Pause toggled.')
|
|
133
|
-
if self.paused:
|
|
134
|
-
self.paused = False
|
|
135
|
-
self.pause_condition.notify_all()
|
|
136
|
-
return False
|
|
137
|
-
else:
|
|
138
|
-
self.paused = True
|
|
139
|
-
return True
|
|
140
|
-
|
|
141
|
-
def clear_interrupt(self) -> None:
|
|
142
|
-
"""
|
|
143
|
-
清除中断信号。
|
|
144
|
-
|
|
145
|
-
用于任务正常结束或重启时重置状态。
|
|
146
|
-
通常在开始新任务前调用。
|
|
147
|
-
"""
|
|
148
|
-
self.interrupt_event.clear()
|
|
149
|
-
logger.info('Interrupt cleared.')
|
|
150
|
-
|
|
151
|
-
def reset(self) -> None:
|
|
152
|
-
"""
|
|
153
|
-
重置流程控制器到初始状态。
|
|
154
|
-
|
|
155
|
-
清除所有信号和状态,相当于重新创建一个新的控制器。
|
|
156
|
-
"""
|
|
157
|
-
self.interrupt_event.clear()
|
|
158
|
-
with self.pause_condition:
|
|
159
|
-
if self.paused:
|
|
160
|
-
self.paused = False
|
|
161
|
-
self.pause_condition.notify_all()
|
|
162
|
-
logger.info('FlowController reset.')
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def is_interrupted(self) -> bool:
|
|
166
|
-
"""
|
|
167
|
-
检查是否收到中断请求。
|
|
168
|
-
|
|
169
|
-
:returns: True 表示已收到中断请求
|
|
170
|
-
"""
|
|
171
|
-
return self.interrupt_event.is_set()
|
|
172
|
-
|
|
173
|
-
@property
|
|
174
|
-
def is_paused(self) -> bool:
|
|
175
|
-
"""
|
|
176
|
-
检查是否处于暂停状态。
|
|
177
|
-
|
|
178
|
-
:returns: True 表示当前处于暂停状态
|
|
179
|
-
"""
|
|
180
|
-
return self.paused
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def status(self) -> Literal['running', 'paused', 'interrupted']:
|
|
184
|
-
"""
|
|
185
|
-
获取当前状态的字符串描述。
|
|
186
|
-
|
|
187
|
-
:returns: 状态描述,可能的值:'running', 'paused', 'interrupted'
|
|
188
|
-
"""
|
|
189
|
-
if self.is_interrupted:
|
|
190
|
-
return 'interrupted'
|
|
191
|
-
elif self.is_paused:
|
|
192
|
-
return 'paused'
|
|
193
|
-
else:
|
|
194
|
-
return 'running'
|
|
195
|
-
|
|
196
|
-
def __repr__(self) -> str:
|
|
1
|
+
import time
|
|
2
|
+
import logging
|
|
3
|
+
import threading
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
class FlowController:
|
|
9
|
+
"""
|
|
10
|
+
一个用于控制任务执行流程(如停止、暂停、恢复)的类。
|
|
11
|
+
|
|
12
|
+
这个类是线程安全的,提供了以下功能:
|
|
13
|
+
|
|
14
|
+
* 停止任务执行(通过中断信号)
|
|
15
|
+
* 暂停/恢复任务执行
|
|
16
|
+
* 可中断和可暂停的 sleep 功能
|
|
17
|
+
* 流程状态检查
|
|
18
|
+
|
|
19
|
+
使用方法::
|
|
20
|
+
|
|
21
|
+
controller = FlowController()
|
|
22
|
+
|
|
23
|
+
# 在任务的关键路径上调用检查
|
|
24
|
+
controller.check()
|
|
25
|
+
|
|
26
|
+
# 使用可控制的 sleep
|
|
27
|
+
controller.sleep(1.0)
|
|
28
|
+
|
|
29
|
+
# 外部控制
|
|
30
|
+
controller.request_pause() # 暂停
|
|
31
|
+
controller.request_resume() # 恢复
|
|
32
|
+
controller.request_stop() # 停止
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self):
|
|
36
|
+
self.interrupt_event: threading.Event = threading.Event()
|
|
37
|
+
"""中断事件,用于停止任务"""
|
|
38
|
+
|
|
39
|
+
self.paused: bool = False
|
|
40
|
+
"""暂停标志"""
|
|
41
|
+
|
|
42
|
+
self.pause_condition: threading.Condition = threading.Condition()
|
|
43
|
+
"""暂停条件变量,用于线程间同步"""
|
|
44
|
+
|
|
45
|
+
def check(self) -> None:
|
|
46
|
+
"""
|
|
47
|
+
检查当前流程状态。
|
|
48
|
+
|
|
49
|
+
如果收到停止请求,则抛出 KeyboardInterrupt 异常。
|
|
50
|
+
如果收到暂停请求,则阻塞直到恢复。
|
|
51
|
+
|
|
52
|
+
这是核心的检查点方法,应在任务的关键路径上(如循环或等待前)调用。
|
|
53
|
+
|
|
54
|
+
:raises KeyboardInterrupt: 当收到停止请求时
|
|
55
|
+
"""
|
|
56
|
+
# 优先检查中断信号
|
|
57
|
+
if self.interrupt_event.is_set():
|
|
58
|
+
raise KeyboardInterrupt("User requested interrupt.")
|
|
59
|
+
|
|
60
|
+
# 检查暂停状态
|
|
61
|
+
with self.pause_condition:
|
|
62
|
+
while self.paused:
|
|
63
|
+
self.pause_condition.wait()
|
|
64
|
+
|
|
65
|
+
def sleep(self, seconds: float) -> None:
|
|
66
|
+
"""
|
|
67
|
+
一个可被中断和暂停的 sleep 方法。
|
|
68
|
+
|
|
69
|
+
与标准的 time.sleep() 不同,这个方法会响应停止和暂停请求。
|
|
70
|
+
在暂停状态下,计时器会暂停,恢复后继续计时。
|
|
71
|
+
|
|
72
|
+
:param seconds: 睡眠时间(秒)
|
|
73
|
+
:raises KeyboardInterrupt: 当收到停止请求时
|
|
74
|
+
"""
|
|
75
|
+
with self.pause_condition:
|
|
76
|
+
end_time = time.time() + seconds
|
|
77
|
+
while True:
|
|
78
|
+
self.check() # 每次循环都检查状态
|
|
79
|
+
remaining = end_time - time.time()
|
|
80
|
+
if remaining <= 0:
|
|
81
|
+
break
|
|
82
|
+
# 等待指定时间或直到被唤醒
|
|
83
|
+
self.pause_condition.wait(timeout=remaining)
|
|
84
|
+
|
|
85
|
+
# 结束后再次检查状态
|
|
86
|
+
self.check()
|
|
87
|
+
|
|
88
|
+
def request_interrupt(self) -> None:
|
|
89
|
+
"""
|
|
90
|
+
请求停止任务。
|
|
91
|
+
|
|
92
|
+
设置中断信号,所有正在执行的任务将在下一个检查点停止。
|
|
93
|
+
停止的优先级高于暂停。
|
|
94
|
+
"""
|
|
95
|
+
logger.info('Interrupt requested.')
|
|
96
|
+
self.interrupt_event.set()
|
|
97
|
+
|
|
98
|
+
def request_pause(self, *, wait_resume: bool = False) -> None:
|
|
99
|
+
"""
|
|
100
|
+
请求暂停任务。
|
|
101
|
+
|
|
102
|
+
设置暂停标志,所有正在执行的任务将在下一个检查点暂停。
|
|
103
|
+
如果任务已经暂停,此操作无效果。
|
|
104
|
+
"""
|
|
105
|
+
with self.pause_condition:
|
|
106
|
+
if not self.paused:
|
|
107
|
+
logger.info('Pause requested.')
|
|
108
|
+
self.paused = True
|
|
109
|
+
if wait_resume:
|
|
110
|
+
self.check()
|
|
111
|
+
|
|
112
|
+
def request_resume(self) -> None:
|
|
113
|
+
"""
|
|
114
|
+
请求恢复任务。
|
|
115
|
+
|
|
116
|
+
清除暂停标志并通知所有等待的线程恢复执行。
|
|
117
|
+
如果任务没有暂停,此操作无效果。
|
|
118
|
+
"""
|
|
119
|
+
with self.pause_condition:
|
|
120
|
+
if self.paused:
|
|
121
|
+
logger.info('Resume requested.')
|
|
122
|
+
self.paused = False
|
|
123
|
+
self.pause_condition.notify_all()
|
|
124
|
+
|
|
125
|
+
def toggle_pause(self) -> bool:
|
|
126
|
+
"""
|
|
127
|
+
切换暂停/恢复状态。
|
|
128
|
+
|
|
129
|
+
:returns: 操作后的暂停状态。True 表示已暂停,False 表示已恢复。
|
|
130
|
+
"""
|
|
131
|
+
with self.pause_condition:
|
|
132
|
+
logger.info('Pause toggled.')
|
|
133
|
+
if self.paused:
|
|
134
|
+
self.paused = False
|
|
135
|
+
self.pause_condition.notify_all()
|
|
136
|
+
return False
|
|
137
|
+
else:
|
|
138
|
+
self.paused = True
|
|
139
|
+
return True
|
|
140
|
+
|
|
141
|
+
def clear_interrupt(self) -> None:
|
|
142
|
+
"""
|
|
143
|
+
清除中断信号。
|
|
144
|
+
|
|
145
|
+
用于任务正常结束或重启时重置状态。
|
|
146
|
+
通常在开始新任务前调用。
|
|
147
|
+
"""
|
|
148
|
+
self.interrupt_event.clear()
|
|
149
|
+
logger.info('Interrupt cleared.')
|
|
150
|
+
|
|
151
|
+
def reset(self) -> None:
|
|
152
|
+
"""
|
|
153
|
+
重置流程控制器到初始状态。
|
|
154
|
+
|
|
155
|
+
清除所有信号和状态,相当于重新创建一个新的控制器。
|
|
156
|
+
"""
|
|
157
|
+
self.interrupt_event.clear()
|
|
158
|
+
with self.pause_condition:
|
|
159
|
+
if self.paused:
|
|
160
|
+
self.paused = False
|
|
161
|
+
self.pause_condition.notify_all()
|
|
162
|
+
logger.info('FlowController reset.')
|
|
163
|
+
|
|
164
|
+
@property
|
|
165
|
+
def is_interrupted(self) -> bool:
|
|
166
|
+
"""
|
|
167
|
+
检查是否收到中断请求。
|
|
168
|
+
|
|
169
|
+
:returns: True 表示已收到中断请求
|
|
170
|
+
"""
|
|
171
|
+
return self.interrupt_event.is_set()
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def is_paused(self) -> bool:
|
|
175
|
+
"""
|
|
176
|
+
检查是否处于暂停状态。
|
|
177
|
+
|
|
178
|
+
:returns: True 表示当前处于暂停状态
|
|
179
|
+
"""
|
|
180
|
+
return self.paused
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def status(self) -> Literal['running', 'paused', 'interrupted']:
|
|
184
|
+
"""
|
|
185
|
+
获取当前状态的字符串描述。
|
|
186
|
+
|
|
187
|
+
:returns: 状态描述,可能的值:'running', 'paused', 'interrupted'
|
|
188
|
+
"""
|
|
189
|
+
if self.is_interrupted:
|
|
190
|
+
return 'interrupted'
|
|
191
|
+
elif self.is_paused:
|
|
192
|
+
return 'paused'
|
|
193
|
+
else:
|
|
194
|
+
return 'running'
|
|
195
|
+
|
|
196
|
+
def __repr__(self) -> str:
|
|
197
197
|
return f"FlowController(status='{self.status}')"
|
kotonebot/backend/loop.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from functools import lru_cache, partial
|
|
3
3
|
from typing import Callable, Any, overload, Literal, Generic, TypeVar, cast, get_args, get_origin
|
|
4
|
+
from typing_extensions import deprecated
|
|
4
5
|
|
|
5
6
|
from cv2.typing import MatLike
|
|
6
7
|
|
|
@@ -9,8 +10,9 @@ from kotonebot import device, image, ocr
|
|
|
9
10
|
from kotonebot.backend.core import Image
|
|
10
11
|
from kotonebot.backend.ocr import TextComparator
|
|
11
12
|
from kotonebot.client.protocol import ClickableObjectProtocol
|
|
13
|
+
from .context import vars
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
@deprecated('No longer used.')
|
|
14
16
|
class LoopAction:
|
|
15
17
|
def __init__(self, loop: 'Loop', func: Callable[[], ClickableObjectProtocol | None]):
|
|
16
18
|
self.loop = loop
|
|
@@ -81,15 +83,16 @@ class Loop:
|
|
|
81
83
|
是否在每次循环开始时(Loop.tick() 被调用时)截图。
|
|
82
84
|
"""
|
|
83
85
|
self.__last_loop: float = -1
|
|
84
|
-
self.
|
|
86
|
+
self.interval = interval
|
|
87
|
+
"""每次循环后等待的时间。"""
|
|
85
88
|
self.screenshot: MatLike | None = None
|
|
86
89
|
"""上次截图时的图像数据。"""
|
|
87
90
|
self.__skip_first_wait = skip_first_wait
|
|
88
91
|
self.__is_first_tick = True
|
|
89
92
|
|
|
90
93
|
def __iter__(self):
|
|
91
|
-
self.__interval.reset()
|
|
92
94
|
self.__is_first_tick = True
|
|
95
|
+
vars.flow.check()
|
|
93
96
|
return self
|
|
94
97
|
|
|
95
98
|
def __next__(self):
|
|
@@ -101,7 +104,7 @@ class Loop:
|
|
|
101
104
|
|
|
102
105
|
def tick(self):
|
|
103
106
|
if not (self.__is_first_tick and self.__skip_first_wait):
|
|
104
|
-
self.
|
|
107
|
+
time.sleep(self.interval)
|
|
105
108
|
self.__is_first_tick = False
|
|
106
109
|
|
|
107
110
|
if self.auto_screenshot:
|
|
@@ -117,13 +120,16 @@ class Loop:
|
|
|
117
120
|
self.running = False
|
|
118
121
|
|
|
119
122
|
@overload
|
|
123
|
+
@deprecated('Use plain if statement instead.')
|
|
120
124
|
def when(self, condition: Image) -> LoopAction:
|
|
121
125
|
...
|
|
122
126
|
|
|
123
127
|
@overload
|
|
128
|
+
@deprecated('Use plain if statement instead.')
|
|
124
129
|
def when(self, condition: TextComparator) -> LoopAction:
|
|
125
130
|
...
|
|
126
131
|
|
|
132
|
+
@deprecated('Use plain if statement instead.')
|
|
127
133
|
def when(self, condition: Any):
|
|
128
134
|
"""
|
|
129
135
|
判断某个条件是否成立。
|
|
@@ -142,6 +148,7 @@ class Loop:
|
|
|
142
148
|
la.do()
|
|
143
149
|
return la
|
|
144
150
|
|
|
151
|
+
@deprecated('Use plain if statement instead.')
|
|
145
152
|
def until(self, condition: Any):
|
|
146
153
|
"""
|
|
147
154
|
当满足指定条件时,结束循环。
|
|
@@ -150,6 +157,7 @@ class Loop:
|
|
|
150
157
|
"""
|
|
151
158
|
return self.when(condition).call(lambda _: self.exit())
|
|
152
159
|
|
|
160
|
+
@deprecated('Use image.find() and device.click() instead.')
|
|
153
161
|
def click_if(self, condition: Any, *, at: tuple[int, int] | None = None):
|
|
154
162
|
"""
|
|
155
163
|
检测指定对象是否出现,若出现,点击该对象或指定位置。
|
|
@@ -198,87 +206,3 @@ class StatedLoop(Loop, Generic[StateType]):
|
|
|
198
206
|
self.states = cast(tuple[StateType, ...], state_values)
|
|
199
207
|
self.state = self.__tmp_initial_state or self.states[0]
|
|
200
208
|
return state_values
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def StatedLoop2(states: StateType) -> StatedLoop[StateType]:
|
|
204
|
-
state_values = get_args(states)
|
|
205
|
-
return cast(StatedLoop[StateType], Loop())
|
|
206
|
-
|
|
207
|
-
if __name__ == '__main__':
|
|
208
|
-
from kotonebot.kaa.tasks import R
|
|
209
|
-
from kotonebot.backend.ocr import contains
|
|
210
|
-
from kotonebot.backend.context import manual_context, init_context
|
|
211
|
-
|
|
212
|
-
# T = TypeVar('T')
|
|
213
|
-
# class Foo(Generic[T]):
|
|
214
|
-
# def get_literal_params(self) -> list | None:
|
|
215
|
-
# """
|
|
216
|
-
# 尝试获取泛型参数 T (如果它是 Literal 类型) 的参数列表。
|
|
217
|
-
# """
|
|
218
|
-
# # self.__orig_class__ 会是 Foo 的具体参数化类型,
|
|
219
|
-
# # 例如 Foo[Literal['p0', 'p1', 'p2', 'p3', 'ap']]
|
|
220
|
-
# if not hasattr(self, '__orig_class__'):
|
|
221
|
-
# # 如果 Foo 不是以参数化泛型的方式实例化的,可能没有 __orig_class__
|
|
222
|
-
# return None
|
|
223
|
-
#
|
|
224
|
-
# # generic_type_args 是传递给 Foo 的类型参数元组
|
|
225
|
-
# # 例如 (Literal['p0', 'p1', 'p2', 'p3', 'ap'],)
|
|
226
|
-
# generic_type_args = get_args(self.__orig_class__)
|
|
227
|
-
#
|
|
228
|
-
# if not generic_type_args:
|
|
229
|
-
# # Foo 没有类型参数
|
|
230
|
-
# return None
|
|
231
|
-
#
|
|
232
|
-
# # T_type 是 Foo 的第一个类型参数
|
|
233
|
-
# # 例如 Literal['p0', 'p1', 'p2', 'p3', 'ap']
|
|
234
|
-
# t_type = generic_type_args[0]
|
|
235
|
-
#
|
|
236
|
-
# # 检查 T_type 是否是 Literal 类型
|
|
237
|
-
# if get_origin(t_type) is Literal:
|
|
238
|
-
# # literal_args 是 Literal 类型的参数元组
|
|
239
|
-
# # 例如 ('p0', 'p1', 'p2', 'p3', 'ap')
|
|
240
|
-
# literal_args = get_args(t_type)
|
|
241
|
-
# return list(literal_args)
|
|
242
|
-
# else:
|
|
243
|
-
# # T 不是 Literal 类型
|
|
244
|
-
# return None
|
|
245
|
-
# f = Foo[Literal['p0', 'p1', 'p2', 'p3', 'ap']]()
|
|
246
|
-
# values = f.get_literal_params()
|
|
247
|
-
# 1
|
|
248
|
-
|
|
249
|
-
from typing_extensions import reveal_type
|
|
250
|
-
slp = StatedLoop[Literal['p0', 'p1', 'p2', 'p3', 'ap']]()
|
|
251
|
-
for l in slp:
|
|
252
|
-
reveal_type(l.states)
|
|
253
|
-
|
|
254
|
-
# init_context()
|
|
255
|
-
# manual_context().begin()
|
|
256
|
-
# for l in Loop():
|
|
257
|
-
# l.when(R.Produce.ButtonUse).click()
|
|
258
|
-
# l.when(R.Produce.ButtonRefillAP).click()
|
|
259
|
-
# l.when(contains("123")).click()
|
|
260
|
-
# l.click_if(contains("!23"), at=(1, 2))
|
|
261
|
-
|
|
262
|
-
# State = Literal['p0', 'p1', 'p2', 'p3', 'ap']
|
|
263
|
-
# for sl in StatedLoop[State]():
|
|
264
|
-
# match sl.state:
|
|
265
|
-
# case 'p0':
|
|
266
|
-
# sl.click_if(R.Produce.ButtonProduce)
|
|
267
|
-
# sl.click_if(contains('master'))
|
|
268
|
-
# sl.when(R.Produce.ButtonPIdolOverview).goto('p1')
|
|
269
|
-
# # AP 不足
|
|
270
|
-
# sl.when(R.Produce.TextAPInsufficient).goto('ap')
|
|
271
|
-
# case 'ap':
|
|
272
|
-
# pass
|
|
273
|
-
# # p1: 选择偶像
|
|
274
|
-
# case 'p1':
|
|
275
|
-
# sl.call(lambda _: select_idol(idol_skin_id), once=True)
|
|
276
|
-
# sl.when(R.Produce.TextAnotherIdolAvailableDialog).call(dialog.no)
|
|
277
|
-
# sl.click_if(R.Common.ButtonNextNoIcon)
|
|
278
|
-
# sl.until(R.Produce.TextStepIndicator2).goto('p2')
|
|
279
|
-
# case 'p2':
|
|
280
|
-
# sl.when(contains("123")).click()
|
|
281
|
-
# case 'p3':
|
|
282
|
-
# sl.click_if(contains("!23"), at=(1, 2))
|
|
283
|
-
# case _:
|
|
284
|
-
# assert_never(sl.state)
|