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,37 +1,37 @@
1
- import winreg
2
- from typing import Any, Literal
3
-
4
- RegKey = Literal["HKLM", "HKCU", "HKCR", "HKU", "HKCC"]
5
-
6
- def read_reg(key: RegKey, subkey: str, name: str, *, default: Any = None) -> Any:
7
- """
8
- 读取注册表项的值。
9
-
10
- :param key: 注册表键,例如 "HKLM" (HKEY_LOCAL_MACHINE), "HKCU" (HKEY_CURRENT_USER) 等。
11
- :param subkey: 注册表子键的路径。
12
- :param name: 要读取的值的名称。
13
- :param default: 如果注册表项不存在时返回的默认值。
14
- :return: 注册表项的值,如果不存在则返回默认值。
15
- """
16
- try:
17
- hkey = {
18
- "HKLM": winreg.HKEY_LOCAL_MACHINE,
19
- "HKCU": winreg.HKEY_CURRENT_USER,
20
- "HKCR": winreg.HKEY_CLASSES_ROOT,
21
- "HKU": winreg.HKEY_USERS,
22
- "HKCC": winreg.HKEY_CURRENT_CONFIG,
23
- }[key]
24
- except KeyError:
25
- raise ValueError(f"Invalid key: {key}")
26
-
27
- try:
28
- with winreg.OpenKey(hkey, subkey) as key_handle:
29
- value, _ = winreg.QueryValueEx(key_handle, name)
30
- return value
31
- except FileNotFoundError:
32
- return default
33
- except OSError as e:
34
- if e.winerror == 2: # 注册表项不存在
35
- return default
36
- else:
37
- raise # 其他 OSError 异常,例如权限问题,重新抛出
1
+ import winreg
2
+ from typing import Any, Literal
3
+
4
+ RegKey = Literal["HKLM", "HKCU", "HKCR", "HKU", "HKCC"]
5
+
6
+ def read_reg(key: RegKey, subkey: str, name: str, *, default: Any = None) -> Any:
7
+ """
8
+ 读取注册表项的值。
9
+
10
+ :param key: 注册表键,例如 "HKLM" (HKEY_LOCAL_MACHINE), "HKCU" (HKEY_CURRENT_USER) 等。
11
+ :param subkey: 注册表子键的路径。
12
+ :param name: 要读取的值的名称。
13
+ :param default: 如果注册表项不存在时返回的默认值。
14
+ :return: 注册表项的值,如果不存在则返回默认值。
15
+ """
16
+ try:
17
+ hkey = {
18
+ "HKLM": winreg.HKEY_LOCAL_MACHINE,
19
+ "HKCU": winreg.HKEY_CURRENT_USER,
20
+ "HKCR": winreg.HKEY_CLASSES_ROOT,
21
+ "HKU": winreg.HKEY_USERS,
22
+ "HKCC": winreg.HKEY_CURRENT_CONFIG,
23
+ }[key]
24
+ except KeyError:
25
+ raise ValueError(f"Invalid key: {key}")
26
+
27
+ try:
28
+ with winreg.OpenKey(hkey, subkey) as key_handle:
29
+ value, _ = winreg.QueryValueEx(key_handle, name)
30
+ return value
31
+ except FileNotFoundError:
32
+ return default
33
+ except OSError as e:
34
+ if e.winerror == 2: # 注册表项不存在
35
+ return default
36
+ else:
37
+ raise # 其他 OSError 异常,例如权限问题,重新抛出
@@ -0,0 +1,224 @@
1
+ import time
2
+ import logging
3
+ import threading
4
+ from typing import List, Callable, Optional
5
+
6
+ from ._mouse import get_pos
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class ShakeMouse:
12
+ """
13
+ 全局鼠标晃动检测器。
14
+
15
+ 模拟 macOS 的鼠标晃动检测机制。
16
+ 使用“累积行程+反转计数”算法,有效区分“快速移动”与“快速晃动”,防止误触。
17
+ """
18
+
19
+ _enabled: bool = False
20
+ _thread: Optional[threading.Thread] = None
21
+ _callbacks: List[Callable[[], None]] = []
22
+ _lock = threading.Lock()
23
+
24
+ # --- 算法参数配置 ---
25
+
26
+ SAMPLE_INTERVAL: float = 0.016
27
+ """
28
+ 采样间隔 (秒)。
29
+
30
+ 越小越灵敏,但在低性能机器上可能占用更多 CPU。
31
+ 0.016 约为 60Hz。
32
+ """
33
+
34
+ STROKE_THRESHOLD: int = 500
35
+ """
36
+ 有效行程阈值 (像素)。
37
+
38
+ 必须在单一方向上至少移动这么多像素,然后反转方向,才会被记为一次“晃动”。
39
+ 设置得过小会导致微小抖动触发,设置得过大需要大幅度甩动鼠标。
40
+ """
41
+
42
+ REQUIRED_SHAKES: int = 6
43
+ """
44
+ 触发所需的连续晃动次数。
45
+
46
+ 1次晃动定义为:满足行程阈值的一次方向改变。
47
+ 比如:左 -> 右 (1), 右 -> 左 (2), 左 -> 右 (3), 右 -> 左 (4)。
48
+ """
49
+
50
+ TIMEOUT_RESET: float = 0.5
51
+ """
52
+ 计数重置时间 (秒)。
53
+
54
+ 如果在这个时间内没有发生下一次有效的晃动,计数器归零。
55
+ """
56
+
57
+ COOLDOWN: float = 2.0
58
+ """
59
+ 防抖冷却时间 (秒)。
60
+
61
+ 触发成功后,在该时间内不再重复触发。
62
+ """
63
+
64
+
65
+ @staticmethod
66
+ def start() -> None:
67
+ """开启鼠标晃动检测。"""
68
+ with ShakeMouse._lock:
69
+ if ShakeMouse._enabled:
70
+ return
71
+ ShakeMouse._enabled = True
72
+
73
+ ShakeMouse._thread = threading.Thread(target=ShakeMouse._monitor_loop, daemon=True, name="ShakeMouseMonitor")
74
+ ShakeMouse._thread.start()
75
+ logger.info("ShakeMouse detection enabled.")
76
+
77
+
78
+ @staticmethod
79
+ def stop() -> None:
80
+ """关闭鼠标晃动检测。"""
81
+ with ShakeMouse._lock:
82
+ ShakeMouse._enabled = False
83
+
84
+ # 线程是 daemon 且 loop 检查 _enabled,不需要 join,让它自然退出即可
85
+ if ShakeMouse._thread and ShakeMouse._thread.is_alive():
86
+ ShakeMouse._thread = None
87
+ logger.info("ShakeMouse detection disabled.")
88
+
89
+
90
+ @staticmethod
91
+ def add_callback(func: Callable[[], None]) -> None:
92
+ """添加晃动触发时的回调函数。"""
93
+ ShakeMouse._callbacks.append(func)
94
+
95
+
96
+ @staticmethod
97
+ def clear_callbacks() -> None:
98
+ """清除所有回调函数。"""
99
+ ShakeMouse._callbacks.clear()
100
+
101
+
102
+ @staticmethod
103
+ def _trigger() -> None:
104
+ """内部触发逻辑。"""
105
+ logger.debug("Mouse shake detected!")
106
+ for cb in ShakeMouse._callbacks:
107
+ try:
108
+ cb()
109
+ except Exception:
110
+ logger.exception(f"Error in ShakeMouse callback {cb}")
111
+
112
+
113
+ @staticmethod
114
+ def _monitor_loop() -> None:
115
+ """检测线程主循环。"""
116
+
117
+ last_pos = get_pos()
118
+
119
+ # 算法状态变量
120
+ # 累计在当前方向上的移动距离
121
+ acc_x = 0
122
+ acc_y = 0
123
+
124
+ # 当前方向标志 (1: 正向, -1: 负向, 0: 静止)
125
+ dir_x = 0
126
+ dir_y = 0
127
+
128
+ # 有效晃动计数
129
+ shake_count = 0
130
+
131
+ # 上一次有效晃动发生的时间
132
+ last_shake_time = time.time()
133
+
134
+ # 上一次触发成功的时间 (用于冷却)
135
+ last_trigger_time = 0
136
+
137
+
138
+ while True:
139
+ # 1. 检查开关状态
140
+ if not ShakeMouse._enabled:
141
+ break
142
+
143
+ now = time.time()
144
+
145
+ # 2. 获取当前位置并计算增量
146
+ curr_pos = get_pos()
147
+ dx = curr_pos.x - last_pos.x
148
+ dy = curr_pos.y - last_pos.y
149
+ last_pos = curr_pos
150
+
151
+ # 3. 冷却期检查
152
+ if now - last_trigger_time < ShakeMouse.COOLDOWN:
153
+ time.sleep(ShakeMouse.SAMPLE_INTERVAL)
154
+ # 冷却期内重置累积状态,防止冷却一结束就立即触发
155
+ acc_x, acc_y, shake_count = 0, 0, 0
156
+ continue
157
+
158
+ # 4. 超时重置检查
159
+ # 如果用户晃动了一两下停下来了,一段时间后重置计数
160
+ if shake_count > 0 and (now - last_shake_time > ShakeMouse.TIMEOUT_RESET):
161
+ shake_count = 0
162
+ acc_x = 0
163
+ acc_y = 0
164
+ # logger.debug("Shake timeout reset")
165
+
166
+ # 5. 核心算法:X 轴检测
167
+ # 我们主要检测水平晃动 (X轴) 或 垂直晃动 (Y轴),取两者中显著的一个
168
+
169
+ # --- X 轴逻辑 ---
170
+ if dx != 0:
171
+ current_dir_x = 1 if dx > 0 else -1
172
+
173
+ if current_dir_x != dir_x:
174
+ # 方向发生改变
175
+ if abs(acc_x) > ShakeMouse.STROKE_THRESHOLD:
176
+ # 如果之前的累积行程足够长,记为一次有效晃动
177
+ shake_count += 1
178
+ last_shake_time = now
179
+ # logger.debug(f"Shake count (X): {shake_count}, Acc: {acc_x}")
180
+
181
+ # 重置累积,开始新的方向
182
+ acc_x = dx
183
+ dir_x = current_dir_x
184
+ else:
185
+ # 方向相同,累积行程
186
+ acc_x += dx
187
+
188
+ # --- Y 轴逻辑 (同理) ---
189
+ if dy != 0:
190
+ current_dir_y = 1 if dy > 0 else -1
191
+
192
+ if current_dir_y != dir_y:
193
+ if abs(acc_y) > ShakeMouse.STROKE_THRESHOLD:
194
+ shake_count += 1
195
+ last_shake_time = now
196
+ # logger.debug(f"Shake count (Y): {shake_count}, Acc: {acc_y}")
197
+
198
+ acc_y = dy
199
+ dir_y = current_dir_y
200
+ else:
201
+ acc_y += dy
202
+
203
+ # 6. 触发判定
204
+ if shake_count >= ShakeMouse.REQUIRED_SHAKES:
205
+ last_trigger_time = now
206
+ shake_count = 0
207
+ acc_x = 0
208
+ acc_y = 0
209
+
210
+ # 在独立线程执行触发,不阻塞检测循环太久 (或者回调本身应当快)
211
+ # 这里直接调用,因为 python GIL 限制,只要 callback 不 sleep 太久即可
212
+ ShakeMouse._trigger()
213
+
214
+ # 7. 循环等待
215
+ time.sleep(ShakeMouse.SAMPLE_INTERVAL)
216
+
217
+ if __name__ == "__main__":
218
+ sm = ShakeMouse()
219
+ def on_shake():
220
+ print("Mouse shaken!")
221
+
222
+ sm.add_callback(on_shake)
223
+ sm.start()
224
+ input("Press Enter to stop...\n")
@@ -1,43 +1,43 @@
1
- import os
2
- import typing
3
-
4
- import pythoncom
5
- from win32comext.shell import shell, shellcon
6
-
7
- def create_shortcut(target_file: str, target_args: str, link_file: str | None, *,
8
- link_name: str | None = None,
9
- icon_path: str, description: str = ""):
10
- """
11
- Creates a shortcut.
12
-
13
- :param target_file: The path to the target file.
14
- :param target_args: The arguments for the target file.
15
- :param link_file: The path to the shortcut file. If None, creates on the desktop.
16
- :param link_name: The name of the shortcut file. If None, it is derived from the target file name.
17
- Only used when link_file is None.
18
- :param icon_path: The path to the icon file.
19
- :param description: The description of the shortcut.
20
- """
21
- pythoncom.CoInitialize()
22
- if link_file is None:
23
- desktop_path = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, 0, 0)
24
- if link_name is None:
25
- link_name = os.path.splitext(os.path.basename(target_file))[0]
26
- link_file = os.path.join(desktop_path, f"{link_name}.lnk")
27
-
28
- shortcut = pythoncom.CoCreateInstance(
29
- shell.CLSID_ShellLink,
30
- None,
31
- pythoncom.CLSCTX_INPROC_SERVER,
32
- shell.IID_IShellLink
33
- )
34
- # TODO: 下面这些方法都没有 typing,会报错,需要一种方法加入 typing
35
- shortcut.SetPath(target_file) # type: ignore
36
- shortcut.SetArguments(target_args) # type: ignore
37
- shortcut.SetDescription(description) # type: ignore
38
- shortcut.SetIconLocation(icon_path, 0) # type: ignore
39
-
40
- persist_file = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
41
- persist_file.Save(link_file, 0) # type: ignore
42
- pythoncom.CoUninitialize()
43
-
1
+ import os
2
+ import typing
3
+
4
+ import pythoncom
5
+ from win32comext.shell import shell, shellcon
6
+
7
+ def create_shortcut(target_file: str, target_args: str, link_file: str | None, *,
8
+ link_name: str | None = None,
9
+ icon_path: str, description: str = ""):
10
+ """
11
+ Creates a shortcut.
12
+
13
+ :param target_file: The path to the target file.
14
+ :param target_args: The arguments for the target file.
15
+ :param link_file: The path to the shortcut file. If None, creates on the desktop.
16
+ :param link_name: The name of the shortcut file. If None, it is derived from the target file name.
17
+ Only used when link_file is None.
18
+ :param icon_path: The path to the icon file.
19
+ :param description: The description of the shortcut.
20
+ """
21
+ pythoncom.CoInitialize()
22
+ if link_file is None:
23
+ desktop_path = shell.SHGetFolderPath(0, shellcon.CSIDL_DESKTOP, 0, 0)
24
+ if link_name is None:
25
+ link_name = os.path.splitext(os.path.basename(target_file))[0]
26
+ link_file = os.path.join(desktop_path, f"{link_name}.lnk")
27
+
28
+ shortcut = pythoncom.CoCreateInstance(
29
+ shell.CLSID_ShellLink,
30
+ None,
31
+ pythoncom.CLSCTX_INPROC_SERVER,
32
+ shell.IID_IShellLink
33
+ )
34
+ # TODO: 下面这些方法都没有 typing,会报错,需要一种方法加入 typing
35
+ shortcut.SetPath(target_file) # type: ignore
36
+ shortcut.SetArguments(target_args) # type: ignore
37
+ shortcut.SetDescription(description) # type: ignore
38
+ shortcut.SetIconLocation(icon_path, 0) # type: ignore
39
+
40
+ persist_file = shortcut.QueryInterface(pythoncom.IID_IPersistFile)
41
+ persist_file.Save(link_file, 0) # type: ignore
42
+ pythoncom.CoUninitialize()
43
+