maxapi-sdk 0.12.0__tar.gz
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_sdk-0.12.0/LICENSE +21 -0
- maxapi_sdk-0.12.0/PKG-INFO +270 -0
- maxapi_sdk-0.12.0/README.md +233 -0
- maxapi_sdk-0.12.0/maxapi/__init__.py +49 -0
- maxapi_sdk-0.12.0/maxapi/bot.py +674 -0
- maxapi_sdk-0.12.0/maxapi/builders/__init__.py +23 -0
- maxapi_sdk-0.12.0/maxapi/builders/keyboards.py +133 -0
- maxapi_sdk-0.12.0/maxapi/builders/media.py +81 -0
- maxapi_sdk-0.12.0/maxapi/callback_schema.py +140 -0
- maxapi_sdk-0.12.0/maxapi/client/__init__.py +3 -0
- maxapi_sdk-0.12.0/maxapi/client/default.py +89 -0
- maxapi_sdk-0.12.0/maxapi/compat/__init__.py +15 -0
- maxapi_sdk-0.12.0/maxapi/connection/__init__.py +3 -0
- maxapi_sdk-0.12.0/maxapi/connection/base.py +136 -0
- maxapi_sdk-0.12.0/maxapi/dispatcher.py +487 -0
- maxapi_sdk-0.12.0/maxapi/exceptions/__init__.py +3 -0
- maxapi_sdk-0.12.0/maxapi/exceptions/max.py +45 -0
- maxapi_sdk-0.12.0/maxapi/filters/__init__.py +25 -0
- maxapi_sdk-0.12.0/maxapi/filters/base.py +73 -0
- maxapi_sdk-0.12.0/maxapi/filters/command.py +28 -0
- maxapi_sdk-0.12.0/maxapi/filters/common.py +78 -0
- maxapi_sdk-0.12.0/maxapi/filters/text.py +71 -0
- maxapi_sdk-0.12.0/maxapi/fsm/__init__.py +17 -0
- maxapi_sdk-0.12.0/maxapi/fsm/context.py +41 -0
- maxapi_sdk-0.12.0/maxapi/fsm/filters.py +30 -0
- maxapi_sdk-0.12.0/maxapi/fsm/middleware.py +42 -0
- maxapi_sdk-0.12.0/maxapi/fsm/state.py +33 -0
- maxapi_sdk-0.12.0/maxapi/fsm/storage/__init__.py +4 -0
- maxapi_sdk-0.12.0/maxapi/fsm/storage/base.py +30 -0
- maxapi_sdk-0.12.0/maxapi/fsm/storage/memory.py +38 -0
- maxapi_sdk-0.12.0/maxapi/middlewares/__init__.py +3 -0
- maxapi_sdk-0.12.0/maxapi/middlewares/base.py +34 -0
- maxapi_sdk-0.12.0/maxapi/plugins/__init__.py +3 -0
- maxapi_sdk-0.12.0/maxapi/plugins/base.py +19 -0
- maxapi_sdk-0.12.0/maxapi/py.typed +1 -0
- maxapi_sdk-0.12.0/maxapi/runners/__init__.py +4 -0
- maxapi_sdk-0.12.0/maxapi/runners/polling.py +55 -0
- maxapi_sdk-0.12.0/maxapi/runners/webhook.py +72 -0
- maxapi_sdk-0.12.0/maxapi/transport/__init__.py +13 -0
- maxapi_sdk-0.12.0/maxapi/transport/client.py +239 -0
- maxapi_sdk-0.12.0/maxapi/transport/config.py +81 -0
- maxapi_sdk-0.12.0/maxapi/transport/errors.py +45 -0
- maxapi_sdk-0.12.0/maxapi/types/__init__.py +73 -0
- maxapi_sdk-0.12.0/maxapi/types/base.py +9 -0
- maxapi_sdk-0.12.0/maxapi/types/bot_mixin.py +14 -0
- maxapi_sdk-0.12.0/maxapi/types/models.py +308 -0
- maxapi_sdk-0.12.0/maxapi_sdk.egg-info/PKG-INFO +270 -0
- maxapi_sdk-0.12.0/maxapi_sdk.egg-info/SOURCES.txt +56 -0
- maxapi_sdk-0.12.0/maxapi_sdk.egg-info/dependency_links.txt +1 -0
- maxapi_sdk-0.12.0/maxapi_sdk.egg-info/requires.txt +14 -0
- maxapi_sdk-0.12.0/maxapi_sdk.egg-info/top_level.txt +1 -0
- maxapi_sdk-0.12.0/pyproject.toml +61 -0
- maxapi_sdk-0.12.0/setup.cfg +4 -0
- maxapi_sdk-0.12.0/tests/test_bot_endpoints.py +48 -0
- maxapi_sdk-0.12.0/tests/test_dispatcher_and_runners.py +141 -0
- maxapi_sdk-0.12.0/tests/test_iteration_four_features.py +158 -0
- maxapi_sdk-0.12.0/tests/test_iteration_three_features.py +175 -0
- maxapi_sdk-0.12.0/tests/test_transport.py +99 -0
|
@@ -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,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,233 @@
|
|
|
1
|
+
# maxapi-sdk 0.12.0
|
|
2
|
+
|
|
3
|
+
Python SDK для MAX Messenger Bot API.
|
|
4
|
+
|
|
5
|
+
Пакет публикуется в PyPI как `maxapi-sdk`, а в коде импортируется как `maxapi`.
|
|
6
|
+
|
|
7
|
+
## Что есть в пакете
|
|
8
|
+
|
|
9
|
+
- typed Bot API client для методов MAX;
|
|
10
|
+
- отдельные runtime-классы `PollingRunner` и `WebhookRunner`;
|
|
11
|
+
- transport-слой с retry/backoff и поддержкой `Retry-After`;
|
|
12
|
+
- `Router` и `Dispatcher` с middleware и инъекцией зависимостей в handlers;
|
|
13
|
+
- composable filters с операторами `&`, `|`, `~`;
|
|
14
|
+
- `InlineKeyboardBuilder` и media helpers для upload/send flow;
|
|
15
|
+
- FSM: `State`, `StatesGroup`, `FSMContext`, `MemoryStorage`, `StateFilter`;
|
|
16
|
+
- plugin API для модульного подключения функциональности;
|
|
17
|
+
- structured callback payload parsing через `CallbackPayloadSchema`;
|
|
18
|
+
- migration layer для старого стиля кода;
|
|
19
|
+
- GitHub Actions для тестов, сборки, GitHub Releases и публикации в PyPI.
|
|
20
|
+
|
|
21
|
+
## Установка
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install maxapi-sdk
|
|
25
|
+
pip install "maxapi-sdk[webhook]"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Установка для разработки
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install -e .
|
|
32
|
+
pip install -e .[webhook]
|
|
33
|
+
pip install -e .[dev]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Быстрый старт: polling
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
import asyncio
|
|
40
|
+
import os
|
|
41
|
+
|
|
42
|
+
from maxapi import Bot, Command, Dispatcher, InlineKeyboardBuilder
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
46
|
+
dp = Dispatcher()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dp.message_created(Command("start"))
|
|
50
|
+
async def handle_start(event):
|
|
51
|
+
keyboard = (
|
|
52
|
+
InlineKeyboardBuilder()
|
|
53
|
+
.callback("Подтвердить", "confirm")
|
|
54
|
+
.link("Документация", "https://dev.max.ru/docs-api")
|
|
55
|
+
.adjust(1, 1)
|
|
56
|
+
)
|
|
57
|
+
await event.message.answer("Привет из maxapi 0.12.0", keyboard=keyboard)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
async def main() -> None:
|
|
61
|
+
await dp.start_polling(bot)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
asyncio.run(main())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## FSM
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import asyncio
|
|
72
|
+
import os
|
|
73
|
+
|
|
74
|
+
from maxapi import Bot, Dispatcher, MemoryStorage, State, StateFilter, StatesGroup
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Registration(StatesGroup):
|
|
78
|
+
name = State()
|
|
79
|
+
confirm = State()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
83
|
+
dp = Dispatcher(storage=MemoryStorage())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dp.message_created()
|
|
87
|
+
async def start_form(message, state):
|
|
88
|
+
if message.body.text == "/form":
|
|
89
|
+
await state.set_state(Registration.name)
|
|
90
|
+
await state.update_data(step="name")
|
|
91
|
+
await message.answer("Введите имя")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dp.message_created(StateFilter(Registration.name))
|
|
95
|
+
async def save_name(message, state, state_data):
|
|
96
|
+
await state.update_data(name=message.body.text)
|
|
97
|
+
data = await state.get_data()
|
|
98
|
+
await state.set_state(Registration.confirm)
|
|
99
|
+
await message.answer(f"Подтвердить имя: {data['name']}")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def main() -> None:
|
|
103
|
+
await dp.start_polling(bot)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
asyncio.run(main())
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Structured callback payload
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from maxapi import CallbackPayloadSchema, Dispatcher
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AdminAction(CallbackPayloadSchema):
|
|
117
|
+
prefix = "admin"
|
|
118
|
+
action: str
|
|
119
|
+
user_id: int
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
payload = AdminAction(action="ban", user_id=42).pack()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
dp = Dispatcher()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dp.message_callback(AdminAction.filter(action="ban"))
|
|
129
|
+
async def handle_ban(callback_event, callback_payload_text):
|
|
130
|
+
parsed = callback_event.unpack(AdminAction)
|
|
131
|
+
await callback_event.answer(notification=f"Ban for user {parsed.user_id}")
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Plugin API
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from maxapi import BasePlugin, Dispatcher
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class MetricsPlugin(BasePlugin):
|
|
141
|
+
name = "metrics"
|
|
142
|
+
|
|
143
|
+
def setup(self, router) -> None:
|
|
144
|
+
@router.message_created()
|
|
145
|
+
async def mark_message(message, bot):
|
|
146
|
+
del bot
|
|
147
|
+
print(f"message_id={message.message_id}")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
dp = Dispatcher()
|
|
151
|
+
dp.include_plugin(MetricsPlugin())
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Webhook
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
import os
|
|
159
|
+
|
|
160
|
+
from maxapi import Bot, Dispatcher
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
bot = Bot(token=os.environ["MAX_BOT_TOKEN"])
|
|
164
|
+
dp = Dispatcher()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
async def main() -> None:
|
|
168
|
+
await dp.handle_webhook(
|
|
169
|
+
bot=bot,
|
|
170
|
+
host="0.0.0.0",
|
|
171
|
+
port=8080,
|
|
172
|
+
path="/webhook",
|
|
173
|
+
secret=os.environ["MAX_BOT_WEBHOOK_SECRET"],
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
asyncio.run(main())
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Media helpers
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
response = await bot.send_image(
|
|
185
|
+
"/tmp/banner.png",
|
|
186
|
+
chat_id=123,
|
|
187
|
+
text="Готово",
|
|
188
|
+
processing_wait=0.5,
|
|
189
|
+
attachment_ready_retries=3,
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Migration layer
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from maxapi.compat import Keyboard, LegacyBot, LegacyDispatcher
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
bot = LegacyBot(token="token")
|
|
200
|
+
dp = LegacyDispatcher()
|
|
201
|
+
keyboard = Keyboard().callback("OK", "done").row()
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dp.message_handler()
|
|
205
|
+
async def legacy_handler(event):
|
|
206
|
+
await bot.send_text(chat_id=event.chat_id, text="legacy", keyboard=keyboard)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## CI/CD
|
|
210
|
+
|
|
211
|
+
В репозитории есть два workflow:
|
|
212
|
+
|
|
213
|
+
- `.github/workflows/tests.yml` — тесты и проверка сборки пакета;
|
|
214
|
+
- `.github/workflows/publish.yml` — сборка артефактов, публикация в PyPI через Trusted Publishing и создание GitHub Release по тегу `v*`.
|
|
215
|
+
|
|
216
|
+
## Репозиторий
|
|
217
|
+
|
|
218
|
+
- GitHub: `https://github.com/Maxi-online/maxapi-sdk`
|
|
219
|
+
|
|
220
|
+
## Структура
|
|
221
|
+
|
|
222
|
+
- `maxapi.bot` — typed Bot API client;
|
|
223
|
+
- `maxapi.dispatcher` — Router/Dispatcher, middleware, handler injection;
|
|
224
|
+
- `maxapi.runners.polling` — long polling runtime;
|
|
225
|
+
- `maxapi.runners.webhook` — FastAPI/uvicorn webhook runtime;
|
|
226
|
+
- `maxapi.filters` — composable filters;
|
|
227
|
+
- `maxapi.builders` — keyboard/media builders;
|
|
228
|
+
- `maxapi.fsm` — FSM, storage и state filters;
|
|
229
|
+
- `maxapi.plugins` — plugin API;
|
|
230
|
+
- `maxapi.middlewares` — middleware base classes;
|
|
231
|
+
- `maxapi.compat` — migration layer;
|
|
232
|
+
- `maxapi.transport` — HTTP transport;
|
|
233
|
+
- `maxapi.types` — pydantic-модели.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from .bot import Bot
|
|
2
|
+
from .builders import InlineKeyboardBuilder
|
|
3
|
+
from .callback_schema import CallbackPayloadSchema
|
|
4
|
+
from .dispatcher import Dispatcher, Router
|
|
5
|
+
from .filters import (
|
|
6
|
+
CallbackData,
|
|
7
|
+
ChatId,
|
|
8
|
+
ChatType,
|
|
9
|
+
Command,
|
|
10
|
+
HasAttachments,
|
|
11
|
+
Regex,
|
|
12
|
+
StateFilter,
|
|
13
|
+
Text,
|
|
14
|
+
TextContains,
|
|
15
|
+
TextStartsWith,
|
|
16
|
+
UserId,
|
|
17
|
+
)
|
|
18
|
+
from .fsm import FSMContext, FSMMiddleware, MemoryStorage, State, StatesGroup
|
|
19
|
+
from .middlewares import BaseMiddleware, FunctionMiddleware
|
|
20
|
+
from .plugins import BasePlugin
|
|
21
|
+
from .types import UpdateType
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"BaseMiddleware",
|
|
25
|
+
"BasePlugin",
|
|
26
|
+
"Bot",
|
|
27
|
+
"CallbackData",
|
|
28
|
+
"CallbackPayloadSchema",
|
|
29
|
+
"ChatId",
|
|
30
|
+
"ChatType",
|
|
31
|
+
"Command",
|
|
32
|
+
"Dispatcher",
|
|
33
|
+
"FSMContext",
|
|
34
|
+
"FSMMiddleware",
|
|
35
|
+
"FunctionMiddleware",
|
|
36
|
+
"HasAttachments",
|
|
37
|
+
"InlineKeyboardBuilder",
|
|
38
|
+
"MemoryStorage",
|
|
39
|
+
"Regex",
|
|
40
|
+
"Router",
|
|
41
|
+
"State",
|
|
42
|
+
"StateFilter",
|
|
43
|
+
"StatesGroup",
|
|
44
|
+
"Text",
|
|
45
|
+
"TextContains",
|
|
46
|
+
"TextStartsWith",
|
|
47
|
+
"UpdateType",
|
|
48
|
+
"UserId",
|
|
49
|
+
]
|