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.
Files changed (66) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -302
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +49 -56
  6. kotonebot/backend/context/task_action.py +183 -175
  7. kotonebot/backend/core.py +129 -126
  8. kotonebot/backend/debug/entry.py +89 -89
  9. kotonebot/backend/debug/mock.py +78 -78
  10. kotonebot/backend/debug/server.py +222 -222
  11. kotonebot/backend/debug/vars.py +351 -351
  12. kotonebot/backend/dispatch.py +227 -227
  13. kotonebot/backend/flow_controller.py +196 -196
  14. kotonebot/backend/loop.py +12 -88
  15. kotonebot/backend/ocr.py +535 -529
  16. kotonebot/backend/preprocessor.py +103 -103
  17. kotonebot/client/__init__.py +9 -9
  18. kotonebot/client/device.py +528 -502
  19. kotonebot/client/fast_screenshot.py +377 -377
  20. kotonebot/client/host/__init__.py +43 -12
  21. kotonebot/client/host/adb_common.py +107 -94
  22. kotonebot/client/host/custom.py +118 -114
  23. kotonebot/client/host/leidian_host.py +196 -201
  24. kotonebot/client/host/mumu12_host.py +353 -358
  25. kotonebot/client/host/protocol.py +214 -213
  26. kotonebot/client/host/windows_common.py +58 -55
  27. kotonebot/client/implements/__init__.py +71 -7
  28. kotonebot/client/implements/adb.py +89 -85
  29. kotonebot/client/implements/adb_raw.py +162 -158
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
  31. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  32. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  33. kotonebot/client/implements/remote_windows.py +188 -192
  34. kotonebot/client/implements/uiautomator2.py +85 -81
  35. kotonebot/client/implements/windows.py +176 -168
  36. kotonebot/client/protocol.py +69 -69
  37. kotonebot/client/registration.py +24 -24
  38. kotonebot/config/base_config.py +96 -96
  39. kotonebot/config/manager.py +36 -36
  40. kotonebot/errors.py +76 -71
  41. kotonebot/interop/win/__init__.py +10 -0
  42. kotonebot/interop/win/_mouse.py +311 -0
  43. kotonebot/interop/win/message_box.py +313 -313
  44. kotonebot/interop/win/reg.py +37 -37
  45. kotonebot/interop/win/shortcut.py +43 -43
  46. kotonebot/interop/win/task_dialog.py +513 -469
  47. kotonebot/logging/__init__.py +2 -2
  48. kotonebot/logging/log.py +17 -17
  49. kotonebot/primitives/__init__.py +17 -17
  50. kotonebot/primitives/geometry.py +862 -290
  51. kotonebot/primitives/visual.py +63 -63
  52. kotonebot/tools/mirror.py +354 -354
  53. kotonebot/ui/file_host/sensio.py +36 -36
  54. kotonebot/ui/file_host/tmp_send.py +54 -54
  55. kotonebot/ui/pushkit/__init__.py +3 -3
  56. kotonebot/ui/pushkit/image_host.py +88 -87
  57. kotonebot/ui/pushkit/protocol.py +13 -13
  58. kotonebot/ui/pushkit/wxpusher.py +54 -53
  59. kotonebot/ui/user.py +148 -143
  60. kotonebot/util.py +436 -409
  61. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -76
  62. kotonebot-0.5.0.dist-info/RECORD +71 -0
  63. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
  64. kotonebot-0.3.1.dist-info/RECORD +0 -70
  65. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
  66. {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.__interval = Interval(interval)
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.__interval.wait()
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)