maxapi-python 0.1.0__py3-none-any.whl → 0.1.2__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.
- maxapi_python-0.1.2.dist-info/METADATA +163 -0
- maxapi_python-0.1.2.dist-info/RECORD +25 -0
- {maxapi_python-0.1.0.dist-info → maxapi_python-0.1.2.dist-info}/WHEEL +1 -2
- pymax/core.py +37 -481
- pymax/crud.py +1 -1
- pymax/files.py +85 -0
- pymax/filters.py +38 -0
- pymax/interfaces.py +67 -0
- pymax/mixins/__init__.py +18 -0
- pymax/mixins/auth.py +81 -0
- pymax/mixins/channel.py +25 -0
- pymax/mixins/group.py +220 -0
- pymax/mixins/handler.py +60 -0
- pymax/mixins/message.py +293 -0
- pymax/mixins/self.py +38 -0
- pymax/mixins/user.py +82 -0
- pymax/mixins/websocket.py +242 -0
- pymax/payloads.py +175 -0
- pymax/static.py +143 -19
- pymax/types.py +134 -29
- pymax/utils.py +38 -0
- maxapi_python-0.1.0.dist-info/METADATA +0 -110
- maxapi_python-0.1.0.dist-info/RECORD +0 -12
- maxapi_python-0.1.0.dist-info/top_level.txt +0 -1
- {maxapi_python-0.1.0.dist-info → maxapi_python-0.1.2.dist-info}/licenses/LICENSE +0 -0
pymax/filters.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
from .static import MessageStatus, MessageType
|
2
|
+
from .types import Message
|
3
|
+
|
4
|
+
|
5
|
+
class Filter:
|
6
|
+
def __init__(
|
7
|
+
self,
|
8
|
+
user_id: int | None = None,
|
9
|
+
text: list[str] | None = None,
|
10
|
+
status: MessageStatus | str | None = None,
|
11
|
+
type: MessageType | str | None = None,
|
12
|
+
text_contains: str | None = None,
|
13
|
+
reaction_info: bool | None = None,
|
14
|
+
) -> None:
|
15
|
+
self.user_id = user_id
|
16
|
+
self.text = text
|
17
|
+
self.status = status
|
18
|
+
self.type = type
|
19
|
+
self.reaction_info = reaction_info
|
20
|
+
self.text_contains = text_contains
|
21
|
+
|
22
|
+
def match(self, message: Message) -> bool:
|
23
|
+
if self.user_id is not None and message.sender != self.user_id:
|
24
|
+
return False
|
25
|
+
if self.text is not None and any(
|
26
|
+
text not in message.text for text in self.text
|
27
|
+
):
|
28
|
+
return False
|
29
|
+
if self.text_contains is not None and self.text_contains not in message.text:
|
30
|
+
return False
|
31
|
+
if self.status is not None and message.status != self.status:
|
32
|
+
return False
|
33
|
+
if self.type is not None and message.type != self.type:
|
34
|
+
return False
|
35
|
+
if self.reaction_info is not None and message.reactionInfo is None: # noqa: SIM103
|
36
|
+
return False
|
37
|
+
|
38
|
+
return True
|
pymax/interfaces.py
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
from abc import ABC, abstractmethod
|
4
|
+
from collections.abc import Awaitable, Callable
|
5
|
+
from logging import Logger
|
6
|
+
from pathlib import Path
|
7
|
+
from typing import TYPE_CHECKING, Any
|
8
|
+
|
9
|
+
import websockets
|
10
|
+
|
11
|
+
from .filters import Filter
|
12
|
+
from .static import Constants
|
13
|
+
from .types import Channel, Chat, Dialog, Me, Message, User
|
14
|
+
|
15
|
+
if TYPE_CHECKING:
|
16
|
+
from uuid import UUID
|
17
|
+
|
18
|
+
from .crud import Database
|
19
|
+
|
20
|
+
|
21
|
+
class ClientProtocol(ABC):
|
22
|
+
def __init__(self, logger: Logger) -> None:
|
23
|
+
super().__init__()
|
24
|
+
self.logger = logger
|
25
|
+
self._users: dict[int, User] = {}
|
26
|
+
self.chats: list[Chat] = []
|
27
|
+
self.phone: str = ""
|
28
|
+
self._database: Database
|
29
|
+
self._device_id: UUID
|
30
|
+
self._on_message_handlers: list[
|
31
|
+
tuple[Callable[[Message], Any], Filter | None]
|
32
|
+
] = []
|
33
|
+
self.uri: str
|
34
|
+
self.is_connected: bool = False
|
35
|
+
self.phone: str
|
36
|
+
self.chats: list[Chat] = []
|
37
|
+
self.dialogs: list[Dialog] = []
|
38
|
+
self.channels: list[Channel] = []
|
39
|
+
self.me: Me | None = None
|
40
|
+
self._users: dict[int, User] = {}
|
41
|
+
self._work_dir: str
|
42
|
+
self._database_path: Path
|
43
|
+
self._ws: websockets.ClientConnection | None = None
|
44
|
+
self._seq: int = 0
|
45
|
+
self._pending: dict[int, asyncio.Future[dict[str, Any]]] = {}
|
46
|
+
self._recv_task: asyncio.Task[Any] | None = None
|
47
|
+
self._incoming: asyncio.Queue[dict[str, Any]] | None = None
|
48
|
+
self.user_agent = Constants.DEFAULT_USER_AGENT.value
|
49
|
+
self._on_message_handlers: list[
|
50
|
+
tuple[Callable[[Message], Any], Filter | None]
|
51
|
+
] = []
|
52
|
+
self._on_start_handler: Callable[[], Any | Awaitable[Any]] | None = None
|
53
|
+
self._background_tasks: set[asyncio.Task[Any]] = set()
|
54
|
+
|
55
|
+
@abstractmethod
|
56
|
+
async def _send_and_wait(
|
57
|
+
self,
|
58
|
+
opcode: int,
|
59
|
+
payload: dict[str, Any],
|
60
|
+
cmd: int = 0,
|
61
|
+
timeout: float = Constants.DEFAULT_TIMEOUT.value,
|
62
|
+
) -> dict[str, Any]:
|
63
|
+
pass
|
64
|
+
|
65
|
+
@abstractmethod
|
66
|
+
async def _get_chat(self, chat_id: int) -> Chat | None:
|
67
|
+
pass
|
pymax/mixins/__init__.py
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
from .auth import AuthMixin
|
2
|
+
from .channel import ChannelMixin
|
3
|
+
from .handler import HandlerMixin
|
4
|
+
from .message import MessageMixin
|
5
|
+
from .self import SelfMixin
|
6
|
+
from .user import UserMixin
|
7
|
+
from .websocket import WebSocketMixin
|
8
|
+
|
9
|
+
|
10
|
+
class ApiMixin(
|
11
|
+
AuthMixin,
|
12
|
+
HandlerMixin,
|
13
|
+
UserMixin,
|
14
|
+
ChannelMixin,
|
15
|
+
SelfMixin,
|
16
|
+
MessageMixin,
|
17
|
+
):
|
18
|
+
pass
|
pymax/mixins/auth.py
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
import asyncio
|
2
|
+
import re
|
3
|
+
from typing import Any
|
4
|
+
|
5
|
+
from pymax.interfaces import ClientProtocol
|
6
|
+
from pymax.payloads import RequestCodePayload, SendCodePayload
|
7
|
+
from pymax.static import AuthType, Constants, Opcode
|
8
|
+
|
9
|
+
|
10
|
+
class AuthMixin(ClientProtocol):
|
11
|
+
def _check_phone(self) -> bool:
|
12
|
+
return bool(re.match(Constants.PHONE_REGEX.value, self.phone))
|
13
|
+
|
14
|
+
async def _request_code(
|
15
|
+
self, phone: str, language: str = "ru"
|
16
|
+
) -> dict[str, int | str]:
|
17
|
+
try:
|
18
|
+
self.logger.info("Requesting auth code")
|
19
|
+
|
20
|
+
payload = RequestCodePayload(
|
21
|
+
phone=phone, type=AuthType.START_AUTH, language=language
|
22
|
+
).model_dump(by_alias=True)
|
23
|
+
|
24
|
+
data = await self._send_and_wait(
|
25
|
+
opcode=Opcode.AUTH_REQUEST, payload=payload
|
26
|
+
)
|
27
|
+
self.logger.debug(
|
28
|
+
"Code request response opcode=%s seq=%s",
|
29
|
+
data.get("opcode"),
|
30
|
+
data.get("seq"),
|
31
|
+
)
|
32
|
+
return data.get("payload")
|
33
|
+
except Exception:
|
34
|
+
self.logger.error("Request code failed", exc_info=True)
|
35
|
+
raise RuntimeError("Request code failed")
|
36
|
+
|
37
|
+
async def _send_code(self, code: str, token: str) -> dict[str, Any]:
|
38
|
+
try:
|
39
|
+
self.logger.info("Sending verification code")
|
40
|
+
|
41
|
+
payload = SendCodePayload(
|
42
|
+
token=token,
|
43
|
+
verify_code=code,
|
44
|
+
auth_token_type=AuthType.CHECK_CODE,
|
45
|
+
).model_dump(by_alias=True)
|
46
|
+
|
47
|
+
data = await self._send_and_wait(opcode=Opcode.AUTH, payload=payload)
|
48
|
+
self.logger.debug(
|
49
|
+
"Send code response opcode=%s seq=%s",
|
50
|
+
data.get("opcode"),
|
51
|
+
data.get("seq"),
|
52
|
+
)
|
53
|
+
return data.get("payload")
|
54
|
+
except Exception:
|
55
|
+
self.logger.error("Send code failed", exc_info=True)
|
56
|
+
raise RuntimeError("Send code failed")
|
57
|
+
|
58
|
+
async def _login(self) -> None:
|
59
|
+
self.logger.info("Starting login flow")
|
60
|
+
request_code_payload = await self._request_code(self.phone)
|
61
|
+
temp_token = request_code_payload.get("token")
|
62
|
+
if not temp_token or not isinstance(temp_token, str):
|
63
|
+
self.logger.critical("Failed to request code: token missing")
|
64
|
+
raise ValueError("Failed to request code")
|
65
|
+
|
66
|
+
code = await asyncio.to_thread(input, "Введите код: ")
|
67
|
+
if len(code) != 6 or not code.isdigit():
|
68
|
+
self.logger.error("Invalid code format entered")
|
69
|
+
raise ValueError("Invalid code format")
|
70
|
+
|
71
|
+
login_resp = await self._send_code(code, temp_token)
|
72
|
+
token: str | None = (
|
73
|
+
login_resp.get("tokenAttrs", {}).get("LOGIN", {}).get("token")
|
74
|
+
)
|
75
|
+
if not token:
|
76
|
+
self.logger.critical("Failed to login, token not received")
|
77
|
+
raise ValueError("Failed to login, token not received")
|
78
|
+
|
79
|
+
self._token = token
|
80
|
+
self._database.update_auth_token(self._device_id, self._token)
|
81
|
+
self.logger.info("Login successful, token saved to database")
|
pymax/mixins/channel.py
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
from pymax.interfaces import ClientProtocol
|
2
|
+
from pymax.payloads import ResolveLinkPayload
|
3
|
+
from pymax.static import Opcode
|
4
|
+
|
5
|
+
|
6
|
+
class ChannelMixin(ClientProtocol):
|
7
|
+
async def resolve_channel_by_name(self, name: str) -> bool:
|
8
|
+
"""
|
9
|
+
Пытается найти канал по его имени
|
10
|
+
|
11
|
+
Args:
|
12
|
+
name (str): Имя канала
|
13
|
+
|
14
|
+
Returns:
|
15
|
+
bool: True, если канал найден
|
16
|
+
"""
|
17
|
+
payload = ResolveLinkPayload(
|
18
|
+
link=f"https://max.ru/{name}",
|
19
|
+
).model_dump(by_alias=True)
|
20
|
+
|
21
|
+
data = await self._send_and_wait(opcode=Opcode.LINK_INFO, payload=payload)
|
22
|
+
if error := data.get("payload", {}).get("error"):
|
23
|
+
self.logger.error("Resolve link error: %s", error)
|
24
|
+
return False
|
25
|
+
return True
|
pymax/mixins/group.py
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
import time
|
2
|
+
|
3
|
+
from pymax.interfaces import ClientProtocol
|
4
|
+
from pymax.payloads import (
|
5
|
+
ChangeGroupProfilePayload,
|
6
|
+
ChangeGroupSettingsOptions,
|
7
|
+
ChangeGroupSettingsPayload,
|
8
|
+
CreateGroupAttach,
|
9
|
+
CreateGroupMessage,
|
10
|
+
CreateGroupPayload,
|
11
|
+
InviteUsersPayload,
|
12
|
+
RemoveUsersPayload,
|
13
|
+
)
|
14
|
+
from pymax.static import Opcode
|
15
|
+
from pymax.types import Chat, Message
|
16
|
+
|
17
|
+
|
18
|
+
class GroupMixin(ClientProtocol):
|
19
|
+
async def create_group(
|
20
|
+
self, name: str, participant_ids: list[int] | None = None, notify: bool = True
|
21
|
+
) -> tuple[Chat, Message] | None:
|
22
|
+
"""
|
23
|
+
Создает группу
|
24
|
+
|
25
|
+
Args:
|
26
|
+
name (str): Название группы.
|
27
|
+
participant_ids (list[int] | None, optional): Список идентификаторов участников. Defaults to None.
|
28
|
+
notify (bool, optional): Флаг оповещения. Defaults to True.
|
29
|
+
|
30
|
+
Returns:
|
31
|
+
tuple[Chat, Message] | None: Объект Chat и Message или None при ошибке.
|
32
|
+
"""
|
33
|
+
try:
|
34
|
+
payload = CreateGroupPayload(
|
35
|
+
message=CreateGroupMessage(
|
36
|
+
cid=int(time.time() * 1000),
|
37
|
+
attaches=[
|
38
|
+
CreateGroupAttach(
|
39
|
+
_type="CONTROL",
|
40
|
+
title=name,
|
41
|
+
user_ids=participant_ids if participant_ids else [],
|
42
|
+
)
|
43
|
+
],
|
44
|
+
),
|
45
|
+
notify=notify,
|
46
|
+
).model_dump(by_alias=True)
|
47
|
+
|
48
|
+
data = await self._send_and_wait(opcode=Opcode.MSG_SEND, payload=payload)
|
49
|
+
if error := data.get("payload", {}).get("error"):
|
50
|
+
self.logger.error("Create group error: %s", error)
|
51
|
+
return None
|
52
|
+
|
53
|
+
chat = Chat.from_dict(data["payload"]["chat"])
|
54
|
+
message = Message.from_dict(data["payload"]["message"])
|
55
|
+
|
56
|
+
if chat:
|
57
|
+
cached_chat = await self._get_chat(chat.id)
|
58
|
+
if cached_chat is None:
|
59
|
+
self.chats.append(chat)
|
60
|
+
else:
|
61
|
+
idx = self.chats.index(cached_chat)
|
62
|
+
self.chats[idx] = chat
|
63
|
+
|
64
|
+
return chat, message
|
65
|
+
|
66
|
+
except Exception:
|
67
|
+
self.logger.exception("Create group failed")
|
68
|
+
|
69
|
+
async def invite_users_to_group(
|
70
|
+
self,
|
71
|
+
chat_id: int,
|
72
|
+
user_ids: list[int],
|
73
|
+
show_history: bool = True,
|
74
|
+
) -> bool:
|
75
|
+
"""
|
76
|
+
Приглашает пользователей в группу
|
77
|
+
|
78
|
+
Args:
|
79
|
+
chat_id (int): ID группы.
|
80
|
+
user_ids (list[int]): Список идентификаторов пользователей.
|
81
|
+
show_history (bool, optional): Флаг оповещения. Defaults to True.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
bool: True, если пользователи успешно приглашены
|
85
|
+
"""
|
86
|
+
try:
|
87
|
+
payload = InviteUsersPayload(
|
88
|
+
chat_id=chat_id,
|
89
|
+
user_ids=user_ids,
|
90
|
+
show_history=show_history,
|
91
|
+
operation="add",
|
92
|
+
).model_dump(by_alias=True)
|
93
|
+
|
94
|
+
data = await self._send_and_wait(
|
95
|
+
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
96
|
+
)
|
97
|
+
|
98
|
+
if error := data.get("payload", {}).get("error"):
|
99
|
+
self.logger.error("Create group error: %s", error)
|
100
|
+
return False
|
101
|
+
|
102
|
+
chat = Chat.from_dict(data["payload"]["chat"])
|
103
|
+
if chat:
|
104
|
+
cached_chat = await self._get_chat(chat.id)
|
105
|
+
if cached_chat is None:
|
106
|
+
self.chats.append(chat)
|
107
|
+
else:
|
108
|
+
idx = self.chats.index(cached_chat)
|
109
|
+
self.chats[idx] = chat
|
110
|
+
|
111
|
+
return True
|
112
|
+
|
113
|
+
except Exception:
|
114
|
+
self.logger.exception("Invite users to group failed")
|
115
|
+
return False
|
116
|
+
|
117
|
+
async def remove_users_from_group(
|
118
|
+
self,
|
119
|
+
chat_id: int,
|
120
|
+
user_ids: list[int],
|
121
|
+
clean_msg_period: int,
|
122
|
+
) -> bool:
|
123
|
+
try:
|
124
|
+
payload = RemoveUsersPayload(
|
125
|
+
chat_id=chat_id,
|
126
|
+
user_ids=user_ids,
|
127
|
+
clean_msg_period=clean_msg_period,
|
128
|
+
).model_dump(by_alias=True)
|
129
|
+
|
130
|
+
data = await self._send_and_wait(
|
131
|
+
opcode=Opcode.CHAT_MEMBERS_UPDATE, payload=payload
|
132
|
+
)
|
133
|
+
|
134
|
+
if error := data.get("payload", {}).get("error"):
|
135
|
+
self.logger.error("Remove users from group error: %s", error)
|
136
|
+
return False
|
137
|
+
|
138
|
+
chat = Chat.from_dict(data["payload"]["chat"])
|
139
|
+
if chat:
|
140
|
+
cached_chat = await self._get_chat(chat.id)
|
141
|
+
if cached_chat is None:
|
142
|
+
self.chats.append(chat)
|
143
|
+
else:
|
144
|
+
idx = self.chats.index(cached_chat)
|
145
|
+
self.chats[idx] = chat
|
146
|
+
|
147
|
+
return True
|
148
|
+
except Exception:
|
149
|
+
self.logger.exception("Remove users from group failed")
|
150
|
+
return False
|
151
|
+
|
152
|
+
async def change_group_settings(
|
153
|
+
self,
|
154
|
+
chat_id: int,
|
155
|
+
all_can_pin_message: bool | None = None,
|
156
|
+
only_owner_can_change_icon_title: bool | None = None,
|
157
|
+
only_admin_can_add_member: bool | None = None,
|
158
|
+
only_admin_can_call: bool | None = None,
|
159
|
+
members_can_see_private_link: bool | None = None,
|
160
|
+
):
|
161
|
+
try:
|
162
|
+
payload = ChangeGroupSettingsPayload(
|
163
|
+
chat_id=chat_id,
|
164
|
+
options=ChangeGroupSettingsOptions(
|
165
|
+
ALL_CAN_PIN_MESSAGE=all_can_pin_message,
|
166
|
+
ONLY_OWNER_CAN_CHANGE_ICON_TITLE=only_owner_can_change_icon_title,
|
167
|
+
ONLY_ADMIN_CAN_ADD_MEMBER=only_admin_can_add_member,
|
168
|
+
ONLY_ADMIN_CAN_CALL=only_admin_can_call,
|
169
|
+
MEMBERS_CAN_SEE_PRIVATE_LINK=members_can_see_private_link,
|
170
|
+
),
|
171
|
+
).model_dump(by_alias=True, exclude_none=True)
|
172
|
+
|
173
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_UPDATE, payload=payload)
|
174
|
+
|
175
|
+
if error := data.get("payload", {}).get("error"):
|
176
|
+
self.logger.error("Change group settings error: %s", error)
|
177
|
+
return
|
178
|
+
|
179
|
+
chat = Chat.from_dict(data["payload"]["chat"])
|
180
|
+
if chat:
|
181
|
+
cached_chat = await self._get_chat(chat.id)
|
182
|
+
if cached_chat is None:
|
183
|
+
self.chats.append(chat)
|
184
|
+
else:
|
185
|
+
idx = self.chats.index(cached_chat)
|
186
|
+
self.chats[idx] = chat
|
187
|
+
|
188
|
+
except Exception:
|
189
|
+
self.logger.exception("Change group settings failed")
|
190
|
+
|
191
|
+
async def change_group_profile(
|
192
|
+
self,
|
193
|
+
chat_id: int,
|
194
|
+
name: str | None,
|
195
|
+
description: str | None = None,
|
196
|
+
):
|
197
|
+
try:
|
198
|
+
payload = ChangeGroupProfilePayload(
|
199
|
+
chat_id=chat_id,
|
200
|
+
theme=name,
|
201
|
+
description=description,
|
202
|
+
).model_dump(by_alias=True, exclude_none=True)
|
203
|
+
|
204
|
+
data = await self._send_and_wait(opcode=Opcode.CHAT_UPDATE, payload=payload)
|
205
|
+
|
206
|
+
if error := data.get("payload", {}).get("error"):
|
207
|
+
self.logger.error("Change group profile error: %s", error)
|
208
|
+
return
|
209
|
+
|
210
|
+
chat = Chat.from_dict(data["payload"]["chat"])
|
211
|
+
if chat:
|
212
|
+
cached_chat = await self._get_chat(chat.id)
|
213
|
+
if cached_chat is None:
|
214
|
+
self.chats.append(chat)
|
215
|
+
else:
|
216
|
+
idx = self.chats.index(cached_chat)
|
217
|
+
self.chats[idx] = chat
|
218
|
+
|
219
|
+
except Exception:
|
220
|
+
self.logger.exception("Change group profile failed")
|
pymax/mixins/handler.py
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
from typing import Any, Awaitable, Callable
|
2
|
+
|
3
|
+
from pymax.interfaces import ClientProtocol, Filter
|
4
|
+
from pymax.types import Message
|
5
|
+
|
6
|
+
|
7
|
+
class HandlerMixin(ClientProtocol):
|
8
|
+
def on_message(
|
9
|
+
self, *, filter: Filter | None = None
|
10
|
+
) -> Callable[
|
11
|
+
[Callable[[Any], Any | Awaitable[Any]]], Callable[[Any], Any | Awaitable[Any]]
|
12
|
+
]:
|
13
|
+
"""
|
14
|
+
Декоратор для установки обработчика входящих сообщений.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
filter: Фильтр для обработки сообщений.
|
18
|
+
|
19
|
+
Returns:
|
20
|
+
Декоратор.
|
21
|
+
"""
|
22
|
+
|
23
|
+
def decorator(
|
24
|
+
handler: Callable[[Any], Any | Awaitable[Any]],
|
25
|
+
) -> Callable[[Any], Any | Awaitable[Any]]:
|
26
|
+
self._on_message_handlers.append((handler, filter))
|
27
|
+
self.logger.info(f"on_message handler set: {handler}, filter: {filter}")
|
28
|
+
return handler
|
29
|
+
|
30
|
+
return decorator
|
31
|
+
|
32
|
+
def on_start(
|
33
|
+
self, handler: Callable[[], Any | Awaitable[Any]]
|
34
|
+
) -> Callable[[], Any | Awaitable[Any]]:
|
35
|
+
"""
|
36
|
+
Устанавливает обработчик, вызываемый при старте клиента.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
handler: Функция или coroutine без аргументов.
|
40
|
+
|
41
|
+
Returns:
|
42
|
+
Установленный обработчик.
|
43
|
+
"""
|
44
|
+
self._on_start_handler = handler
|
45
|
+
self.logger.debug("on_start handler set: %r", handler)
|
46
|
+
return handler
|
47
|
+
|
48
|
+
def add_message_handler(
|
49
|
+
self, handler: Callable[[Message], Any | Awaitable[Any]], filter: Filter | None
|
50
|
+
) -> Callable[[Message], Any | Awaitable[Any]]:
|
51
|
+
self.logger.debug("add_message_handler (alias) used")
|
52
|
+
self._on_message_handlers.append((handler, filter))
|
53
|
+
return handler
|
54
|
+
|
55
|
+
def add_on_start_handler(
|
56
|
+
self, handler: Callable[[], Any | Awaitable[Any]]
|
57
|
+
) -> Callable[[], Any | Awaitable[Any]]:
|
58
|
+
self.logger.debug("add_on_start_handler (alias) used")
|
59
|
+
self._on_start_handler = handler
|
60
|
+
return handler
|