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
kotonebot/backend/loop.py CHANGED
@@ -1,208 +1,222 @@
1
- import time
2
- from functools import lru_cache, partial
3
- from typing import Callable, Any, overload, Literal, Generic, TypeVar, cast, get_args, get_origin
4
- from typing_extensions import deprecated
5
-
6
- from cv2.typing import MatLike
7
-
8
- from kotonebot.util import Interval
9
- from kotonebot import device, image, ocr
10
- from kotonebot.backend.core import Image
11
- from kotonebot.backend.ocr import TextComparator
12
- from kotonebot.client.protocol import ClickableObjectProtocol
13
- from .context import vars
14
-
15
- @deprecated('No longer used.')
16
- class LoopAction:
17
- def __init__(self, loop: 'Loop', func: Callable[[], ClickableObjectProtocol | None]):
18
- self.loop = loop
19
- self.func = func
20
- self.result: ClickableObjectProtocol | None = None
21
-
22
- @property
23
- def found(self):
24
- """
25
- 是否找到结果。若父 Loop 未在运行中,则返回 False。
26
- """
27
- if not self.loop.running:
28
- return False
29
- return bool(self.result)
30
-
31
- def __bool__(self):
32
- return self.found
33
-
34
- def reset(self):
35
- """
36
- 重置 LoopAction,以复用此对象。
37
- """
38
- self.result = None
39
-
40
- def do(self):
41
- """
42
- 执行 LoopAction。
43
- :return: 执行结果。
44
- """
45
- if not self.loop.running:
46
- return
47
- if self.loop.found_anything:
48
- # 本轮循环已执行任意操作,因此不需要再继续检测
49
- return
50
- self.result = self.func()
51
- if self.result:
52
- self.loop.found_anything = True
53
-
54
- def click(self, *, at: tuple[int, int] | None = None):
55
- """
56
- 点击寻找结果。若结果为空,会跳过执行。
57
-
58
- :return:
59
- """
60
- if self.result:
61
- if at is not None:
62
- device.click(*at)
63
- else:
64
- device.click(self.result)
65
-
66
- def call(self, func: Callable[[ClickableObjectProtocol], Any]):
67
- pass
68
-
69
-
70
- class Loop:
71
- def __init__(
72
- self,
73
- *,
74
- timeout: float = 300,
75
- interval: float = 0.3,
76
- auto_screenshot: bool = True,
77
- skip_first_wait: bool = True
78
- ):
79
- self.running = True
80
- self.found_anything = False
81
- self.auto_screenshot = auto_screenshot
82
- """
83
- 是否在每次循环开始时(Loop.tick() 被调用时)截图。
84
- """
85
- self.__last_loop: float = -1
86
- self.interval = interval
87
- """每次循环后等待的时间。"""
88
- self.screenshot: MatLike | None = None
89
- """上次截图时的图像数据。"""
90
- self.__skip_first_wait = skip_first_wait
91
- self.__is_first_tick = True
92
-
93
- def __iter__(self):
94
- self.__is_first_tick = True
95
- vars.flow.check()
96
- return self
97
-
98
- def __next__(self):
99
- if not self.running:
100
- raise StopIteration
101
- self.found_anything = False
102
- self.__last_loop = time.time()
103
- return self.tick()
104
-
105
- def tick(self):
106
- if not (self.__is_first_tick and self.__skip_first_wait):
107
- time.sleep(self.interval)
108
- self.__is_first_tick = False
109
-
110
- if self.auto_screenshot:
111
- self.screenshot = device.screenshot()
112
- self.__last_loop = time.time()
113
- self.found_anything = False
114
- return self
115
-
116
- def exit(self):
117
- """
118
- 结束循环。
119
- """
120
- self.running = False
121
-
122
- @overload
123
- @deprecated('Use plain if statement instead.')
124
- def when(self, condition: Image) -> LoopAction:
125
- ...
126
-
127
- @overload
128
- @deprecated('Use plain if statement instead.')
129
- def when(self, condition: TextComparator) -> LoopAction:
130
- ...
131
-
132
- @deprecated('Use plain if statement instead.')
133
- def when(self, condition: Any):
134
- """
135
- 判断某个条件是否成立。
136
-
137
- :param condition:
138
- :return:
139
- """
140
- if isinstance(condition, Image):
141
- func = partial(image.find, condition)
142
- elif isinstance(condition, TextComparator):
143
- func = partial(ocr.find, condition)
144
- else:
145
- raise ValueError('Invalid condition type.')
146
- la = LoopAction(self, func)
147
- la.reset()
148
- la.do()
149
- return la
150
-
151
- @deprecated('Use plain if statement instead.')
152
- def until(self, condition: Any):
153
- """
154
- 当满足指定条件时,结束循环。
155
-
156
- 等价于 ``loop.when(...).call(lambda _: loop.exit())``
157
- """
158
- return self.when(condition).call(lambda _: self.exit())
159
-
160
- @deprecated('Use image.find() and device.click() instead.')
161
- def click_if(self, condition: Any, *, at: tuple[int, int] | None = None):
162
- """
163
- 检测指定对象是否出现,若出现,点击该对象或指定位置。
164
-
165
- ``click_if()`` 等价于 ``loop.when(...).click(...)``。
166
-
167
- :param condition: 检测目标。
168
- :param at: 点击位置。若为 None,表示点击找到的目标。
169
- """
170
- return self.when(condition).click(at=at)
171
-
172
- StateType = TypeVar('StateType')
173
- class StatedLoop(Loop, Generic[StateType]):
174
- def __init__(
175
- self,
176
- states: list[Any] | None = None,
177
- initial_state: StateType | None = None,
178
- *,
179
- timeout: float = 300,
180
- interval: float = 0.3,
181
- auto_screenshot: bool = True
182
- ):
183
- self.__tmp_states = states
184
- self.__tmp_initial_state = initial_state
185
- self.state: StateType
186
- super().__init__(timeout=timeout, interval=interval, auto_screenshot=auto_screenshot)
187
-
188
- def __iter__(self):
189
- # __retrive_state_values() 只能在非 __init__ 中调用
190
- self.__retrive_state_values()
191
- return super().__iter__()
192
-
193
- def __retrive_state_values(self):
194
- # HACK: __orig_class__ undocumented 属性
195
- if not hasattr(self, '__orig_class__'):
196
- # 如果 Foo 不是以参数化泛型的方式实例化的,可能没有 __orig_class__
197
- if self.state is None:
198
- raise ValueError('Either specify `states` or use StatedLoop[Literal[...]] syntax.')
199
- else:
200
- generic_type_args = get_args(self.__orig_class__) # type: ignore
201
- if len(generic_type_args) != 1:
202
- raise ValueError('StatedLoop must have exactly one generic type argument.')
203
- state_values = get_args(generic_type_args[0])
204
- if not state_values:
205
- raise ValueError('StatedLoop must have at least one state value.')
206
- self.states = cast(tuple[StateType, ...], state_values)
207
- self.state = self.__tmp_initial_state or self.states[0]
208
- return state_values
1
+ import time
2
+ from functools import lru_cache, partial
3
+ from typing import Callable, Any, overload, Literal, Generic, TypeVar, cast, get_args, get_origin
4
+ from typing_extensions import deprecated
5
+
6
+ from cv2.typing import MatLike
7
+
8
+ from kotonebot.config.config import conf
9
+ from kotonebot.util import Interval
10
+ from kotonebot import device, image, ocr
11
+ from kotonebot.backend.core import Image
12
+ from kotonebot.backend.ocr import TextComparator
13
+ from kotonebot.client.protocol import ClickableObjectProtocol
14
+ from .context import vars
15
+
16
+ @deprecated('No longer used.')
17
+ class LoopAction:
18
+ def __init__(self, loop: 'Loop', func: Callable[[], ClickableObjectProtocol | None]):
19
+ self.loop = loop
20
+ self.func = func
21
+ self.result: ClickableObjectProtocol | None = None
22
+
23
+ @property
24
+ def found(self):
25
+ """
26
+ 是否找到结果。若父 Loop 未在运行中,则返回 False。
27
+ """
28
+ if not self.loop.running:
29
+ return False
30
+ return bool(self.result)
31
+
32
+ def __bool__(self):
33
+ return self.found
34
+
35
+ def reset(self):
36
+ """
37
+ 重置 LoopAction,以复用此对象。
38
+ """
39
+ self.result = None
40
+
41
+ def do(self):
42
+ """
43
+ 执行 LoopAction。
44
+ :return: 执行结果。
45
+ """
46
+ if not self.loop.running:
47
+ return
48
+ if self.loop.found_anything:
49
+ # 本轮循环已执行任意操作,因此不需要再继续检测
50
+ return
51
+ self.result = self.func()
52
+ if self.result:
53
+ self.loop.found_anything = True
54
+
55
+ def click(self, *, at: tuple[int, int] | None = None):
56
+ """
57
+ 点击寻找结果。若结果为空,会跳过执行。
58
+
59
+ :return:
60
+ """
61
+ if self.result:
62
+ if at is not None:
63
+ device.click(*at)
64
+ else:
65
+ device.click(self.result)
66
+
67
+ def call(self, func: Callable[[ClickableObjectProtocol], Any]):
68
+ pass
69
+
70
+
71
+ class Loop:
72
+ def __init__(
73
+ self,
74
+ *,
75
+ timeout: float = 300,
76
+ interval: float = 0.3,
77
+ auto_screenshot: bool = True,
78
+ skip_first_wait: bool = True
79
+ ):
80
+ self.running = True
81
+ self.found_anything = False
82
+ self.auto_screenshot = auto_screenshot
83
+ """
84
+ 是否在每次循环开始时(Loop.tick() 被调用时)截图。
85
+ """
86
+ self.__last_loop: float = -1
87
+ self.interval = interval
88
+ """每次循环后等待的时间。"""
89
+ self.screenshot: MatLike | None = None
90
+ """上次截图时的图像数据。"""
91
+ self.__skip_first_wait = skip_first_wait
92
+ self.__is_first_tick = True
93
+
94
+ def __iter__(self):
95
+ self.__is_first_tick = True
96
+ vars.flow.check()
97
+ return self
98
+
99
+ def __next__(self):
100
+ if not self.running:
101
+ raise StopIteration
102
+ self.found_anything = False
103
+ self.__last_loop = time.time()
104
+ return self.tick()
105
+
106
+ def tick(self):
107
+ if not (self.__is_first_tick and self.__skip_first_wait):
108
+ time.sleep(self.interval)
109
+ self.__is_first_tick = False
110
+
111
+ if self.auto_screenshot:
112
+ self.screenshot = device.screenshot()
113
+ self.__last_loop = time.time()
114
+ self.found_anything = False
115
+ # 执行全局回调
116
+ callbacks = conf().loop.loop_callbacks
117
+ while True:
118
+ did = False
119
+ for cb in callbacks:
120
+ did = cb(self)
121
+ if did:
122
+ time.sleep(self.interval)
123
+ self.screenshot = device.screenshot()
124
+ break
125
+ if not did:
126
+ break
127
+
128
+ return self
129
+
130
+ def exit(self):
131
+ """
132
+ 结束循环。
133
+ """
134
+ self.running = False
135
+
136
+ @overload
137
+ @deprecated('Use plain if statement instead.')
138
+ def when(self, condition: Image) -> LoopAction:
139
+ ...
140
+
141
+ @overload
142
+ @deprecated('Use plain if statement instead.')
143
+ def when(self, condition: TextComparator) -> LoopAction:
144
+ ...
145
+
146
+ @deprecated('Use plain if statement instead.')
147
+ def when(self, condition: Any):
148
+ """
149
+ 判断某个条件是否成立。
150
+
151
+ :param condition:
152
+ :return:
153
+ """
154
+ if isinstance(condition, Image):
155
+ func = partial(image.find, condition)
156
+ elif isinstance(condition, TextComparator):
157
+ func = partial(ocr.find, condition)
158
+ else:
159
+ raise ValueError('Invalid condition type.')
160
+ la = LoopAction(self, func)
161
+ la.reset()
162
+ la.do()
163
+ return la
164
+
165
+ @deprecated('Use plain if statement instead.')
166
+ def until(self, condition: Any):
167
+ """
168
+ 当满足指定条件时,结束循环。
169
+
170
+ 等价于 ``loop.when(...).call(lambda _: loop.exit())``
171
+ """
172
+ return self.when(condition).call(lambda _: self.exit())
173
+
174
+ @deprecated('Use image.find() and device.click() instead.')
175
+ def click_if(self, condition: Any, *, at: tuple[int, int] | None = None):
176
+ """
177
+ 检测指定对象是否出现,若出现,点击该对象或指定位置。
178
+
179
+ ``click_if()`` 等价于 ``loop.when(...).click(...)``。
180
+
181
+ :param condition: 检测目标。
182
+ :param at: 点击位置。若为 None,表示点击找到的目标。
183
+ """
184
+ return self.when(condition).click(at=at)
185
+
186
+ StateType = TypeVar('StateType')
187
+ class StatedLoop(Loop, Generic[StateType]):
188
+ def __init__(
189
+ self,
190
+ states: list[Any] | None = None,
191
+ initial_state: StateType | None = None,
192
+ *,
193
+ timeout: float = 300,
194
+ interval: float = 0.3,
195
+ auto_screenshot: bool = True
196
+ ):
197
+ self.__tmp_states = states
198
+ self.__tmp_initial_state = initial_state
199
+ self.state: StateType
200
+ super().__init__(timeout=timeout, interval=interval, auto_screenshot=auto_screenshot)
201
+
202
+ def __iter__(self):
203
+ # __retrive_state_values() 只能在非 __init__ 中调用
204
+ self.__retrive_state_values()
205
+ return super().__iter__()
206
+
207
+ def __retrive_state_values(self):
208
+ # HACK: __orig_class__ 是 undocumented 属性
209
+ if not hasattr(self, '__orig_class__'):
210
+ # 如果 Foo 不是以参数化泛型的方式实例化的,可能没有 __orig_class__
211
+ if self.state is None:
212
+ raise ValueError('Either specify `states` or use StatedLoop[Literal[...]] syntax.')
213
+ else:
214
+ generic_type_args = get_args(self.__orig_class__) # type: ignore
215
+ if len(generic_type_args) != 1:
216
+ raise ValueError('StatedLoop must have exactly one generic type argument.')
217
+ state_values = get_args(generic_type_args[0])
218
+ if not state_values:
219
+ raise ValueError('StatedLoop must have at least one state value.')
220
+ self.states = cast(tuple[StateType, ...], state_values)
221
+ self.state = self.__tmp_initial_state or self.states[0]
222
+ return state_values