maxapi-sdk 0.12.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.
- maxapi/__init__.py +49 -0
- maxapi/bot.py +674 -0
- maxapi/builders/__init__.py +23 -0
- maxapi/builders/keyboards.py +133 -0
- maxapi/builders/media.py +81 -0
- maxapi/callback_schema.py +140 -0
- maxapi/client/__init__.py +3 -0
- maxapi/client/default.py +89 -0
- maxapi/compat/__init__.py +15 -0
- maxapi/connection/__init__.py +3 -0
- maxapi/connection/base.py +136 -0
- maxapi/dispatcher.py +487 -0
- maxapi/exceptions/__init__.py +3 -0
- maxapi/exceptions/max.py +45 -0
- maxapi/filters/__init__.py +25 -0
- maxapi/filters/base.py +73 -0
- maxapi/filters/command.py +28 -0
- maxapi/filters/common.py +78 -0
- maxapi/filters/text.py +71 -0
- maxapi/fsm/__init__.py +17 -0
- maxapi/fsm/context.py +41 -0
- maxapi/fsm/filters.py +30 -0
- maxapi/fsm/middleware.py +42 -0
- maxapi/fsm/state.py +33 -0
- maxapi/fsm/storage/__init__.py +4 -0
- maxapi/fsm/storage/base.py +30 -0
- maxapi/fsm/storage/memory.py +38 -0
- maxapi/middlewares/__init__.py +3 -0
- maxapi/middlewares/base.py +34 -0
- maxapi/plugins/__init__.py +3 -0
- maxapi/plugins/base.py +19 -0
- maxapi/py.typed +1 -0
- maxapi/runners/__init__.py +4 -0
- maxapi/runners/polling.py +55 -0
- maxapi/runners/webhook.py +72 -0
- maxapi/transport/__init__.py +13 -0
- maxapi/transport/client.py +239 -0
- maxapi/transport/config.py +81 -0
- maxapi/transport/errors.py +45 -0
- maxapi/types/__init__.py +73 -0
- maxapi/types/base.py +9 -0
- maxapi/types/bot_mixin.py +14 -0
- maxapi/types/models.py +308 -0
- maxapi_sdk-0.12.0.dist-info/METADATA +270 -0
- maxapi_sdk-0.12.0.dist-info/RECORD +48 -0
- maxapi_sdk-0.12.0.dist-info/WHEEL +5 -0
- maxapi_sdk-0.12.0.dist-info/licenses/LICENSE +21 -0
- maxapi_sdk-0.12.0.dist-info/top_level.txt +1 -0
maxapi/types/models.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ..builders import normalize_attachments
|
|
7
|
+
|
|
8
|
+
from pydantic import Field, PrivateAttr
|
|
9
|
+
|
|
10
|
+
from .base import ApiModel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TextFormat(str, Enum):
|
|
14
|
+
MARKDOWN = "markdown"
|
|
15
|
+
HTML = "html"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SenderAction(str, Enum):
|
|
19
|
+
TYPING_ON = "typing_on"
|
|
20
|
+
SENDING_PHOTO = "sending_photo"
|
|
21
|
+
SENDING_VIDEO = "sending_video"
|
|
22
|
+
SENDING_AUDIO = "sending_audio"
|
|
23
|
+
SENDING_FILE = "sending_file"
|
|
24
|
+
MARK_SEEN = "mark_seen"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class UploadType(str, Enum):
|
|
28
|
+
IMAGE = "image"
|
|
29
|
+
VIDEO = "video"
|
|
30
|
+
AUDIO = "audio"
|
|
31
|
+
FILE = "file"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UpdateType(str, Enum):
|
|
35
|
+
MESSAGE_CREATED = "message_created"
|
|
36
|
+
MESSAGE_CALLBACK = "message_callback"
|
|
37
|
+
MESSAGE_EDITED = "message_edited"
|
|
38
|
+
MESSAGE_REMOVED = "message_removed"
|
|
39
|
+
BOT_STARTED = "bot_started"
|
|
40
|
+
BOT_ADDED = "bot_added"
|
|
41
|
+
BOT_REMOVED = "bot_removed"
|
|
42
|
+
BOT_STOPPED = "bot_stopped"
|
|
43
|
+
USER_ADDED = "user_added"
|
|
44
|
+
USER_REMOVED = "user_removed"
|
|
45
|
+
CHAT_TITLE_CHANGED = "chat_title_changed"
|
|
46
|
+
DIALOG_CLEARED = "dialog_cleared"
|
|
47
|
+
DIALOG_MUTED = "dialog_muted"
|
|
48
|
+
DIALOG_UNMUTED = "dialog_unmuted"
|
|
49
|
+
DIALOG_REMOVED = "dialog_removed"
|
|
50
|
+
RAW_API_RESPONSE = "raw_api_response"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Image(ApiModel):
|
|
54
|
+
url: str | None = None
|
|
55
|
+
token: str | None = None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class BotCommand(ApiModel):
|
|
59
|
+
name: str
|
|
60
|
+
description: str | None = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class User(ApiModel):
|
|
64
|
+
user_id: int
|
|
65
|
+
first_name: str | None = None
|
|
66
|
+
last_name: str | None = None
|
|
67
|
+
username: str | None = None
|
|
68
|
+
is_bot: bool | None = None
|
|
69
|
+
last_activity_time: int | None = None
|
|
70
|
+
name: str | None = None
|
|
71
|
+
description: str | None = None
|
|
72
|
+
avatar_url: str | None = None
|
|
73
|
+
full_avatar_url: str | None = None
|
|
74
|
+
commands: list[BotCommand] | None = None
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Recipient(ApiModel):
|
|
78
|
+
chat_id: int | None = None
|
|
79
|
+
user_id: int | None = None
|
|
80
|
+
type: str | None = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class LinkedMessage(ApiModel):
|
|
84
|
+
type: str | None = None
|
|
85
|
+
message: dict[str, Any] | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class MessageBody(ApiModel):
|
|
89
|
+
text: str | None = None
|
|
90
|
+
attachments: list[dict[str, Any]] | None = None
|
|
91
|
+
link: dict[str, Any] | None = None
|
|
92
|
+
notify: bool | None = None
|
|
93
|
+
format: TextFormat | None = Field(default=None, alias="format")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Message(ApiModel):
|
|
97
|
+
message_id: str | None = None
|
|
98
|
+
sender: User | None = None
|
|
99
|
+
recipient: Recipient | None = None
|
|
100
|
+
timestamp: int | None = None
|
|
101
|
+
link: LinkedMessage | None = None
|
|
102
|
+
body: MessageBody | None = None
|
|
103
|
+
stat: dict[str, Any] | None = None
|
|
104
|
+
url: str | None = None
|
|
105
|
+
chat_id: int | None = None
|
|
106
|
+
callback_id: str | None = None
|
|
107
|
+
update_type: str | None = None
|
|
108
|
+
_bot: Any = PrivateAttr(default=None)
|
|
109
|
+
|
|
110
|
+
def bind_bot(self, bot: Any) -> "Message":
|
|
111
|
+
self._bot = bot
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
async def answer(
|
|
115
|
+
self,
|
|
116
|
+
text: str,
|
|
117
|
+
*,
|
|
118
|
+
format: TextFormat | None = None,
|
|
119
|
+
notify: bool | None = None,
|
|
120
|
+
attachments: list[dict[str, Any]] | None = None,
|
|
121
|
+
keyboard: Any | None = None,
|
|
122
|
+
disable_link_preview: bool | None = None,
|
|
123
|
+
) -> "SendMessageResponse":
|
|
124
|
+
if self._bot is None:
|
|
125
|
+
raise RuntimeError("Сообщение не связано с экземпляром Bot.")
|
|
126
|
+
target_chat_id = self.chat_id
|
|
127
|
+
if target_chat_id is None and self.recipient is not None:
|
|
128
|
+
target_chat_id = self.recipient.chat_id
|
|
129
|
+
target_user_id = None
|
|
130
|
+
if target_chat_id is None and self.recipient is not None:
|
|
131
|
+
target_user_id = self.recipient.user_id
|
|
132
|
+
return await self._bot.send_message(
|
|
133
|
+
chat_id=target_chat_id,
|
|
134
|
+
user_id=target_user_id,
|
|
135
|
+
text=text,
|
|
136
|
+
format=format,
|
|
137
|
+
notify=notify,
|
|
138
|
+
attachments=normalize_attachments(attachments, keyboard=keyboard),
|
|
139
|
+
disable_link_preview=disable_link_preview,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
async def reply(
|
|
143
|
+
self,
|
|
144
|
+
text: str,
|
|
145
|
+
*,
|
|
146
|
+
format: TextFormat | None = None,
|
|
147
|
+
notify: bool | None = None,
|
|
148
|
+
attachments: list[dict[str, Any]] | None = None,
|
|
149
|
+
keyboard: Any | None = None,
|
|
150
|
+
disable_link_preview: bool | None = None,
|
|
151
|
+
) -> "SendMessageResponse":
|
|
152
|
+
return await self.answer(
|
|
153
|
+
text=text,
|
|
154
|
+
format=format,
|
|
155
|
+
notify=notify,
|
|
156
|
+
attachments=attachments,
|
|
157
|
+
keyboard=keyboard,
|
|
158
|
+
disable_link_preview=disable_link_preview,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MessageList(ApiModel):
|
|
163
|
+
messages: list[Message]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class SendMessageResponse(ApiModel):
|
|
167
|
+
message: Message
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class EditMessageResponse(ApiModel):
|
|
171
|
+
message: Message
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class SuccessResponse(ApiModel):
|
|
175
|
+
success: bool
|
|
176
|
+
message: str | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class VideoInfo(ApiModel):
|
|
180
|
+
url: str | None = None
|
|
181
|
+
token: str | None = None
|
|
182
|
+
duration: int | None = None
|
|
183
|
+
width: int | None = None
|
|
184
|
+
height: int | None = None
|
|
185
|
+
preview_url: str | None = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class Chat(ApiModel):
|
|
189
|
+
chat_id: int
|
|
190
|
+
type: str | None = None
|
|
191
|
+
status: str | None = None
|
|
192
|
+
title: str | None = None
|
|
193
|
+
icon: Image | None = None
|
|
194
|
+
last_event_time: int | None = None
|
|
195
|
+
participants_count: int | None = None
|
|
196
|
+
owner_id: int | None = None
|
|
197
|
+
participants: dict[str, Any] | None = None
|
|
198
|
+
is_public: bool | None = None
|
|
199
|
+
link: str | None = None
|
|
200
|
+
description: str | None = None
|
|
201
|
+
dialog_with_user: User | None = None
|
|
202
|
+
chat_message_id: str | None = None
|
|
203
|
+
pinned_message: Message | None = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class ChatsPage(ApiModel):
|
|
207
|
+
chats: list[Chat]
|
|
208
|
+
marker: int | None = None
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ChatMember(User):
|
|
212
|
+
last_access_time: int | None = None
|
|
213
|
+
is_owner: bool | None = None
|
|
214
|
+
is_admin: bool | None = None
|
|
215
|
+
join_time: int | None = None
|
|
216
|
+
permissions: list[str] | None = None
|
|
217
|
+
alias: str | None = None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class ChatAdmin(ApiModel):
|
|
221
|
+
user_id: int
|
|
222
|
+
permissions: list[str] | None = None
|
|
223
|
+
alias: str | None = None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class MembersPage(ApiModel):
|
|
227
|
+
members: list[ChatMember]
|
|
228
|
+
marker: int | None = None
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class Subscription(ApiModel):
|
|
232
|
+
url: str
|
|
233
|
+
update_types: list[str] | None = None
|
|
234
|
+
secret: str | None = None
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class SubscriptionsPage(ApiModel):
|
|
238
|
+
subscriptions: list[Subscription]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class UploadResponse(ApiModel):
|
|
242
|
+
url: str
|
|
243
|
+
token: str | None = None
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class CallbackPayload(ApiModel):
|
|
247
|
+
callback_id: str | None = None
|
|
248
|
+
payload: Any | None = None
|
|
249
|
+
user: User | None = None
|
|
250
|
+
message: Message | None = None
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class Update(ApiModel):
|
|
254
|
+
update_type: UpdateType | str
|
|
255
|
+
timestamp: int | None = None
|
|
256
|
+
message: Message | None = None
|
|
257
|
+
callback: CallbackPayload | None = None
|
|
258
|
+
callback_id: str | None = None
|
|
259
|
+
user_locale: str | None = None
|
|
260
|
+
chat_id: int | None = None
|
|
261
|
+
user_id: int | None = None
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class UpdatesPage(ApiModel):
|
|
265
|
+
updates: list[Update]
|
|
266
|
+
marker: int | None = None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class SendMessageRequest(ApiModel):
|
|
270
|
+
text: str | None = None
|
|
271
|
+
attachments: list[dict[str, Any]] | None = None
|
|
272
|
+
link: dict[str, Any] | None = None
|
|
273
|
+
notify: bool | None = None
|
|
274
|
+
format: TextFormat | None = Field(default=None, alias="format")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class EditMessageRequest(ApiModel):
|
|
278
|
+
message_id: str
|
|
279
|
+
text: str | None = None
|
|
280
|
+
attachments: list[dict[str, Any]] | None = None
|
|
281
|
+
link: dict[str, Any] | None = None
|
|
282
|
+
notify: bool | None = None
|
|
283
|
+
format: TextFormat | None = Field(default=None, alias="format")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class WebhookRequest(ApiModel):
|
|
287
|
+
url: str
|
|
288
|
+
update_types: list[UpdateType | str] | None = None
|
|
289
|
+
secret: str | None = None
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class PinMessageRequest(ApiModel):
|
|
293
|
+
message_id: str
|
|
294
|
+
notify: bool | None = True
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class AddMembersRequest(ApiModel):
|
|
298
|
+
user_ids: list[int]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class AddAdminsRequest(ApiModel):
|
|
302
|
+
admins: list[ChatAdmin]
|
|
303
|
+
marker: int | None = None
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class AnswerCallbackRequest(ApiModel):
|
|
307
|
+
notification: str | None = None
|
|
308
|
+
message: MessageBody | None = None
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: maxapi-sdk
|
|
3
|
+
Version: 0.12.0
|
|
4
|
+
Summary: Python SDK for MAX Messenger Bot API
|
|
5
|
+
Author: Maxim Strekolovskiy
|
|
6
|
+
Maintainer: Maxim Strekolovskiy
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/Maxi-online/maxapi-sdk
|
|
9
|
+
Project-URL: Repository, https://github.com/Maxi-online/maxapi-sdk
|
|
10
|
+
Project-URL: Issues, https://github.com/Maxi-online/maxapi-sdk/issues
|
|
11
|
+
Keywords: max,messenger,bot,sdk,api,asyncio
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Framework :: AsyncIO
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: aiofiles>=24.1.0
|
|
25
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
26
|
+
Requires-Dist: pydantic>=2.8.0
|
|
27
|
+
Provides-Extra: webhook
|
|
28
|
+
Requires-Dist: fastapi>=0.111.0; extra == "webhook"
|
|
29
|
+
Requires-Dist: uvicorn>=0.30.0; extra == "webhook"
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
33
|
+
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
34
|
+
Requires-Dist: build>=1.2.0; extra == "dev"
|
|
35
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# maxapi-sdk 0.12.0
|
|
39
|
+
|
|
40
|
+
Python SDK для MAX Messenger Bot API.
|
|
41
|
+
|
|
42
|
+
Пакет публикуется в PyPI как `maxapi-sdk`, а в коде импортируется как `maxapi`.
|
|
43
|
+
|
|
44
|
+
## Что есть в пакете
|
|
45
|
+
|
|
46
|
+
- typed Bot API client для методов MAX;
|
|
47
|
+
- отдельные runtime-классы `PollingRunner` и `WebhookRunner`;
|
|
48
|
+
- transport-слой с retry/backoff и поддержкой `Retry-After`;
|
|
49
|
+
- `Router` и `Dispatcher` с middleware и инъекцией зависимостей в handlers;
|
|
50
|
+
- composable filters с операторами `&`, `|`, `~`;
|
|
51
|
+
- `InlineKeyboardBuilder` и media helpers для upload/send flow;
|
|
52
|
+
- FSM: `State`, `StatesGroup`, `FSMContext`, `MemoryStorage`, `StateFilter`;
|
|
53
|
+
- plugin API для модульного подключения функциональности;
|
|
54
|
+
- structured callback payload parsing через `CallbackPayloadSchema`;
|
|
55
|
+
- migration layer для старого стиля кода;
|
|
56
|
+
- GitHub Actions для тестов, сборки, GitHub Releases и публикации в PyPI.
|
|
57
|
+
|
|
58
|
+
## Установка
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install maxapi-sdk
|
|
62
|
+
pip install "maxapi-sdk[webhook]"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Установка для разработки
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install -e .
|
|
69
|
+
pip install -e .[webhook]
|
|
70
|
+
pip install -e .[dev]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Быстрый старт: polling
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
import os
|
|
78
|
+
|
|
79
|
+
from maxapi import Bot, Command, Dispatcher, InlineKeyboardBuilder
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
83
|
+
dp = Dispatcher()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dp.message_created(Command("start"))
|
|
87
|
+
async def handle_start(event):
|
|
88
|
+
keyboard = (
|
|
89
|
+
InlineKeyboardBuilder()
|
|
90
|
+
.callback("Подтвердить", "confirm")
|
|
91
|
+
.link("Документация", "https://dev.max.ru/docs-api")
|
|
92
|
+
.adjust(1, 1)
|
|
93
|
+
)
|
|
94
|
+
await event.message.answer("Привет из maxapi 0.12.0", keyboard=keyboard)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
async def main() -> None:
|
|
98
|
+
await dp.start_polling(bot)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == "__main__":
|
|
102
|
+
asyncio.run(main())
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## FSM
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
import asyncio
|
|
109
|
+
import os
|
|
110
|
+
|
|
111
|
+
from maxapi import Bot, Dispatcher, MemoryStorage, State, StateFilter, StatesGroup
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Registration(StatesGroup):
|
|
115
|
+
name = State()
|
|
116
|
+
confirm = State()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
120
|
+
dp = Dispatcher(storage=MemoryStorage())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@dp.message_created()
|
|
124
|
+
async def start_form(message, state):
|
|
125
|
+
if message.body.text == "/form":
|
|
126
|
+
await state.set_state(Registration.name)
|
|
127
|
+
await state.update_data(step="name")
|
|
128
|
+
await message.answer("Введите имя")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dp.message_created(StateFilter(Registration.name))
|
|
132
|
+
async def save_name(message, state, state_data):
|
|
133
|
+
await state.update_data(name=message.body.text)
|
|
134
|
+
data = await state.get_data()
|
|
135
|
+
await state.set_state(Registration.confirm)
|
|
136
|
+
await message.answer(f"Подтвердить имя: {data['name']}")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
async def main() -> None:
|
|
140
|
+
await dp.start_polling(bot)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
if __name__ == "__main__":
|
|
144
|
+
asyncio.run(main())
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Structured callback payload
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from maxapi import CallbackPayloadSchema, Dispatcher
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class AdminAction(CallbackPayloadSchema):
|
|
154
|
+
prefix = "admin"
|
|
155
|
+
action: str
|
|
156
|
+
user_id: int
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
payload = AdminAction(action="ban", user_id=42).pack()
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
dp = Dispatcher()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dp.message_callback(AdminAction.filter(action="ban"))
|
|
166
|
+
async def handle_ban(callback_event, callback_payload_text):
|
|
167
|
+
parsed = callback_event.unpack(AdminAction)
|
|
168
|
+
await callback_event.answer(notification=f"Ban for user {parsed.user_id}")
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Plugin API
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from maxapi import BasePlugin, Dispatcher
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class MetricsPlugin(BasePlugin):
|
|
178
|
+
name = "metrics"
|
|
179
|
+
|
|
180
|
+
def setup(self, router) -> None:
|
|
181
|
+
@router.message_created()
|
|
182
|
+
async def mark_message(message, bot):
|
|
183
|
+
del bot
|
|
184
|
+
print(f"message_id={message.message_id}")
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
dp = Dispatcher()
|
|
188
|
+
dp.include_plugin(MetricsPlugin())
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Webhook
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import asyncio
|
|
195
|
+
import os
|
|
196
|
+
|
|
197
|
+
from maxapi import Bot, Dispatcher
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
201
|
+
dp = Dispatcher()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def main() -> None:
|
|
205
|
+
await dp.handle_webhook(
|
|
206
|
+
bot=bot,
|
|
207
|
+
host="0.0.0.0",
|
|
208
|
+
port=8080,
|
|
209
|
+
path="/webhook",
|
|
210
|
+
secret=os.environ["MAX_BOT_WEBHOOK_SECRET"],
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
asyncio.run(main())
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Media helpers
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
response = await bot.send_image(
|
|
222
|
+
"/tmp/banner.png",
|
|
223
|
+
chat_id=123,
|
|
224
|
+
text="Готово",
|
|
225
|
+
processing_wait=0.5,
|
|
226
|
+
attachment_ready_retries=3,
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Migration layer
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from maxapi.compat import Keyboard, LegacyBot, LegacyDispatcher
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
bot = LegacyBot(token="token")
|
|
237
|
+
dp = LegacyDispatcher()
|
|
238
|
+
keyboard = Keyboard().callback("OK", "done").row()
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@dp.message_handler()
|
|
242
|
+
async def legacy_handler(event):
|
|
243
|
+
await bot.send_text(chat_id=event.chat_id, text="legacy", keyboard=keyboard)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## CI/CD
|
|
247
|
+
|
|
248
|
+
В репозитории есть два workflow:
|
|
249
|
+
|
|
250
|
+
- `.github/workflows/tests.yml` — тесты и проверка сборки пакета;
|
|
251
|
+
- `.github/workflows/publish.yml` — сборка артефактов, публикация в PyPI через Trusted Publishing и создание GitHub Release по тегу `v*`.
|
|
252
|
+
|
|
253
|
+
## Репозиторий
|
|
254
|
+
|
|
255
|
+
- GitHub: `https://github.com/Maxi-online/maxapi-sdk`
|
|
256
|
+
|
|
257
|
+
## Структура
|
|
258
|
+
|
|
259
|
+
- `maxapi.bot` — typed Bot API client;
|
|
260
|
+
- `maxapi.dispatcher` — Router/Dispatcher, middleware, handler injection;
|
|
261
|
+
- `maxapi.runners.polling` — long polling runtime;
|
|
262
|
+
- `maxapi.runners.webhook` — FastAPI/uvicorn webhook runtime;
|
|
263
|
+
- `maxapi.filters` — composable filters;
|
|
264
|
+
- `maxapi.builders` — keyboard/media builders;
|
|
265
|
+
- `maxapi.fsm` — FSM, storage и state filters;
|
|
266
|
+
- `maxapi.plugins` — plugin API;
|
|
267
|
+
- `maxapi.middlewares` — middleware base classes;
|
|
268
|
+
- `maxapi.compat` — migration layer;
|
|
269
|
+
- `maxapi.transport` — HTTP transport;
|
|
270
|
+
- `maxapi.types` — pydantic-модели.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
maxapi/__init__.py,sha256=kj9TG45WdfmZW5l3sGmynrN_7kw5V2KbmaqUlT_R6BQ,1028
|
|
2
|
+
maxapi/bot.py,sha256=Sm9eGUSmG5anVlUkCDOAakXV2K-pvb5IvHrsemMyOjU,23654
|
|
3
|
+
maxapi/callback_schema.py,sha256=7nfNJH7JrkoN6jPtc8KsB7bjVbWn1xgyELCkBrqKUfo,4709
|
|
4
|
+
maxapi/dispatcher.py,sha256=0tiuR6-22_ZKemAm2vBRiyrmqXONaAReLdOzoWosvwY,17877
|
|
5
|
+
maxapi/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
6
|
+
maxapi/builders/__init__.py,sha256=I9dSL8YPvgCUB_Pi0K53iqTAzr0Je3NiH9iwytpIjI4,512
|
|
7
|
+
maxapi/builders/keyboards.py,sha256=sG1W31cOXTVOl-inGFdtxZ2nPjPpPUa458rpicRVGXw,5133
|
|
8
|
+
maxapi/builders/media.py,sha256=6Fagwgc6VAlou34DcSUeNt4Qo3vgYjDtJS5olUAfgSE,2650
|
|
9
|
+
maxapi/client/__init__.py,sha256=0kdhnuZ7ZigkigbI4GIqmbBSUa2bfMrMiPYOTynu3x0,92
|
|
10
|
+
maxapi/client/default.py,sha256=wNaOsX8cAQjoCW5b0UEOUCGSsIt07MjiLcTX5Su-Bhc,3039
|
|
11
|
+
maxapi/compat/__init__.py,sha256=1cCwZtFBJ-9p1MyugZFDRk_BoFDzlyG52gSAuabB-z0,396
|
|
12
|
+
maxapi/connection/__init__.py,sha256=y8yZB-53PLM0Ce-95ZcIsjwckbnShqIUovVIo3jkJ3g,63
|
|
13
|
+
maxapi/connection/base.py,sha256=trzzcVvkBGuSgnny1xkZ_2NQtveNl3YZ2bEyKR7w_C4,4824
|
|
14
|
+
maxapi/exceptions/__init__.py,sha256=qjRalsLd0otqbkrRnHjZbqFq067clpjVlYQNv60wHv8,140
|
|
15
|
+
maxapi/exceptions/max.py,sha256=0Dz4zqiCcC-XAnrwpq94Q1--_Oo3Us036WJ91lKdFZE,1073
|
|
16
|
+
maxapi/filters/__init__.py,sha256=3oxGaaRZv8z1HfomgTzbkxEMD9Mj56bPhNz2nCm022g,607
|
|
17
|
+
maxapi/filters/base.py,sha256=WA1Rc5znG5BipwqJP57269KGhrWuGEue3GEhz1AGuYY,2045
|
|
18
|
+
maxapi/filters/command.py,sha256=KBeP2vspahpubcL0hOQqf9_Ag7eGWCuGuMfNPlyGZHE,855
|
|
19
|
+
maxapi/filters/common.py,sha256=IhYkC8E6x2AAi7rB27j8TUKXtEeOfNDJPuT37I1B7ZQ,2529
|
|
20
|
+
maxapi/filters/text.py,sha256=OhbaSzTM_J7gWlr4620j_M7Fcdwh-n50lsuoYWfe3nY,2254
|
|
21
|
+
maxapi/fsm/__init__.py,sha256=Lifi7BhONF8nouRuLEf8RdQuHjZyOR9N7ES7_ZuOUcg,396
|
|
22
|
+
maxapi/fsm/context.py,sha256=RW2Vhih_RyGyJPeE0D7JYBVDCwmKEjExSXnxfgHbMVU,1256
|
|
23
|
+
maxapi/fsm/filters.py,sha256=gF0y9_qYiIImrFENNevOqLQn_BoHjtAXSYePqWc_LWM,889
|
|
24
|
+
maxapi/fsm/middleware.py,sha256=HkmGwDenzh8pgFqLzALXJCpd2qbx0lLsRL55WQwcBT8,1388
|
|
25
|
+
maxapi/fsm/state.py,sha256=N--2mratH9BoIsXkXIg2aI7kpYLo3_hjoi9KiUG2QR8,926
|
|
26
|
+
maxapi/fsm/storage/__init__.py,sha256=PHNmviGtrt_MQO4EnHIrRcH5zskQOH0NlmrR2P5aCBE,134
|
|
27
|
+
maxapi/fsm/storage/base.py,sha256=VMWqiP75gyMrxUraLp4-z8gPo--Eh7JBB0YYpqNxUzM,736
|
|
28
|
+
maxapi/fsm/storage/memory.py,sha256=AIPo4aLolrVWEs3CGa9Pn24bwCqu2gnm-Fx65d3Fhc8,1171
|
|
29
|
+
maxapi/middlewares/__init__.py,sha256=97zpeKmNhs08jdOgFNVZn2Q09i01NVVwojQVfmUQSp8,145
|
|
30
|
+
maxapi/middlewares/base.py,sha256=FDpQw-uMgGM5qyb5D8Mj5zt_vBmm2SzlbXdjQmTwtyg,868
|
|
31
|
+
maxapi/plugins/__init__.py,sha256=ic94Z3NPyhgkhtkbq7VLzsbCvUDCuTvKHWjh-l_V9ho,89
|
|
32
|
+
maxapi/plugins/base.py,sha256=Dnh2_R-Pma9l8yIQhncXLyAUQ4Ad9eLiUsT9rIxep2s,395
|
|
33
|
+
maxapi/runners/__init__.py,sha256=CjJkYKNv5m9lxUjNeScIMBRG62c70ZXx2u9ybL71YPY,116
|
|
34
|
+
maxapi/runners/polling.py,sha256=nOslWAIys_FCxvP35fmHAQKlpCSWxYHYYl4oEgaVoE4,1617
|
|
35
|
+
maxapi/runners/webhook.py,sha256=H7W65QeCWGFKnm_UDN6grGC-5Fbe9tC1qkozPo6XYsM,2221
|
|
36
|
+
maxapi/transport/__init__.py,sha256=JlU34NSR2YJYz9NikjjIgw6GoaK7FTqmNHQKKn_kUJk,374
|
|
37
|
+
maxapi/transport/client.py,sha256=hWa1_G7PT7T2Y2-L75Mm-5Qhi7V0zkKEeyYbDaLaEus,8745
|
|
38
|
+
maxapi/transport/config.py,sha256=NueD8iAvwHOqN-jnZtCkA17lnu5mjq4xWgEP70a3AzA,2632
|
|
39
|
+
maxapi/transport/errors.py,sha256=b9EZzjj7b1d0ARs0eAQdxa4DOWxQOGMXkUXvP_e3AKE,1169
|
|
40
|
+
maxapi/types/__init__.py,sha256=BjTUzPsN0nsk6uMpKqLFD27w7hgy74CINrVOS8ihhr4,1344
|
|
41
|
+
maxapi/types/base.py,sha256=fKvMYVSZlDBbFWyF7sn8fdOXnBUv2nRYJVZ8D5OHlh8,220
|
|
42
|
+
maxapi/types/bot_mixin.py,sha256=ksSA0kvZV-gewo6Zj2LhCjxDqBCQ0tdSpOa_s7spgSk,413
|
|
43
|
+
maxapi/types/models.py,sha256=QEC7Yj5qV1JJhZFQQc4EBgrmeUm3ADy1vESEEgvsCVQ,7832
|
|
44
|
+
maxapi_sdk-0.12.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
45
|
+
maxapi_sdk-0.12.0.dist-info/METADATA,sha256=nzKx3BL2RG0JUxc9q5hWdWmdWYGCQ9iu8UtLB5FyveM,7237
|
|
46
|
+
maxapi_sdk-0.12.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
47
|
+
maxapi_sdk-0.12.0.dist-info/top_level.txt,sha256=zuEF-Sj4okog5Ocl021oNZxUEvFAyUsqYA8WUfEC55A,7
|
|
48
|
+
maxapi_sdk-0.12.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
maxapi
|