kotonebot 0.5.0__py3-none-any.whl → 0.6.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 (103) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -312
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +1002 -1002
  6. kotonebot/backend/context/task_action.py +183 -183
  7. kotonebot/backend/core.py +86 -129
  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/image.py +36 -5
  15. kotonebot/backend/loop.py +222 -208
  16. kotonebot/backend/ocr.py +535 -535
  17. kotonebot/backend/preprocessor.py +103 -103
  18. kotonebot/client/__init__.py +9 -9
  19. kotonebot/client/device.py +369 -529
  20. kotonebot/client/fast_screenshot.py +377 -377
  21. kotonebot/client/host/__init__.py +43 -43
  22. kotonebot/client/host/adb_common.py +101 -107
  23. kotonebot/client/host/custom.py +118 -118
  24. kotonebot/client/host/leidian_host.py +196 -196
  25. kotonebot/client/host/mumu12_host.py +353 -353
  26. kotonebot/client/host/protocol.py +214 -214
  27. kotonebot/client/host/windows_common.py +58 -58
  28. kotonebot/client/implements/__init__.py +65 -70
  29. kotonebot/client/implements/adb.py +89 -89
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -11
  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 -188
  34. kotonebot/client/implements/uiautomator2.py +85 -85
  35. kotonebot/client/implements/windows.py +176 -176
  36. kotonebot/client/protocol.py +69 -69
  37. kotonebot/client/registration.py +24 -24
  38. kotonebot/client/scaler.py +467 -0
  39. kotonebot/config/base_config.py +96 -96
  40. kotonebot/config/config.py +61 -0
  41. kotonebot/config/manager.py +36 -36
  42. kotonebot/core/__init__.py +13 -0
  43. kotonebot/core/entities/base.py +182 -0
  44. kotonebot/core/entities/compound.py +75 -0
  45. kotonebot/core/entities/ocr.py +117 -0
  46. kotonebot/core/entities/template_match.py +198 -0
  47. kotonebot/devtools/__init__.py +42 -0
  48. kotonebot/devtools/cli/__init__.py +6 -0
  49. kotonebot/devtools/cli/main.py +53 -0
  50. kotonebot/{tools → devtools}/mirror.py +354 -354
  51. kotonebot/devtools/project/project.py +41 -0
  52. kotonebot/devtools/project/scanner.py +202 -0
  53. kotonebot/devtools/project/schema.py +99 -0
  54. kotonebot/devtools/resgen/__init__.py +42 -0
  55. kotonebot/devtools/resgen/codegen.py +331 -0
  56. kotonebot/devtools/resgen/core.py +94 -0
  57. kotonebot/devtools/resgen/parsers.py +360 -0
  58. kotonebot/devtools/resgen/utils.py +158 -0
  59. kotonebot/devtools/resgen/validation.py +115 -0
  60. kotonebot/devtools/web/dist/assets/bootstrap-icons-BOrJxbIo.woff +0 -0
  61. kotonebot/devtools/web/dist/assets/bootstrap-icons-BtvjY1KL.woff2 +0 -0
  62. kotonebot/devtools/web/dist/assets/ext-language_tools-CD021WJ2.js +2577 -0
  63. kotonebot/devtools/web/dist/assets/index-B_m5f2LF.js +2836 -0
  64. kotonebot/devtools/web/dist/assets/index-BlEDyGGa.css +9 -0
  65. kotonebot/devtools/web/dist/assets/language-client-C9muzqaq.js +128 -0
  66. kotonebot/devtools/web/dist/assets/mode-python-CtHp76XS.js +476 -0
  67. kotonebot/devtools/web/dist/icons/symbol-class.svg +3 -0
  68. kotonebot/devtools/web/dist/icons/symbol-file.svg +3 -0
  69. kotonebot/devtools/web/dist/icons/symbol-method.svg +3 -0
  70. kotonebot/devtools/web/dist/index.html +25 -0
  71. kotonebot/devtools/web/server/__init__.py +0 -0
  72. kotonebot/devtools/web/server/rest_api.py +217 -0
  73. kotonebot/devtools/web/server/server.py +85 -0
  74. kotonebot/errors.py +76 -76
  75. kotonebot/interop/win/__init__.py +11 -9
  76. kotonebot/interop/win/_mouse.py +310 -310
  77. kotonebot/interop/win/message_box.py +313 -313
  78. kotonebot/interop/win/reg.py +37 -37
  79. kotonebot/interop/win/shake_mouse.py +224 -0
  80. kotonebot/interop/win/shortcut.py +43 -43
  81. kotonebot/interop/win/task_dialog.py +513 -513
  82. kotonebot/logging/__init__.py +2 -2
  83. kotonebot/logging/log.py +17 -17
  84. kotonebot/primitives/__init__.py +19 -17
  85. kotonebot/primitives/geometry.py +1067 -862
  86. kotonebot/primitives/visual.py +143 -63
  87. kotonebot/ui/file_host/sensio.py +36 -36
  88. kotonebot/ui/file_host/tmp_send.py +54 -54
  89. kotonebot/ui/pushkit/__init__.py +3 -3
  90. kotonebot/ui/pushkit/image_host.py +88 -88
  91. kotonebot/ui/pushkit/protocol.py +13 -13
  92. kotonebot/ui/pushkit/wxpusher.py +54 -54
  93. kotonebot/ui/user.py +148 -148
  94. kotonebot/util.py +436 -436
  95. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/METADATA +84 -82
  96. kotonebot-0.6.0.dist-info/RECORD +105 -0
  97. kotonebot-0.6.0.dist-info/entry_points.txt +2 -0
  98. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/licenses/LICENSE +673 -673
  99. kotonebot/client/implements/adb_raw.py +0 -163
  100. kotonebot-0.5.0.dist-info/RECORD +0 -71
  101. /kotonebot/{tools → devtools/project}/__init__.py +0 -0
  102. {kotonebot-0.5.0.dist-info → kotonebot-0.6.0.dist-info}/WHEEL +0 -0
  103. {kotonebot-0.5.0.dist-info → kotonebot-0.6.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}')"
@@ -161,11 +161,19 @@ def template_match(
161
161
  # 统一参数
162
162
  template = unify_image(template, transparent)
163
163
  image = unify_image(image)
164
+ th, tw = template.shape[:2]
165
+ ih, iw = image.shape[:2]
166
+ if th > ih or tw > iw:
167
+ raise ValueError(f"Template size ({tw}x{th}) is larger than image size ({iw}x{ih}).")
164
168
 
165
169
  # 处理矩形区域
166
170
  original_image = image
167
171
  if rect is not None:
168
172
  x, y, w, h = rect.xywh
173
+ if h < th or w < tw:
174
+ raise ValueError(
175
+ f"rect size ({w}x{h}) is smaller than template size ({tw}x{th})."
176
+ )
169
177
  image = image[y:y+h, x:x+w]
170
178
 
171
179
  if transparent is True and mask is not None:
@@ -186,12 +194,35 @@ def template_match(
186
194
  if mask is not None:
187
195
  mask = preprocessor.process(mask)
188
196
  # 匹配模板
189
- if mask is not None:
190
- # https://stackoverflow.com/questions/35642497/python-opencv-cv2-matchtemplate-with-transparency
191
- # 使用 Mask 时,必须使用 TM_CCORR_NORMED 方法
192
- result = cv2.matchTemplate(image, template, cv2.TM_CCORR_NORMED, mask=mask)
197
+ if not colored:
198
+ # 当 colored=False 时,使用灰度匹配以忽略颜色差异并提高速度
199
+ # 准备灰度图用于匹配
200
+ if image.ndim == 3:
201
+ img_for_match = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
202
+ else:
203
+ img_for_match = image
204
+
205
+ if template.ndim == 3:
206
+ tpl_for_match = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
207
+ else:
208
+ tpl_for_match = template
209
+
210
+ # 如果有 mask,确保为单通道二值掩码
211
+ if mask is not None:
212
+ if mask.ndim == 3:
213
+ mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
214
+ mask = cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY)[1]
215
+ # 使用带 mask 的归一化相关性方法
216
+ result = cv2.matchTemplate(img_for_match, tpl_for_match, cv2.TM_CCORR_NORMED, mask=mask)
217
+ else:
218
+ result = cv2.matchTemplate(img_for_match, tpl_for_match, cv2.TM_CCOEFF_NORMED)
193
219
  else:
194
- result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
220
+ if mask is not None:
221
+ # https://stackoverflow.com/questions/35642497/python-opencv-cv2-matchtemplate-with-transparency
222
+ # 使用 Mask 时,必须使用 TM_CCORR_NORMED 方法
223
+ result = cv2.matchTemplate(image, template, cv2.TM_CCORR_NORMED, mask=mask)
224
+ else:
225
+ result = cv2.matchTemplate(image, template, cv2.TM_CCOEFF_NORMED)
195
226
 
196
227
  # ========== 整理结果 ==========
197
228
  # 去重、排序、转换为 TemplateMatchResult