kotonebot 0.1.0__py3-none-any.whl → 0.3.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/backend/context/context.py +1008 -1000
- kotonebot/backend/debug/vars.py +6 -1
- kotonebot/backend/image.py +778 -748
- kotonebot/backend/loop.py +283 -276
- kotonebot/backend/ocr.py +20 -2
- kotonebot/client/device.py +6 -3
- kotonebot/client/host/mumu12_host.py +157 -44
- kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +5 -0
- kotonebot-0.3.0.dist-info/METADATA +76 -0
- {kotonebot-0.1.0.dist-info → kotonebot-0.3.0.dist-info}/RECORD +13 -13
- kotonebot-0.1.0.dist-info/METADATA +0 -204
- {kotonebot-0.1.0.dist-info → kotonebot-0.3.0.dist-info}/WHEEL +0 -0
- {kotonebot-0.1.0.dist-info → kotonebot-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {kotonebot-0.1.0.dist-info → kotonebot-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import json
|
|
4
4
|
import subprocess
|
|
5
5
|
from functools import lru_cache
|
|
6
|
-
from typing import Any, Literal, overload
|
|
6
|
+
from typing import Any, Literal, overload, TYPE_CHECKING
|
|
7
7
|
from typing_extensions import override
|
|
8
8
|
|
|
9
9
|
from kotonebot import logging
|
|
@@ -22,6 +22,10 @@ else:
|
|
|
22
22
|
"""Stub for read_reg on non-Windows platforms."""
|
|
23
23
|
return default
|
|
24
24
|
|
|
25
|
+
# Forward declarations for type hints
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from typing import Type
|
|
28
|
+
|
|
25
29
|
logger = logging.getLogger(__name__)
|
|
26
30
|
MuMu12Recipes = AdbRecipes | Literal['nemu_ipc']
|
|
27
31
|
|
|
@@ -35,24 +39,137 @@ class MuMu12HostConfig(AdbHostConfig):
|
|
|
35
39
|
app_index: int = 0
|
|
36
40
|
"""多开应用索引,传给 get_display_id 方法。"""
|
|
37
41
|
|
|
42
|
+
class Mumu12Host(HostProtocol[MuMu12Recipes]):
|
|
43
|
+
InstanceClass: 'Type[Mumu12Instance]'
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
@lru_cache(maxsize=1)
|
|
47
|
+
def _read_install_path() -> str | None:
|
|
48
|
+
r"""
|
|
49
|
+
从注册表中读取 MuMu Player 12 的安装路径。
|
|
50
|
+
|
|
51
|
+
返回的路径为根目录。如 `F:\Apps\Netease\MuMuPlayer-12.0`。
|
|
52
|
+
|
|
53
|
+
:return: 若找到,则返回安装路径;否则返回 None。
|
|
54
|
+
"""
|
|
55
|
+
if os.name != 'nt':
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
uninstall_subkeys = [
|
|
59
|
+
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MuMuPlayer-12.0',
|
|
60
|
+
# TODO: 支持国际版 MuMu
|
|
61
|
+
# r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MuMuPlayerGlobal-12.0'
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
for subkey in uninstall_subkeys:
|
|
65
|
+
icon_path = read_reg('HKLM', subkey, 'DisplayIcon', default=None)
|
|
66
|
+
if icon_path and isinstance(icon_path, str):
|
|
67
|
+
icon_path = icon_path.replace('"', '')
|
|
68
|
+
path = os.path.dirname(icon_path)
|
|
69
|
+
logger.debug('MuMu Player 12 installation path: %s', path)
|
|
70
|
+
# 返回根目录(去掉 shell 子目录)
|
|
71
|
+
if os.path.basename(path).lower() == 'shell':
|
|
72
|
+
path = os.path.dirname(path)
|
|
73
|
+
return path
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def _invoke_manager(cls,args: list[str]) -> str:
|
|
78
|
+
"""
|
|
79
|
+
调用 MuMuManager.exe。
|
|
80
|
+
|
|
81
|
+
:param args: 命令行参数列表。
|
|
82
|
+
:return: 命令执行的输出。
|
|
83
|
+
"""
|
|
84
|
+
install_path = cls._read_install_path()
|
|
85
|
+
if install_path is None:
|
|
86
|
+
raise RuntimeError('MuMu Player 12 is not installed.')
|
|
87
|
+
manager_path = os.path.join(install_path, 'shell', 'MuMuManager.exe')
|
|
88
|
+
logger.debug('MuMuManager execute: %s', repr(args))
|
|
89
|
+
output = subprocess.run(
|
|
90
|
+
[manager_path] + args,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
encoding='utf-8',
|
|
94
|
+
# https://stackoverflow.com/questions/6011235/run-a-program-from-python-and-have-it-continue-to-run-after-the-script-is-kille
|
|
95
|
+
creationflags=subprocess.DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
|
|
96
|
+
)
|
|
97
|
+
if output.returncode != 0:
|
|
98
|
+
# raise RuntimeError(f'Failed to invoke MuMuManager: {output.stderr}')
|
|
99
|
+
logger.warning('Failed to invoke MuMuManager: %s', output.stderr)
|
|
100
|
+
return output.stdout
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def installed() -> bool:
|
|
104
|
+
return Mumu12Host._read_install_path() is not None
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def list(cls) -> list[Instance]:
|
|
108
|
+
nemu_path = cls._read_install_path()
|
|
109
|
+
if nemu_path is None:
|
|
110
|
+
raise RuntimeError("Nemu path not found.")
|
|
111
|
+
output = cls._invoke_manager(['info', '-v', 'all'])
|
|
112
|
+
logger.debug('MuMuManager.exe output: %s', output)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
data: dict[str, dict[str, Any]] = json.loads(output)
|
|
116
|
+
if 'name' in data.keys():
|
|
117
|
+
# 这里有个坑:
|
|
118
|
+
# 如果只有一个实例,返回的 JSON 结构是单个对象而不是数组
|
|
119
|
+
data = { '0': data }
|
|
120
|
+
instances = []
|
|
121
|
+
for index, instance_data in data.items():
|
|
122
|
+
instance = cls.InstanceClass(
|
|
123
|
+
id=index,
|
|
124
|
+
name=instance_data['name'],
|
|
125
|
+
adb_port=instance_data.get('adb_port'),
|
|
126
|
+
adb_ip=instance_data.get('adb_host_ip', '127.0.0.1'),
|
|
127
|
+
adb_name=None,
|
|
128
|
+
)
|
|
129
|
+
instance.nemu_path = nemu_path
|
|
130
|
+
instance.index = int(index)
|
|
131
|
+
instance.is_android_started = instance_data.get('is_android_started', False)
|
|
132
|
+
logger.debug('Mumu12 instance: %s', repr(instance))
|
|
133
|
+
instances.append(instance)
|
|
134
|
+
return instances
|
|
135
|
+
except json.JSONDecodeError as e:
|
|
136
|
+
raise RuntimeError(f'Failed to parse output: {e}')
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
def query(cls, *, id: str) -> Instance | None:
|
|
140
|
+
instances = cls.list()
|
|
141
|
+
for instance in instances:
|
|
142
|
+
if instance.id == id:
|
|
143
|
+
return instance
|
|
144
|
+
return None
|
|
145
|
+
|
|
146
|
+
@staticmethod
|
|
147
|
+
def recipes() -> 'list[MuMu12Recipes]':
|
|
148
|
+
return ['adb', 'adb_raw', 'uiautomator2', 'nemu_ipc']
|
|
38
149
|
|
|
39
150
|
class Mumu12Instance(CommonAdbCreateDeviceMixin, Instance[MuMu12HostConfig]):
|
|
151
|
+
HostClass: 'type[Mumu12Host]' = Mumu12Host
|
|
152
|
+
|
|
40
153
|
@copy_type(Instance.__init__)
|
|
41
154
|
def __init__(self, *args, **kwargs):
|
|
155
|
+
if not hasattr(self.HostClass, 'InstanceClass'):
|
|
156
|
+
raise RuntimeError(f"{self.HostClass.__name__}.InstanceClass not initialized")
|
|
157
|
+
|
|
42
158
|
super().__init__(*args, **kwargs)
|
|
43
159
|
self._args = args
|
|
44
160
|
self.index: int | None = None
|
|
45
161
|
self.is_android_started: bool = False
|
|
162
|
+
self.nemu_path: str | None = None
|
|
46
163
|
|
|
47
164
|
@override
|
|
48
165
|
def refresh(self):
|
|
49
|
-
ins =
|
|
50
|
-
|
|
51
|
-
if ins is not None:
|
|
166
|
+
ins = self.HostClass.query(id=self.id)
|
|
167
|
+
if ins is not None and isinstance(ins, self.__class__):
|
|
52
168
|
self.adb_port = ins.adb_port
|
|
53
169
|
self.adb_ip = ins.adb_ip
|
|
54
170
|
self.adb_name = ins.adb_name
|
|
55
171
|
self.is_android_started = ins.is_android_started
|
|
172
|
+
self.nemu_path = ins.nemu_path
|
|
56
173
|
logger.debug('Refreshed MuMu12 instance: %s', repr(ins))
|
|
57
174
|
|
|
58
175
|
@override
|
|
@@ -61,7 +178,7 @@ class Mumu12Instance(CommonAdbCreateDeviceMixin, Instance[MuMu12HostConfig]):
|
|
|
61
178
|
logger.warning('Instance is already running.')
|
|
62
179
|
return
|
|
63
180
|
logger.info('Starting MuMu12 instance %s', self)
|
|
64
|
-
|
|
181
|
+
self.HostClass._invoke_manager(['control', '-v', self.id, 'launch'])
|
|
65
182
|
self.refresh()
|
|
66
183
|
|
|
67
184
|
@override
|
|
@@ -70,7 +187,7 @@ class Mumu12Instance(CommonAdbCreateDeviceMixin, Instance[MuMu12HostConfig]):
|
|
|
70
187
|
logger.warning('Instance is not running.')
|
|
71
188
|
return
|
|
72
189
|
logger.info('Stopping MuMu12 instance id=%s name=%s...', self.id, self.name)
|
|
73
|
-
|
|
190
|
+
self.HostClass._invoke_manager(['control', '-v', self.id, 'shutdown'])
|
|
74
191
|
self.refresh()
|
|
75
192
|
|
|
76
193
|
@override
|
|
@@ -100,11 +217,10 @@ class Mumu12Instance(CommonAdbCreateDeviceMixin, Instance[MuMu12HostConfig]):
|
|
|
100
217
|
|
|
101
218
|
if recipe == 'nemu_ipc' and isinstance(host_config, MuMu12HostConfig):
|
|
102
219
|
# NemuImpl
|
|
103
|
-
nemu_path
|
|
104
|
-
|
|
105
|
-
raise RuntimeError("无法找到 MuMu12 的安装路径。")
|
|
220
|
+
if self.nemu_path is None:
|
|
221
|
+
raise RuntimeError("Nemu path is not set.")
|
|
106
222
|
nemu_config = NemuIpcImplConfig(
|
|
107
|
-
nemu_folder=nemu_path,
|
|
223
|
+
nemu_folder=self.nemu_path,
|
|
108
224
|
instance_id=int(self.id),
|
|
109
225
|
display_id=host_config.display_id,
|
|
110
226
|
target_package_name=host_config.target_package_name,
|
|
@@ -129,12 +245,14 @@ class Mumu12Instance(CommonAdbCreateDeviceMixin, Instance[MuMu12HostConfig]):
|
|
|
129
245
|
else:
|
|
130
246
|
raise ValueError(f'Unknown recipe: {recipe}')
|
|
131
247
|
|
|
132
|
-
class Mumu12Host
|
|
133
|
-
|
|
248
|
+
class Mumu12V5Host(Mumu12Host):
|
|
249
|
+
InstanceClass: 'Type[Mumu12V5Instance]'
|
|
250
|
+
|
|
251
|
+
@classmethod
|
|
134
252
|
@lru_cache(maxsize=1)
|
|
135
|
-
def _read_install_path() -> str | None:
|
|
253
|
+
def _read_install_path(cls) -> str | None:
|
|
136
254
|
r"""
|
|
137
|
-
从注册表中读取 MuMu Player 12 的安装路径。
|
|
255
|
+
从注册表中读取 MuMu Player 12 v5.x 的安装路径。
|
|
138
256
|
|
|
139
257
|
返回的路径为根目录。如 `F:\Apps\Netease\MuMuPlayer-12.0`。
|
|
140
258
|
|
|
@@ -144,9 +262,7 @@ class Mumu12Host(HostProtocol[MuMu12Recipes]):
|
|
|
144
262
|
return None
|
|
145
263
|
|
|
146
264
|
uninstall_subkeys = [
|
|
147
|
-
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MuMuPlayer
|
|
148
|
-
# TODO: 支持国际版 MuMu
|
|
149
|
-
# r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MuMuPlayerGlobal-12.0'
|
|
265
|
+
r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MuMuPlayer',
|
|
150
266
|
]
|
|
151
267
|
|
|
152
268
|
for subkey in uninstall_subkeys:
|
|
@@ -156,23 +272,23 @@ class Mumu12Host(HostProtocol[MuMu12Recipes]):
|
|
|
156
272
|
path = os.path.dirname(icon_path)
|
|
157
273
|
logger.debug('MuMu Player 12 installation path: %s', path)
|
|
158
274
|
# 返回根目录(去掉 shell 子目录)
|
|
159
|
-
if os.path.basename(path).lower() == '
|
|
275
|
+
if os.path.basename(path).lower() == 'nx_main':
|
|
160
276
|
path = os.path.dirname(path)
|
|
161
277
|
return path
|
|
162
278
|
return None
|
|
163
279
|
|
|
164
|
-
@
|
|
165
|
-
def _invoke_manager(args: list[str]) -> str:
|
|
280
|
+
@classmethod
|
|
281
|
+
def _invoke_manager(cls, args: list[str]) -> str:
|
|
166
282
|
"""
|
|
167
283
|
调用 MuMuManager.exe。
|
|
168
284
|
|
|
169
285
|
:param args: 命令行参数列表。
|
|
170
286
|
:return: 命令执行的输出。
|
|
171
287
|
"""
|
|
172
|
-
install_path =
|
|
288
|
+
install_path = cls._read_install_path()
|
|
173
289
|
if install_path is None:
|
|
174
|
-
raise RuntimeError('MuMu Player 12 is not installed.')
|
|
175
|
-
manager_path = os.path.join(install_path, '
|
|
290
|
+
raise RuntimeError('MuMu Player 12 v5.x is not installed.')
|
|
291
|
+
manager_path = os.path.join(install_path, 'nx_main', 'MuMuManager.exe')
|
|
176
292
|
logger.debug('MuMuManager execute: %s', repr(args))
|
|
177
293
|
output = subprocess.run(
|
|
178
294
|
[manager_path] + args,
|
|
@@ -187,13 +303,13 @@ class Mumu12Host(HostProtocol[MuMu12Recipes]):
|
|
|
187
303
|
logger.warning('Failed to invoke MuMuManager: %s', output.stderr)
|
|
188
304
|
return output.stdout
|
|
189
305
|
|
|
190
|
-
@
|
|
191
|
-
def installed() -> bool:
|
|
192
|
-
return
|
|
306
|
+
@classmethod
|
|
307
|
+
def installed(cls) -> bool:
|
|
308
|
+
return cls._read_install_path() is not None
|
|
193
309
|
|
|
194
|
-
@
|
|
195
|
-
def list() -> list[Instance]:
|
|
196
|
-
output =
|
|
310
|
+
@classmethod
|
|
311
|
+
def list(cls) -> list[Instance]:
|
|
312
|
+
output = cls._invoke_manager(['info', '-v', 'all'])
|
|
197
313
|
logger.debug('MuMuManager.exe output: %s', output)
|
|
198
314
|
|
|
199
315
|
try:
|
|
@@ -204,40 +320,37 @@ class Mumu12Host(HostProtocol[MuMu12Recipes]):
|
|
|
204
320
|
data = { '0': data }
|
|
205
321
|
instances = []
|
|
206
322
|
for index, instance_data in data.items():
|
|
207
|
-
instance =
|
|
323
|
+
instance = cls.InstanceClass(
|
|
208
324
|
id=index,
|
|
209
325
|
name=instance_data['name'],
|
|
210
326
|
adb_port=instance_data.get('adb_port'),
|
|
211
327
|
adb_ip=instance_data.get('adb_host_ip', '127.0.0.1'),
|
|
212
328
|
adb_name=None
|
|
213
329
|
)
|
|
330
|
+
instance.nemu_path = cls._read_install_path()
|
|
214
331
|
instance.index = int(index)
|
|
215
332
|
instance.is_android_started = instance_data.get('is_android_started', False)
|
|
216
|
-
logger.debug('Mumu12 instance: %s', repr(instance))
|
|
333
|
+
logger.debug('Mumu12 v5.x instance: %s', repr(instance))
|
|
217
334
|
instances.append(instance)
|
|
218
335
|
return instances
|
|
219
336
|
except json.JSONDecodeError as e:
|
|
220
337
|
raise RuntimeError(f'Failed to parse output: {e}')
|
|
221
|
-
|
|
222
|
-
@staticmethod
|
|
223
|
-
def query(*, id: str) -> Instance | None:
|
|
224
|
-
instances = Mumu12Host.list()
|
|
225
|
-
for instance in instances:
|
|
226
|
-
if instance.id == id:
|
|
227
|
-
return instance
|
|
228
|
-
return None
|
|
229
338
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
339
|
+
class Mumu12V5Instance(Mumu12Instance):
|
|
340
|
+
HostClass: 'type[Mumu12V5Host]' = Mumu12V5Host
|
|
341
|
+
|
|
342
|
+
# 延迟初始化 InstanceClass 变量
|
|
343
|
+
Mumu12Host.InstanceClass = Mumu12Instance
|
|
344
|
+
Mumu12V5Host.InstanceClass = Mumu12V5Instance
|
|
345
|
+
|
|
346
|
+
|
|
234
347
|
if __name__ == '__main__':
|
|
235
348
|
logging.basicConfig(level=logging.DEBUG, format='[%(asctime)s] [%(levelname)s] [%(name)s] [%(funcName)s] [%(lineno)d] %(message)s')
|
|
236
349
|
print(Mumu12Host._read_install_path())
|
|
237
350
|
print(Mumu12Host.installed())
|
|
238
351
|
print(Mumu12Host.list())
|
|
239
352
|
print(ins:=Mumu12Host.query(id='2'))
|
|
240
|
-
assert isinstance(ins,
|
|
353
|
+
assert isinstance(ins, Mumu12Host.InstanceClass)
|
|
241
354
|
ins.start()
|
|
242
355
|
ins.wait_available()
|
|
243
356
|
print('status', ins.running(), ins.adb_port, ins.adb_ip)
|
|
@@ -204,6 +204,7 @@ class ExternalRendererIpc:
|
|
|
204
204
|
def __load_dll(self, mumu_root_folder: str) -> ctypes.CDLL:
|
|
205
205
|
"""尝试多条路径加载 DLL。传入为 MuMu 根目录。"""
|
|
206
206
|
candidate_paths = [
|
|
207
|
+
# <= 4.x
|
|
207
208
|
os.path.join(mumu_root_folder, "shell", "sdk", "external_renderer_ipc.dll"),
|
|
208
209
|
os.path.join(
|
|
209
210
|
mumu_root_folder,
|
|
@@ -213,6 +214,10 @@ class ExternalRendererIpc:
|
|
|
213
214
|
"sdk",
|
|
214
215
|
"external_renderer_ipc.dll",
|
|
215
216
|
),
|
|
217
|
+
# >= 5.x
|
|
218
|
+
os.path.join(
|
|
219
|
+
mumu_root_folder, "nx_device", "12.0", "shell", "sdk", "external_renderer_ipc.dll"
|
|
220
|
+
),
|
|
216
221
|
]
|
|
217
222
|
for p in candidate_paths:
|
|
218
223
|
if not os.path.exists(p):
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kotonebot
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Kotonebot is game/app automation library based on computer vision technology, works for Windows and Android.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: opencv-python~=4.10
|
|
9
|
+
Requires-Dist: rapidocr_onnxruntime~=1.4
|
|
10
|
+
Requires-Dist: av~=14.0
|
|
11
|
+
Requires-Dist: scikit-image~=0.25
|
|
12
|
+
Requires-Dist: thefuzz~=0.22
|
|
13
|
+
Requires-Dist: pydantic~=2.10
|
|
14
|
+
Requires-Dist: ksaa-res~=0.2
|
|
15
|
+
Requires-Dist: typing-extensions~=4.12
|
|
16
|
+
Requires-Dist: python-dotenv~=1.0
|
|
17
|
+
Requires-Dist: onnxruntime~=1.14
|
|
18
|
+
Requires-Dist: numpy
|
|
19
|
+
Provides-Extra: android
|
|
20
|
+
Requires-Dist: adbutils~=2.8; extra == "android"
|
|
21
|
+
Requires-Dist: uiautomator2~=3.2; extra == "android"
|
|
22
|
+
Provides-Extra: windows
|
|
23
|
+
Requires-Dist: pywin32; extra == "windows"
|
|
24
|
+
Requires-Dist: ahk~=1.8; extra == "windows"
|
|
25
|
+
Requires-Dist: win11toast~=0.35; extra == "windows"
|
|
26
|
+
Requires-Dist: psutil~=6.1; extra == "windows"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: fastapi~=0.115; extra == "dev"
|
|
29
|
+
Requires-Dist: uvicorn~=0.34; extra == "dev"
|
|
30
|
+
Requires-Dist: python-multipart~=0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: websockets~=14.1; extra == "dev"
|
|
32
|
+
Requires-Dist: psutil~=6.1; extra == "dev"
|
|
33
|
+
Requires-Dist: gradio~=5.21; extra == "dev"
|
|
34
|
+
Requires-Dist: snakeviz; extra == "dev"
|
|
35
|
+
Requires-Dist: build; extra == "dev"
|
|
36
|
+
Provides-Extra: all
|
|
37
|
+
Requires-Dist: kotonebot[android,windows]; extra == "all"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# kotonebot
|
|
41
|
+
> [!WARNING]
|
|
42
|
+
> 本项目仍然处于早期开发阶段,可能随时会发生 breaking change。如果要使用,建议 pin 到一个具体的版本。
|
|
43
|
+
|
|
44
|
+
kotonebot 是一个使用 Python 编写,基于 OpenCV、RapidOCR 等技术,致力于简化 Python 游戏自动化脚本编写流程的框架。
|
|
45
|
+
|
|
46
|
+
## 特性
|
|
47
|
+
* 层次化引入
|
|
48
|
+
* 包含 Library、Framework、Application 三个不同层次,分别封装到不同程度,可自由选择
|
|
49
|
+
* 平台无关的输入输出(截图与模拟点击)
|
|
50
|
+
* 基于代码生成的图片资源引用
|
|
51
|
+
* 避免硬编码字符串
|
|
52
|
+
* 图像/OCR 识别结果追踪 & 可视化查看工具
|
|
53
|
+
* 开箱即用的模拟器管理(目前仅支持 MuMu12 与雷电模拟器)
|
|
54
|
+
|
|
55
|
+
## 安装
|
|
56
|
+
要求:Python >= 3.10
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Windows Host, Windows Client
|
|
60
|
+
pip install kotonebot[windows]
|
|
61
|
+
# Windows Host, Android Client
|
|
62
|
+
pip install kotonebot[android]
|
|
63
|
+
# Development dependencies
|
|
64
|
+
pip install kotonebot[dev]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 快速开始
|
|
68
|
+
WIP
|
|
69
|
+
|
|
70
|
+
## 文档
|
|
71
|
+
WIP
|
|
72
|
+
|
|
73
|
+
## 其他
|
|
74
|
+
本项目分离自 [KotonesAutoAssistant](https://github.com/XcantloadX/kotones-auto-assistant),因此 c69130 以前的提交均为 KotonesAutoAssistant 的历史提交。
|
|
75
|
+
|
|
76
|
+
由于使用 filter-repo 移除了大量无用文件,因此历史提交信息和更改的文件可能无法完全对应。
|
|
@@ -7,20 +7,20 @@ kotonebot/backend/color.py,sha256=KqFISc6puMNbsyB5diu5PcqNjdkLwUeryVsMmeAJG4Q,20
|
|
|
7
7
|
kotonebot/backend/core.py,sha256=pn1XORbU6_ddfgLJQpLSmiGL2MjnF5iCk5GBdrq3GYE,3783
|
|
8
8
|
kotonebot/backend/dispatch.py,sha256=t3eqkOVNNrdaeXSMLxDbReYhVRoAlSRNxb7oO2clRsQ,7423
|
|
9
9
|
kotonebot/backend/flow_controller.py,sha256=sbr6JiuYXErevY_BPrzw7hUCfxLGVrz0W_KNbXIxe9Q,6228
|
|
10
|
-
kotonebot/backend/image.py,sha256=
|
|
11
|
-
kotonebot/backend/loop.py,sha256=
|
|
12
|
-
kotonebot/backend/ocr.py,sha256=
|
|
10
|
+
kotonebot/backend/image.py,sha256=jXYGVXa9K1zBCJBG1Btsf2AG5XI9A0DMy5uYy6KOusk,28111
|
|
11
|
+
kotonebot/backend/loop.py,sha256=HlgMON1LcBPZP710w5uHeziMRSF5Dhwv3UKUdjCkouk,9594
|
|
12
|
+
kotonebot/backend/ocr.py,sha256=IE_LUCdD4i00a02xgLkSRPcYYo3R2MBfZKKQHjOFfgM,18058
|
|
13
13
|
kotonebot/backend/preprocessor.py,sha256=YmAbLa-XXES2AchMJtsBpPZwIIGHuYapwpXpw0YSpbA,3423
|
|
14
14
|
kotonebot/backend/context/__init__.py,sha256=0X9jzM0ftGQUgjoXCk98xf1inoCqHqaUwT0wcHn9P5s,168
|
|
15
|
-
kotonebot/backend/context/context.py,sha256=
|
|
15
|
+
kotonebot/backend/context/context.py,sha256=dvGD_kKaThYdwIuC21Gd2P0uYwyh3yRgMYqkZ5_Ap3w,34472
|
|
16
16
|
kotonebot/backend/context/task_action.py,sha256=7fKy6ZOtOaDW87hketiub72_eND9yorJqBP3Z5b8fX8,6259
|
|
17
17
|
kotonebot/backend/debug/__init__.py,sha256=pcSpwzU2YwGrogOoHmsI035nkA_-kDLfm-lfBxHuQ-c,43
|
|
18
18
|
kotonebot/backend/debug/entry.py,sha256=_sIGi9_LegbaM2DCcDTvGJhrkyUqF6W2Al4nYmkb9rM,2833
|
|
19
19
|
kotonebot/backend/debug/mock.py,sha256=0fTiJeqVTanQv6L3TPbldgbJRBPmunZOjlzKtVicJ_M,2118
|
|
20
20
|
kotonebot/backend/debug/server.py,sha256=9PEpczIrwCnD4c_FAtiojwk27aKkkzTtRySCsdho4Rs,7517
|
|
21
|
-
kotonebot/backend/debug/vars.py,sha256=
|
|
21
|
+
kotonebot/backend/debug/vars.py,sha256=3wtbkH2WFNXyT2ZNu-8en3S0pAb40h-SefYyScRMLnM,10912
|
|
22
22
|
kotonebot/client/__init__.py,sha256=1eXyGopBFpYoucNTNkTo-7nIeDyasyETfHJ8DWuaNTo,192
|
|
23
|
-
kotonebot/client/device.py,sha256=
|
|
23
|
+
kotonebot/client/device.py,sha256=5BVa1dXVI_jH5gLCL61tJ_MXG6BVSt_mETqyUVlutWo,19370
|
|
24
24
|
kotonebot/client/fast_screenshot.py,sha256=q69AX15VXRuB0U2qFJKfoTOBgG4nVBCUcaN1CX0VsUc,13647
|
|
25
25
|
kotonebot/client/protocol.py,sha256=x05llULFI3MddbvBX_c0sYWlSzCyWa3hped24ktq2Ko,2300
|
|
26
26
|
kotonebot/client/registration.py,sha256=XK424QEWbJKfNdkiDoIJUh_JIy8ryk4I4I3hrWUXCX8,848
|
|
@@ -28,7 +28,7 @@ kotonebot/client/host/__init__.py,sha256=W92CkvQIPMV4WYbBed-Oz1gLN46ZJK-SQ0ExdQ-
|
|
|
28
28
|
kotonebot/client/host/adb_common.py,sha256=b9t5bbiIYGARpqB8qayptAzznQaWtzWt533yqoAAyvQ,3280
|
|
29
29
|
kotonebot/client/host/custom.py,sha256=_r2GAtJ20CoEx8JO7wVa4jr6KT8YgO5wzrFHLFtOgZE,4079
|
|
30
30
|
kotonebot/client/host/leidian_host.py,sha256=EoogcI1XN1akE1zKLaZCNMvs7uIRrraZsg6ib5cRwAU,7696
|
|
31
|
-
kotonebot/client/host/mumu12_host.py,sha256=
|
|
31
|
+
kotonebot/client/host/mumu12_host.py,sha256=cWlbZiV0HdN2S42L1Nd5Jcv3bw-b1oxjX4_3y5JE-tI,14444
|
|
32
32
|
kotonebot/client/host/protocol.py,sha256=x2TbnDELDQpqxSKWUHLT3Pez8Qx6IzL4wyUfgf9mFyk,8051
|
|
33
33
|
kotonebot/client/host/windows_common.py,sha256=sz2uxfYa4by4uNM7_7qHS1kfvyu9iYxd5DgJ8ThQAKA,2223
|
|
34
34
|
kotonebot/client/implements/__init__.py,sha256=w9Yz_nVEnby_7eCS2FT1oYp1IuO3yqSdvhZEu6iJcvk,303
|
|
@@ -38,7 +38,7 @@ kotonebot/client/implements/remote_windows.py,sha256=tc9R0kcKAwLwWt4Qu8Gt8wLZJCS
|
|
|
38
38
|
kotonebot/client/implements/uiautomator2.py,sha256=ER4cNLI_cCpIGKWIXeuaUPmVtz50JuFO5Kx-ZwCGI1s,2575
|
|
39
39
|
kotonebot/client/implements/windows.py,sha256=-xS2qN5RdHB_X4R1a8Oi5tM2Wh5YI774wNAJ21lYirQ,6623
|
|
40
40
|
kotonebot/client/implements/nemu_ipc/__init__.py,sha256=vSZzv75bn38Wch86PYs5UDOCLwxxoDGm2v1jrwff_S8,200
|
|
41
|
-
kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py,sha256=
|
|
41
|
+
kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py,sha256=YsfKf0-qorfAf2YvNuxpLb9af-HJFsu97bnXABshhbA,10643
|
|
42
42
|
kotonebot/client/implements/nemu_ipc/nemu_ipc.py,sha256=LhUUyfB28MDnRg8z2FyGah1hTeOMFiX7w8LZLAAjLF8,12082
|
|
43
43
|
kotonebot/config/__init__.py,sha256=-jATUOdrpUrBRT9RiTRQho2-2zeet50qQggsVMVpqNE,35
|
|
44
44
|
kotonebot/config/base_config.py,sha256=lsps93A32tnJtApuG1ugeGIt0OWuFFi3uUFsXmH2c6E,3426
|
|
@@ -63,8 +63,8 @@ kotonebot/ui/pushkit/__init__.py,sha256=xDUctRUL3euvge-yl8IhFYMlxIxQXsjxcyGN5tUw
|
|
|
63
63
|
kotonebot/ui/pushkit/image_host.py,sha256=nB6BCOA5ZgSGi-ntgqQp49H1UZDk8qC41O_PTLPzZ-E,2581
|
|
64
64
|
kotonebot/ui/pushkit/protocol.py,sha256=KVZ-xr0sMdiuri7AiYqugpZRRtefBsosXm6zouScUR4,266
|
|
65
65
|
kotonebot/ui/pushkit/wxpusher.py,sha256=U7WKxyf9pVgGvppmBxwMRuBuFkQG3NC3tkdRh7_-IOw,1732
|
|
66
|
-
kotonebot-0.
|
|
67
|
-
kotonebot-0.
|
|
68
|
-
kotonebot-0.
|
|
69
|
-
kotonebot-0.
|
|
70
|
-
kotonebot-0.
|
|
66
|
+
kotonebot-0.3.0.dist-info/licenses/LICENSE,sha256=gcuuhKKc5-dwvyvHsXjlC9oM6N5gZ6umYbC8ewW1Yvg,35821
|
|
67
|
+
kotonebot-0.3.0.dist-info/METADATA,sha256=FeFIGsrzBZc-L2zDZz_igPyPCk45IbveHzqq7FQKZWs,2788
|
|
68
|
+
kotonebot-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
69
|
+
kotonebot-0.3.0.dist-info/top_level.txt,sha256=QUWAZdbBndoojkrs6RcNytLAn7a0ns4YNF4tLx2Nc4s,10
|
|
70
|
+
kotonebot-0.3.0.dist-info/RECORD,,
|