maapy 0.1.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 (49) hide show
  1. maapy/__init__.py +93 -0
  2. maapy/_callback/__init__.py +9 -0
  3. maapy/_callback/common.py +47 -0
  4. maapy/_callback/global_taskchain.py +282 -0
  5. maapy/_callback/parser.py +851 -0
  6. maapy/_callback/payloads.py +392 -0
  7. maapy/_callback/subtask.py +70 -0
  8. maapy/_callback/subtask_roguelike.py +164 -0
  9. maapy/_callback/subtask_structured.py +491 -0
  10. maapy/_ffi.py +129 -0
  11. maapy/_json.py +38 -0
  12. maapy/_loader.py +77 -0
  13. maapy/_lowlevel.py +238 -0
  14. maapy/callback.py +314 -0
  15. maapy/client.py +843 -0
  16. maapy/connection.py +50 -0
  17. maapy/constants.py +325 -0
  18. maapy/events/__init__.py +295 -0
  19. maapy/events/_base.py +26 -0
  20. maapy/events/global_events.py +195 -0
  21. maapy/events/rules.py +380 -0
  22. maapy/events/subtask_events.py +654 -0
  23. maapy/events/taskchain_events.py +40 -0
  24. maapy/exceptions.py +38 -0
  25. maapy/game_data.py +119 -0
  26. maapy/instance.py +274 -0
  27. maapy/py.typed +0 -0
  28. maapy/tasks/__init__.py +55 -0
  29. maapy/tasks/_base.py +130 -0
  30. maapy/tasks/award.py +20 -0
  31. maapy/tasks/closedown.py +21 -0
  32. maapy/tasks/copilot.py +106 -0
  33. maapy/tasks/custom.py +110 -0
  34. maapy/tasks/debug.py +14 -0
  35. maapy/tasks/depot.py +14 -0
  36. maapy/tasks/fight.py +52 -0
  37. maapy/tasks/infrast.py +35 -0
  38. maapy/tasks/mall.py +23 -0
  39. maapy/tasks/operbox.py +14 -0
  40. maapy/tasks/reclamation.py +21 -0
  41. maapy/tasks/recruit.py +82 -0
  42. maapy/tasks/roguelike.py +58 -0
  43. maapy/tasks/startup.py +23 -0
  44. maapy/utils/__init__.py +1 -0
  45. maapy/utils/image.py +27 -0
  46. maapy-0.1.0.dist-info/METADATA +24 -0
  47. maapy-0.1.0.dist-info/RECORD +49 -0
  48. maapy-0.1.0.dist-info/WHEEL +4 -0
  49. maapy-0.1.0.dist-info/licenses/LICENSE +8 -0
maapy/__init__.py ADDED
@@ -0,0 +1,93 @@
1
+ """maapy——基于 CFFI 的 MaaCore.dll Python 绑定。
2
+
3
+ 使用方式:
4
+ from maapy import MaaClient
5
+ from maapy.tasks import FightTask, StartUpTask
6
+ from maapy.events import StageDropsEvent, ConnectionEvent
7
+
8
+ MaaClient.load(core_dir="F:/maa")
9
+ client = MaaClient()
10
+ client.connect("127.0.0.1:5555")
11
+ task = client.append(FightTask(stage="1-7", times=10), tag="farm")
12
+ client.start()
13
+ task.wait()
14
+ """
15
+
16
+ from .client import MaaClient, TaskHandle, TaskResult, TaskStatus
17
+ from .connection import (
18
+ ConnectionExtras,
19
+ LDPlayerConnectionExtras,
20
+ MuMuConnectionExtras,
21
+ )
22
+ from .constants import (
23
+ AsstMsg,
24
+ AsstTaskType,
25
+ ClientType,
26
+ ConnectionWhat,
27
+ Facility,
28
+ InstanceOptionKey,
29
+ ReclamationTheme,
30
+ RoguelikeTheme,
31
+ Server,
32
+ StaticOptionKey,
33
+ SubTaskWhat,
34
+ Win32InputMethod,
35
+ Win32ScreencapMethod,
36
+ )
37
+ from .events import (
38
+ AllTasksCompletedEvent,
39
+ AsyncCallInfoEvent,
40
+ CallbackErrorEvent,
41
+ ConnectionEvent,
42
+ E,
43
+ AppendTask,
44
+ CancelAndAppend,
45
+ CancelTask,
46
+ Continue,
47
+ StageDropsEvent,
48
+ StopCore,
49
+ TaskChainCompletedEvent,
50
+ TaskChainErrorEvent,
51
+ TaskChainStartEvent,
52
+ UpdateParams,
53
+ )
54
+ from .exceptions import (
55
+ MaaCallbackError,
56
+ MaaConnectionError,
57
+ MaaDataError,
58
+ MaaError,
59
+ MaaLoadError,
60
+ MaaTaskError,
61
+ MaaValidationError,
62
+ )
63
+
64
+ __all__ = [
65
+ "MaaClient",
66
+ "TaskHandle",
67
+ "TaskResult",
68
+ "TaskStatus",
69
+ "ConnectionExtras",
70
+ "MuMuConnectionExtras",
71
+ "LDPlayerConnectionExtras",
72
+ "AsstMsg",
73
+ "AsstTaskType",
74
+ "StaticOptionKey",
75
+ "InstanceOptionKey",
76
+ "Win32ScreencapMethod",
77
+ "Win32InputMethod",
78
+ "MaaError",
79
+ "MaaLoadError",
80
+ "MaaConnectionError",
81
+ "MaaTaskError",
82
+ "MaaValidationError",
83
+ "MaaDataError",
84
+ "MaaCallbackError",
85
+ "CallbackErrorEvent",
86
+ "E",
87
+ "Continue",
88
+ "StopCore",
89
+ "CancelTask",
90
+ "AppendTask",
91
+ "CancelAndAppend",
92
+ "UpdateParams",
93
+ ]
@@ -0,0 +1,9 @@
1
+ """回调解析子模块"""
2
+
3
+ from .global_taskchain import GlobalTaskchainParsingMixin
4
+ from .subtask import SubtaskParsingMixin
5
+
6
+ __all__ = [
7
+ "GlobalTaskchainParsingMixin",
8
+ "SubtaskParsingMixin",
9
+ ]
@@ -0,0 +1,47 @@
1
+ """回调解析共用的辅助函数和类型别名"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Callable, TypeAlias, TypedDict, TypeVar
6
+
7
+ import msgspec
8
+
9
+ from .._json import JsonDict, as_json_dict
10
+ from ..constants import AsstMsg
11
+ from ..events._base import Event
12
+
13
+ _MsgParser: TypeAlias = Callable[[AsstMsg, JsonDict], Event]
14
+ _EventFactory: TypeAlias = Callable[..., Event]
15
+ _SubTaskExtraParser: TypeAlias = Callable[..., Event]
16
+ _T = TypeVar("_T")
17
+
18
+
19
+ class _SubtaskEventKwargs(TypedDict):
20
+ msg: int
21
+ uuid: str
22
+ subtask: str
23
+ class_name: str
24
+ taskchain: str
25
+ taskid: int
26
+ what: str
27
+ why: str
28
+
29
+
30
+ def _decode_struct(value: object, model: type[_T], field_name: str) -> _T:
31
+ try:
32
+ data = as_json_dict(value)
33
+ except msgspec.ValidationError as exc:
34
+ raise msgspec.ValidationError(f"{field_name}: {exc}") from exc
35
+ try:
36
+ return msgspec.convert(data, type=model)
37
+ except msgspec.ValidationError as exc:
38
+ raise msgspec.ValidationError(f"{field_name}: {exc}") from exc
39
+
40
+
41
+ __all__ = [
42
+ "_EventFactory",
43
+ "_MsgParser",
44
+ "_SubTaskExtraParser",
45
+ "_SubtaskEventKwargs",
46
+ "_decode_struct",
47
+ ]
@@ -0,0 +1,282 @@
1
+ """全局和任务链回调的解析逻辑"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import msgspec
6
+
7
+ from .._json import JsonDict
8
+ from ..constants import AsstMsg
9
+ from ..events._base import Event
10
+ from ..events.global_events import (
11
+ AllTasksCompletedEvent,
12
+ AsyncCallInfoEvent,
13
+ AttachWindowAsyncCallInfoEvent,
14
+ CallbackErrorEvent,
15
+ ClickAsyncCallInfoEvent,
16
+ ConnectAsyncCallInfoEvent,
17
+ ConnectFailedConnectionEvent,
18
+ ConnectedConnectionEvent,
19
+ ConnectionEvent,
20
+ DestroyedEvent,
21
+ DisconnectConnectionEvent,
22
+ FastestWayToScreencapConnectionEvent,
23
+ InitFailedEvent,
24
+ InternalErrorEvent,
25
+ ReconnectedConnectionEvent,
26
+ ReconnectingConnectionEvent,
27
+ ReportRequestEvent,
28
+ ResolutionErrorConnectionEvent,
29
+ ResolutionGotConnectionEvent,
30
+ ResolutionInfoConnectionEvent,
31
+ ScreencapAsyncCallInfoEvent,
32
+ ScreencapCostConnectionEvent,
33
+ ScreencapFailedConnectionEvent,
34
+ ScreencapMethodAlternative,
35
+ TouchModeNotAvailableConnectionEvent,
36
+ UnknownAsyncCallInfoEvent,
37
+ UnsupportedPlayToolsConnectionEvent,
38
+ UnsupportedResolutionConnectionEvent,
39
+ UuidGotConnectionEvent,
40
+ )
41
+ from ..events.taskchain_events import RoutingRestartTaskChainEvent, TaskChainExtraInfoEvent
42
+ from .common import _EventFactory, _MsgParser, _decode_struct
43
+ from .payloads import (
44
+ AllTasksCompletedPayload,
45
+ AsyncCallInfoPayload,
46
+ ConnectionInfoPayload,
47
+ InitFailedPayload,
48
+ ReportRequestPayload,
49
+ ScreencapAlternativePayload,
50
+ TaskChainPayload,
51
+ )
52
+
53
+
54
+ class GlobalTaskchainParsingMixin:
55
+ """集中处理全局事件和任务链事件的 JSON 解析"""
56
+
57
+ _msg_dispatch: dict[AsstMsg, _MsgParser]
58
+ _taskchain_event_classes: dict[AsstMsg, _EventFactory]
59
+
60
+ def _parse_and_route(self, msg: int, data: JsonDict) -> Event | None:
61
+ try:
62
+ msg_id = AsstMsg(msg)
63
+ except ValueError:
64
+ raise msgspec.ValidationError(f"unknown callback msg: {msg}") from None
65
+ parser = self._msg_dispatch.get(msg_id)
66
+ if parser is not None:
67
+ return parser(msg_id, data)
68
+ raise msgspec.ValidationError(f"unhandled callback msg: {msg_id.name}") from None
69
+
70
+ def _callback_error(self, msg: int, source: str, exc: BaseException) -> CallbackErrorEvent:
71
+ return CallbackErrorEvent(
72
+ msg=msg,
73
+ uuid="",
74
+ source=source,
75
+ error=f"{type(exc).__name__}: {exc}",
76
+ )
77
+
78
+ def _parse_internal_error(self, msg_id: AsstMsg, data: JsonDict) -> Event:
79
+ del data
80
+ return InternalErrorEvent(msg=int(msg_id), uuid="")
81
+
82
+ def _parse_init_failed(self, msg_id: AsstMsg, data: JsonDict) -> Event:
83
+ payload = _decode_struct(data, InitFailedPayload, "callback")
84
+ return InitFailedEvent(
85
+ msg=int(msg_id),
86
+ uuid="",
87
+ what=payload.what,
88
+ why=payload.why,
89
+ )
90
+
91
+ def _parse_connection_info(self, msg_id: AsstMsg, data: JsonDict) -> Event:
92
+ payload = _decode_struct(data, ConnectionInfoPayload, "callback")
93
+ details = payload.details
94
+ what = payload.what
95
+ common_kwargs = {
96
+ "msg": int(msg_id),
97
+ "uuid": payload.uuid,
98
+ "what": what,
99
+ "why": payload.why,
100
+ "connected": what in ("Connected", "UuidGot"),
101
+ }
102
+
103
+ if what == "ConnectFailed":
104
+ return ConnectFailedConnectionEvent(
105
+ **common_kwargs,
106
+ adb=details.adb,
107
+ address=details.address,
108
+ config=details.config,
109
+ adb_output=details.adb_output,
110
+ )
111
+ if what == "Connected":
112
+ return ConnectedConnectionEvent(
113
+ **common_kwargs,
114
+ adb=details.adb,
115
+ address=details.address,
116
+ config=details.config,
117
+ )
118
+ if what == "UuidGot":
119
+ return UuidGotConnectionEvent(
120
+ **common_kwargs,
121
+ adb=details.adb,
122
+ address=details.address,
123
+ config=details.config,
124
+ device_uuid=details.uuid,
125
+ )
126
+ if what == "UnsupportedResolution":
127
+ return UnsupportedResolutionConnectionEvent(
128
+ **common_kwargs,
129
+ width=details.width or payload.width,
130
+ height=details.height or payload.height,
131
+ )
132
+ if what == "ResolutionInfo":
133
+ return ResolutionInfoConnectionEvent(
134
+ **common_kwargs,
135
+ width=details.width or payload.width,
136
+ height=details.height or payload.height,
137
+ )
138
+ if what == "ResolutionGot":
139
+ return ResolutionGotConnectionEvent(
140
+ **common_kwargs,
141
+ adb=details.adb,
142
+ address=details.address,
143
+ config=details.config,
144
+ width=details.width or payload.width,
145
+ height=details.height or payload.height,
146
+ )
147
+ if what == "ResolutionError":
148
+ return ResolutionErrorConnectionEvent(
149
+ **common_kwargs,
150
+ adb=details.adb,
151
+ address=details.address,
152
+ config=details.config,
153
+ width=details.width or payload.width,
154
+ height=details.height or payload.height,
155
+ )
156
+ if what == "Reconnecting":
157
+ return ReconnectingConnectionEvent(
158
+ **common_kwargs,
159
+ reconnect=details.reconnect,
160
+ cmd=details.cmd,
161
+ times=details.times,
162
+ )
163
+ if what == "Reconnected":
164
+ return ReconnectedConnectionEvent(
165
+ **common_kwargs,
166
+ reconnect=details.reconnect,
167
+ cmd=details.cmd,
168
+ times=details.times,
169
+ )
170
+ if what == "Disconnect":
171
+ return DisconnectConnectionEvent(
172
+ **common_kwargs,
173
+ reconnect=details.reconnect,
174
+ cmd=details.cmd,
175
+ times=details.times,
176
+ )
177
+ if what == "ScreencapFailed":
178
+ return ScreencapFailedConnectionEvent(**common_kwargs)
179
+ if what == "TouchModeNotAvailable":
180
+ return TouchModeNotAvailableConnectionEvent(
181
+ **common_kwargs,
182
+ adb=details.adb,
183
+ address=details.address,
184
+ config=details.config,
185
+ )
186
+ if what == "FastestWayToScreencap":
187
+ return FastestWayToScreencapConnectionEvent(
188
+ **common_kwargs,
189
+ method=details.method,
190
+ cost=details.cost,
191
+ alternatives=[_screencap_alternative(item) for item in details.alternatives],
192
+ )
193
+ if what == "ScreencapCost":
194
+ return ScreencapCostConnectionEvent(
195
+ **common_kwargs,
196
+ min_cost=details.min_cost,
197
+ max_cost=details.max_cost,
198
+ avg_cost=details.avg_cost,
199
+ fault_times=details.fault_times,
200
+ )
201
+ if what == "UnsupportedPlayTools":
202
+ return UnsupportedPlayToolsConnectionEvent(**common_kwargs)
203
+ raise msgspec.ValidationError(f"unknown ConnectionInfo what: {what}") from None
204
+
205
+ def _parse_all_tasks_completed(self, msg_id: AsstMsg, data: JsonDict) -> Event:
206
+ payload = _decode_struct(data, AllTasksCompletedPayload, "callback")
207
+ return AllTasksCompletedEvent(
208
+ msg=int(msg_id),
209
+ uuid=payload.uuid,
210
+ taskchain=payload.taskchain,
211
+ finished_tasks=list(payload.finished_tasks),
212
+ )
213
+
214
+ def _parse_async_call_info(self, msg_id: AsstMsg, data: JsonDict) -> Event:
215
+ payload = _decode_struct(data, AsyncCallInfoPayload, "callback")
216
+ common_kwargs = {
217
+ "msg": int(msg_id),
218
+ "uuid": payload.uuid,
219
+ "what": payload.what,
220
+ "async_call_id": payload.async_call_id,
221
+ "ret": payload.details.ret,
222
+ "cost": payload.details.cost,
223
+ }
224
+ what = payload.what
225
+ if what == "Connect":
226
+ return ConnectAsyncCallInfoEvent(**common_kwargs)
227
+ if what == "AttachWindow":
228
+ return AttachWindowAsyncCallInfoEvent(**common_kwargs)
229
+ if what == "Click":
230
+ return ClickAsyncCallInfoEvent(**common_kwargs)
231
+ if what == "Screencap":
232
+ return ScreencapAsyncCallInfoEvent(**common_kwargs)
233
+ if what == "Unknown":
234
+ return UnknownAsyncCallInfoEvent(**common_kwargs)
235
+ raise msgspec.ValidationError(f"unknown AsyncCallInfo what: {what}") from None
236
+
237
+ def _parse_destroyed(self, msg_id: AsstMsg, data: JsonDict) -> Event:
238
+ payload = _decode_struct(data, TaskChainPayload, "callback")
239
+ return DestroyedEvent(msg=int(msg_id), uuid=payload.uuid)
240
+
241
+ def _parse_report_request(self, msg_id: AsstMsg, data: JsonDict) -> Event:
242
+ payload = _decode_struct(data, ReportRequestPayload, "callback")
243
+ return ReportRequestEvent(
244
+ msg=int(msg_id),
245
+ uuid=payload.uuid,
246
+ url=payload.url,
247
+ headers=dict(payload.headers),
248
+ body=payload.body,
249
+ subtask=payload.subtask,
250
+ )
251
+
252
+ def _parse_taskchain(self, msg_id: AsstMsg, data: JsonDict) -> Event:
253
+ payload = _decode_struct(data, TaskChainPayload, "callback")
254
+ if msg_id == AsstMsg.TASK_CHAIN_EXTRA_INFO:
255
+ common_kwargs = {
256
+ "msg": int(msg_id),
257
+ "uuid": payload.uuid,
258
+ "taskchain": payload.taskchain,
259
+ "taskid": payload.taskid,
260
+ "what": payload.what,
261
+ "why": payload.why,
262
+ }
263
+ if payload.what == "RoutingRestart":
264
+ return RoutingRestartTaskChainEvent(
265
+ **common_kwargs,
266
+ node_cost=payload.node_cost,
267
+ )
268
+ return TaskChainExtraInfoEvent(**common_kwargs)
269
+
270
+ event_class = self._taskchain_event_classes.get(msg_id)
271
+ if event_class is None:
272
+ raise msgspec.ValidationError(f"unhandled taskchain msg: {msg_id.name}")
273
+ return event_class(
274
+ msg=int(msg_id),
275
+ uuid=payload.uuid,
276
+ taskchain=payload.taskchain,
277
+ taskid=payload.taskid,
278
+ )
279
+
280
+
281
+ def _screencap_alternative(item: ScreencapAlternativePayload) -> ScreencapMethodAlternative:
282
+ return ScreencapMethodAlternative(method=item.method, cost=item.cost)