kotonebot 0.4.0__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 (64) 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/task_action.py +183 -183
  6. kotonebot/backend/core.py +129 -129
  7. kotonebot/backend/debug/entry.py +89 -89
  8. kotonebot/backend/debug/mock.py +78 -78
  9. kotonebot/backend/debug/server.py +222 -222
  10. kotonebot/backend/debug/vars.py +351 -351
  11. kotonebot/backend/dispatch.py +227 -227
  12. kotonebot/backend/flow_controller.py +196 -196
  13. kotonebot/backend/ocr.py +535 -529
  14. kotonebot/backend/preprocessor.py +103 -103
  15. kotonebot/client/__init__.py +9 -9
  16. kotonebot/client/device.py +528 -503
  17. kotonebot/client/fast_screenshot.py +377 -377
  18. kotonebot/client/host/__init__.py +43 -12
  19. kotonebot/client/host/adb_common.py +107 -103
  20. kotonebot/client/host/custom.py +118 -114
  21. kotonebot/client/host/leidian_host.py +196 -201
  22. kotonebot/client/host/mumu12_host.py +353 -358
  23. kotonebot/client/host/protocol.py +214 -213
  24. kotonebot/client/host/windows_common.py +58 -58
  25. kotonebot/client/implements/__init__.py +71 -15
  26. kotonebot/client/implements/adb.py +89 -85
  27. kotonebot/client/implements/adb_raw.py +162 -158
  28. kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
  29. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  30. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  31. kotonebot/client/implements/remote_windows.py +188 -188
  32. kotonebot/client/implements/uiautomator2.py +85 -81
  33. kotonebot/client/implements/windows.py +176 -172
  34. kotonebot/client/protocol.py +69 -69
  35. kotonebot/client/registration.py +24 -24
  36. kotonebot/config/base_config.py +96 -96
  37. kotonebot/config/manager.py +36 -36
  38. kotonebot/errors.py +76 -71
  39. kotonebot/interop/win/__init__.py +10 -3
  40. kotonebot/interop/win/_mouse.py +311 -0
  41. kotonebot/interop/win/message_box.py +313 -313
  42. kotonebot/interop/win/reg.py +37 -37
  43. kotonebot/interop/win/shortcut.py +43 -43
  44. kotonebot/interop/win/task_dialog.py +513 -513
  45. kotonebot/logging/__init__.py +2 -2
  46. kotonebot/logging/log.py +17 -17
  47. kotonebot/primitives/__init__.py +17 -17
  48. kotonebot/primitives/geometry.py +862 -290
  49. kotonebot/primitives/visual.py +63 -63
  50. kotonebot/tools/mirror.py +354 -354
  51. kotonebot/ui/file_host/sensio.py +36 -36
  52. kotonebot/ui/file_host/tmp_send.py +54 -54
  53. kotonebot/ui/pushkit/__init__.py +3 -3
  54. kotonebot/ui/pushkit/image_host.py +88 -87
  55. kotonebot/ui/pushkit/protocol.py +13 -13
  56. kotonebot/ui/pushkit/wxpusher.py +54 -53
  57. kotonebot/ui/user.py +148 -148
  58. kotonebot/util.py +436 -436
  59. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -81
  60. kotonebot-0.5.0.dist-info/RECORD +71 -0
  61. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
  62. kotonebot-0.4.0.dist-info/RECORD +0 -70
  63. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
  64. {kotonebot-0.4.0.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,96 +1,96 @@
1
- import uuid
2
- from typing import Generic, TypeVar, Literal
3
-
4
- from pydantic import BaseModel, ConfigDict
5
-
6
-
7
- T = TypeVar('T')
8
- BackendType = Literal['custom', 'mumu12', 'mumu12v5', 'leidian', 'dmm']
9
- DeviceRecipes = Literal['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
10
-
11
- class ConfigBaseModel(BaseModel):
12
- model_config = ConfigDict(use_attribute_docstrings=True)
13
-
14
- class BackendConfig(ConfigBaseModel):
15
- type: BackendType = 'custom'
16
- """后端类型。"""
17
- instance_id: str | None = None
18
- """模拟器实例 ID。"""
19
- adb_ip: str = '127.0.0.1'
20
- """adb 连接的 ip 地址。"""
21
- adb_port: int = 5555
22
- """adb 连接的端口。"""
23
- adb_emulator_name: str | None = None
24
- """
25
- adb 连接的模拟器名,用于 自动启动模拟器 功能。
26
-
27
- 雷电模拟器需要设置正确的模拟器名,否则 自动启动模拟器 功能将无法正常工作。
28
- 其他功能不受影响。
29
- """
30
- screenshot_impl: DeviceRecipes = 'adb'
31
- """
32
- 截图方法。暂时推荐使用【adb】截图方式。
33
-
34
- 如果使用 remote_windows,需要在 adb_ip 中填写远程 Windows 的 IP 地址,在 adb_port 中填写远程 Windows 的端口号。
35
- """
36
- check_emulator: bool = False
37
- """
38
- 检查并启动模拟器
39
-
40
- 启动脚本的时候,如果检测到模拟器未启动,则自动启动模拟器。
41
- 如果模拟器已经启动,则不启动。
42
- """
43
- emulator_path: str | None = None
44
- """模拟器 exe 文件路径"""
45
- emulator_args: str = ""
46
- """模拟器启动时的命令行参数"""
47
- windows_window_title: str = 'gakumas'
48
- """Windows 截图方式的窗口标题"""
49
- windows_ahk_path: str | None = None
50
- """Windows 截图方式的 AutoHotkey 可执行文件路径,为 None 时使用默认路径"""
51
- mumu_background_mode: bool = False
52
- """MuMu12 模拟器后台保活模式"""
53
- target_screenshot_interval: float | None = None
54
- """最小截图间隔,单位为秒。为 None 时不限制截图速度。"""
55
-
56
- class PushConfig(ConfigBaseModel):
57
- """推送配置。"""
58
-
59
- wx_pusher_enabled: bool = False
60
- """是否启用 WxPusher 推送。"""
61
- wx_pusher_app_token: str | None = None
62
- """WxPusher 的 app token。"""
63
- wx_pusher_uid: str | None = None
64
- """WxPusher 的 uid。"""
65
-
66
- free_image_host_key: str | None = None
67
- """FreeImageHost API key。用于在推送通知时显示图片。"""
68
-
69
- class UserConfig(ConfigBaseModel, Generic[T]):
70
- """用户可以自由添加、删除的配置数据。"""
71
-
72
- name: str = 'default_config'
73
- """显示名称。通常由用户输入。"""
74
- id: str = uuid.uuid4().hex
75
- """唯一标识符。"""
76
- category: str = 'default'
77
- """类别。如:'global'、'china'、'asia' 等。"""
78
- description: str = ''
79
- """描述。通常由用户输入。"""
80
- backend: BackendConfig = BackendConfig()
81
- """后端配置。"""
82
- keep_screenshots: bool = False
83
- """
84
- 是否保留截图。
85
- 若启用,则会保存每一张截图到 `dumps` 目录下。启用该选项有助于辅助调试。
86
- """
87
- options: T
88
- """下游脚本储存的具体数据。"""
89
-
90
-
91
- class RootConfig(ConfigBaseModel, Generic[T]):
92
- version: int = 5
93
- """配置版本。"""
94
- user_configs: list[UserConfig[T]] = []
95
- """用户配置。"""
96
-
1
+ import uuid
2
+ from typing import Generic, TypeVar, Literal
3
+
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+
7
+ T = TypeVar('T')
8
+ BackendType = Literal['custom', 'mumu12', 'mumu12v5', 'leidian', 'dmm']
9
+ DeviceRecipes = Literal['adb', 'adb_raw', 'uiautomator2', 'windows', 'remote_windows', 'nemu_ipc']
10
+
11
+ class ConfigBaseModel(BaseModel):
12
+ model_config = ConfigDict(use_attribute_docstrings=True)
13
+
14
+ class BackendConfig(ConfigBaseModel):
15
+ type: BackendType = 'custom'
16
+ """后端类型。"""
17
+ instance_id: str | None = None
18
+ """模拟器实例 ID。"""
19
+ adb_ip: str = '127.0.0.1'
20
+ """adb 连接的 ip 地址。"""
21
+ adb_port: int = 5555
22
+ """adb 连接的端口。"""
23
+ adb_emulator_name: str | None = None
24
+ """
25
+ adb 连接的模拟器名,用于 自动启动模拟器 功能。
26
+
27
+ 雷电模拟器需要设置正确的模拟器名,否则 自动启动模拟器 功能将无法正常工作。
28
+ 其他功能不受影响。
29
+ """
30
+ screenshot_impl: DeviceRecipes = 'adb'
31
+ """
32
+ 截图方法。暂时推荐使用【adb】截图方式。
33
+
34
+ 如果使用 remote_windows,需要在 adb_ip 中填写远程 Windows 的 IP 地址,在 adb_port 中填写远程 Windows 的端口号。
35
+ """
36
+ check_emulator: bool = False
37
+ """
38
+ 检查并启动模拟器
39
+
40
+ 启动脚本的时候,如果检测到模拟器未启动,则自动启动模拟器。
41
+ 如果模拟器已经启动,则不启动。
42
+ """
43
+ emulator_path: str | None = None
44
+ """模拟器 exe 文件路径"""
45
+ emulator_args: str = ""
46
+ """模拟器启动时的命令行参数"""
47
+ windows_window_title: str = 'gakumas'
48
+ """Windows 截图方式的窗口标题"""
49
+ windows_ahk_path: str | None = None
50
+ """Windows 截图方式的 AutoHotkey 可执行文件路径,为 None 时使用默认路径"""
51
+ mumu_background_mode: bool = False
52
+ """MuMu12 模拟器后台保活模式"""
53
+ target_screenshot_interval: float | None = None
54
+ """最小截图间隔,单位为秒。为 None 时不限制截图速度。"""
55
+
56
+ class PushConfig(ConfigBaseModel):
57
+ """推送配置。"""
58
+
59
+ wx_pusher_enabled: bool = False
60
+ """是否启用 WxPusher 推送。"""
61
+ wx_pusher_app_token: str | None = None
62
+ """WxPusher 的 app token。"""
63
+ wx_pusher_uid: str | None = None
64
+ """WxPusher 的 uid。"""
65
+
66
+ free_image_host_key: str | None = None
67
+ """FreeImageHost API key。用于在推送通知时显示图片。"""
68
+
69
+ class UserConfig(ConfigBaseModel, Generic[T]):
70
+ """用户可以自由添加、删除的配置数据。"""
71
+
72
+ name: str = 'default_config'
73
+ """显示名称。通常由用户输入。"""
74
+ id: str = uuid.uuid4().hex
75
+ """唯一标识符。"""
76
+ category: str = 'default'
77
+ """类别。如:'global'、'china'、'asia' 等。"""
78
+ description: str = ''
79
+ """描述。通常由用户输入。"""
80
+ backend: BackendConfig = BackendConfig()
81
+ """后端配置。"""
82
+ keep_screenshots: bool = False
83
+ """
84
+ 是否保留截图。
85
+ 若启用,则会保存每一张截图到 `dumps` 目录下。启用该选项有助于辅助调试。
86
+ """
87
+ options: T
88
+ """下游脚本储存的具体数据。"""
89
+
90
+
91
+ class RootConfig(ConfigBaseModel, Generic[T]):
92
+ version: int = 5
93
+ """配置版本。"""
94
+ user_configs: list[UserConfig[T]] = []
95
+ """用户配置。"""
96
+
@@ -1,36 +1,36 @@
1
- import os
2
- from typing import Type, Generic, TypeVar
3
-
4
- from .base_config import RootConfig, UserConfig
5
-
6
- T = TypeVar('T')
7
-
8
- def load_config(
9
- config_path: str,
10
- *,
11
- type: Type[T],
12
- use_default_if_not_found: bool = True
13
- ) -> RootConfig[T]:
14
- """
15
- 从指定路径读取配置文件
16
-
17
- :param config_path: 配置文件路径
18
- :param use_default_if_not_found: 如果配置文件不存在,是否使用默认配置
19
- """
20
- if not os.path.exists(config_path):
21
- if use_default_if_not_found:
22
- return RootConfig[type]()
23
- else:
24
- raise FileNotFoundError(f"Config file not found: {config_path}")
25
-
26
- with open(config_path, 'r', encoding='utf-8') as f:
27
- return RootConfig[type].model_validate_json(f.read())
28
-
29
- def save_config(
30
- config: RootConfig[T],
31
- config_path: str,
32
- ):
33
- """将配置保存到指定路径"""
34
- RootConfig[T].model_validate(config)
35
- with open(config_path, 'w+', encoding='utf-8') as f:
36
- f.write(config.model_dump_json(indent=4))
1
+ import os
2
+ from typing import Type, Generic, TypeVar
3
+
4
+ from .base_config import RootConfig, UserConfig
5
+
6
+ T = TypeVar('T')
7
+
8
+ def load_config(
9
+ config_path: str,
10
+ *,
11
+ type: Type[T],
12
+ use_default_if_not_found: bool = True
13
+ ) -> RootConfig[T]:
14
+ """
15
+ 从指定路径读取配置文件
16
+
17
+ :param config_path: 配置文件路径
18
+ :param use_default_if_not_found: 如果配置文件不存在,是否使用默认配置
19
+ """
20
+ if not os.path.exists(config_path):
21
+ if use_default_if_not_found:
22
+ return RootConfig[type]()
23
+ else:
24
+ raise FileNotFoundError(f"Config file not found: {config_path}")
25
+
26
+ with open(config_path, 'r', encoding='utf-8') as f:
27
+ return RootConfig[type].model_validate_json(f.read())
28
+
29
+ def save_config(
30
+ config: RootConfig[T],
31
+ config_path: str,
32
+ ):
33
+ """将配置保存到指定路径"""
34
+ RootConfig[T].model_validate(config)
35
+ with open(config_path, 'w+', encoding='utf-8') as f:
36
+ f.write(config.model_dump_json(indent=4))
kotonebot/errors.py CHANGED
@@ -1,72 +1,77 @@
1
- from typing import Callable
2
-
3
-
4
- class KotonebotError(Exception):
5
- pass
6
-
7
- class KotonebotWarning(Warning):
8
- pass
9
-
10
- class UserFriendlyError(KotonebotError):
11
- def __init__(
12
- self,
13
- message: str,
14
- actions: list[tuple[int, str, Callable[[], None]]] = [],
15
- *args, **kwargs
16
- ) -> None:
17
- super().__init__(*args, **kwargs)
18
- self.message = message
19
- self.actions = actions or []
20
-
21
- @property
22
- def action_buttons(self) -> list[tuple[int, str]]:
23
- """
24
- (id: int, btn_text: str) 的形式返回所有按钮定义。
25
- """
26
- return [(id, text) for id, text, _ in self.actions]
27
-
28
- def invoke(self, action_id: int):
29
- """
30
- 执行指定 ID 的 action。
31
- """
32
- for id, _, func in self.actions:
33
- if id == action_id:
34
- func()
35
- break
36
- else:
37
- raise ValueError(f'Action with id {action_id} not found.')
38
-
39
- class UnrecoverableError(KotonebotError):
40
- pass
41
-
42
- class GameUpdateNeededError(UnrecoverableError):
43
- def __init__(self):
44
- super().__init__(
45
- 'Game update required. '
46
- 'Please go to Play Store and update the game manually.'
47
- )
48
-
49
- class ResourceFileMissingError(KotonebotError):
50
- def __init__(self, file_path: str, description: str):
51
- self.file_path = file_path
52
- self.description = description
53
- super().__init__(f'Resource file ({description}) "{file_path}" is missing.')
54
-
55
- class TaskNotFoundError(KotonebotError):
56
- def __init__(self, task_id: str):
57
- self.task_id = task_id
58
- super().__init__(f'Task "{task_id}" not found.')
59
-
60
- class UnscalableResolutionError(KotonebotError):
61
- def __init__(self, target_resolution: tuple[int, int], screen_size: tuple[int, int]):
62
- self.target_resolution = target_resolution
63
- self.screen_size = screen_size
64
- super().__init__(f'Cannot scale to target resolution {target_resolution}. '
65
- f'Screen size: {screen_size}')
66
-
67
- class ContextNotInitializedError(KotonebotError):
68
- def __init__(self, msg: str = 'Context not initialized'):
69
- super().__init__(msg)
70
-
71
- class StopCurrentTask(KotonebotError):
1
+ from typing import Callable
2
+
3
+
4
+ class KotonebotError(Exception):
5
+ pass
6
+
7
+ class KotonebotWarning(Warning):
8
+ pass
9
+
10
+ class MissingDependencyError(KotonebotError, ImportError):
11
+ def __init__(self, e: ImportError, group_name: str) -> None:
12
+ self.original_error = e
13
+ super().__init__(f'Cannot import module "{e.name}". Did you forget to run "pip install kotonebot[{group_name}]"?')
14
+
15
+ class UserFriendlyError(KotonebotError):
16
+ def __init__(
17
+ self,
18
+ message: str,
19
+ actions: list[tuple[int, str, Callable[[], None]]] = [],
20
+ *args, **kwargs
21
+ ) -> None:
22
+ super().__init__(*args, **kwargs)
23
+ self.message = message
24
+ self.actions = actions or []
25
+
26
+ @property
27
+ def action_buttons(self) -> list[tuple[int, str]]:
28
+ """
29
+ 以 (id: int, btn_text: str) 的形式返回所有按钮定义。
30
+ """
31
+ return [(id, text) for id, text, _ in self.actions]
32
+
33
+ def invoke(self, action_id: int):
34
+ """
35
+ 执行指定 ID 的 action。
36
+ """
37
+ for id, _, func in self.actions:
38
+ if id == action_id:
39
+ func()
40
+ break
41
+ else:
42
+ raise ValueError(f'Action with id {action_id} not found.')
43
+
44
+ class UnrecoverableError(KotonebotError):
45
+ pass
46
+
47
+ class GameUpdateNeededError(UnrecoverableError):
48
+ def __init__(self):
49
+ super().__init__(
50
+ 'Game update required. '
51
+ 'Please go to Play Store and update the game manually.'
52
+ )
53
+
54
+ class ResourceFileMissingError(KotonebotError):
55
+ def __init__(self, file_path: str, description: str):
56
+ self.file_path = file_path
57
+ self.description = description
58
+ super().__init__(f'Resource file ({description}) "{file_path}" is missing.')
59
+
60
+ class TaskNotFoundError(KotonebotError):
61
+ def __init__(self, task_id: str):
62
+ self.task_id = task_id
63
+ super().__init__(f'Task "{task_id}" not found.')
64
+
65
+ class UnscalableResolutionError(KotonebotError):
66
+ def __init__(self, target_resolution: tuple[int, int], screen_size: tuple[int, int]):
67
+ self.target_resolution = target_resolution
68
+ self.screen_size = screen_size
69
+ super().__init__(f'Cannot scale to target resolution {target_resolution}. '
70
+ f'Screen size: {screen_size}')
71
+
72
+ class ContextNotInitializedError(KotonebotError):
73
+ def __init__(self, msg: str = 'Context not initialized'):
74
+ super().__init__(msg)
75
+
76
+ class StopCurrentTask(KotonebotError):
72
77
  pass
@@ -1,3 +1,10 @@
1
- # ruff: noqa: E402
2
- from kotonebot.util import require_windows
3
- require_windows('kotonebot.interop.win module')
1
+ # ruff: noqa: E402
2
+ from kotonebot.util import require_windows
3
+ require_windows('kotonebot.interop.win module')
4
+
5
+
6
+ from . import _mouse as mouse
7
+
8
+ __all__ = [
9
+ 'mouse',
10
+ ]