Compare commits
159 Commits
68748d0899
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e3c17ab60f | |||
| af4c9dc487 | |||
| 1400f72cd0 | |||
| e0569de1c5 | |||
| ff4575fe84 | |||
| e922132319 | |||
| b59d97da8a | |||
| fe68e41b7a | |||
| 50980dfc77 | |||
| 036c92d072 | |||
| cb2226eee5 | |||
| 3855f93862 | |||
| 01e9cdd2fd | |||
| 338d9c4089 | |||
| 3fa34079ae | |||
| 5bc5fb45c8 | |||
| 95313ad3dc | |||
| 42690d24ee | |||
| 6ad3df5829 | |||
| 622d3a3eb3 | |||
| 67de8aae1f | |||
| 1b0b118239 | |||
| 8d62d0d20a | |||
| a7173b4371 | |||
| b25b0c0a5e | |||
| af6b1f1a74 | |||
| b1f8fb91cb | |||
| 9c0567d858 | |||
| 9ab960ebe4 | |||
| a653ed6792 | |||
| 103535d3ba | |||
| 82dc8d255a | |||
| 35b60a1b44 | |||
| e07aeef726 | |||
| 03a60014d4 | |||
| b69713f996 | |||
| 9010c697f4 | |||
| b994717f64 | |||
| 44dcf39f42 | |||
| 0bd59ddb4a | |||
| f497760f5b | |||
| 5c64c3d040 | |||
| c8dd896691 | |||
| e0e7bb53b6 | |||
| adbea8e55f | |||
| 3ff9907a58 | |||
| 6f86d15de4 | |||
| 7ea24fe2af | |||
| 39fb0c5823 | |||
| af84301e4f | |||
| ec432fe8ce | |||
| 1bfd93f2ea | |||
| 7925087ac7 | |||
| 7ed540683c | |||
| 30350c8521 | |||
| 54683256ce | |||
| be7f98976e | |||
| 54c073ab76 | |||
| 29b319768b | |||
| 5e98e540ea | |||
| 1df293f44d | |||
| c667b82a6c | |||
| 62523c1eb2 | |||
| b0b7040206 | |||
| 29d3d7c042 | |||
| fd048e8544 | |||
| 32c0ca7647 | |||
| 02b4e2d39a | |||
| 354c296fed | |||
| 8f93cf36e4 | |||
| e1064761e4 | |||
| 2420e4232e | |||
| fd9986b02e | |||
| 48b480ff9e | |||
| 69e6274f42 | |||
| 0daa9d508d | |||
| 0ae0758bc1 | |||
| 7d2826c4b5 | |||
| 2cd3d64bb8 | |||
| 1fb2fbd654 | |||
| 7534ae60a8 | |||
| 5ad37b8adf | |||
| d731e5e905 | |||
| 7155f974a2 | |||
| cd85a1b7fb | |||
| bdc51a32c5 | |||
| c88b5228b7 | |||
| 611114ec09 | |||
| 52d69904c6 | |||
| 7e9163adc9 | |||
| 6a0406f476 | |||
| 9241917bb1 | |||
| 0354fbc5fd | |||
| f600decf2b | |||
| dd6c8ff9ea | |||
| c844d2b2e6 | |||
| 7336b2ebb9 | |||
| 85e4c086d4 | |||
| 964fba7c32 | |||
| 78976c5393 | |||
| e8b7c71d25 | |||
| aab87e16b2 | |||
| df383665dc | |||
| bd06b33343 | |||
| 7b70d1de18 | |||
| 5f2c908da4 | |||
| 1abbc16cc8 | |||
| 7b61ceaa58 | |||
| 37f7907398 | |||
| 9dab5f97fb | |||
| 0a3d1ca327 | |||
| 93043835d1 | |||
| 3548d0558f | |||
| 5ae4de6816 | |||
| d77288ea07 | |||
| 30cf778504 | |||
| dd1bdb5e37 | |||
| b20a46de24 | |||
| de05e7931a | |||
| a8727c71e9 | |||
| b6c11cd28a | |||
| 12f64f0805 | |||
| 3df4dd21b4 | |||
| b60d8571d3 | |||
| c9a334a615 | |||
| 222ca919fc | |||
| 111b83bf84 | |||
| 98f4f8717b | |||
| 2a351fdbd7 | |||
| 3a3c581914 | |||
| 57d9035afb | |||
| 5f2beb7e07 | |||
| e8ad92c3c3 | |||
| 1be05dea83 | |||
| c1df0c5338 | |||
| b5947e5f47 | |||
| c481e3e931 | |||
| 63af421777 | |||
| 512eb9a4af | |||
| 8aa9c65fcc | |||
| 8f5fc9f398 | |||
| de684aa200 | |||
| 7b8aa3d092 | |||
| d35e15941f | |||
| 294c05ef91 | |||
| bca4c3fd6c | |||
| 2ba1ba23b6 | |||
| 7ea18c3187 | |||
| 390dc94279 | |||
| 9d0eeb9f89 | |||
| ee58238261 | |||
| ab52abc474 | |||
| 688b1502d1 | |||
| 07bd5d090f | |||
| 98ab03670a | |||
| d12d3fc15f | |||
| e08e620b92 | |||
| 1e4d22a2bc | |||
| bfa247e082 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,4 +1,6 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
|
test
|
||||||
|
test.py
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 Твоё Имя
|
Copyright (c) 2025 Denis
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
2
MANIFEST.in
Normal file
2
MANIFEST.in
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
include LICENSE.md
|
||||||
|
include README.md
|
||||||
103
README.md
103
README.md
@@ -1,21 +1,53 @@
|
|||||||
# Асинхронный MAX API
|
<p align="center">
|
||||||
|
<a href="https://github.com/love-apples/maxapi"><img src="https://s.iimg.su/s/29/DCvw4dx2HZgFdTcpqGAs6xdnJnvD44r9zLga2GGe.png" alt="MaxAPI"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
[](https://pypi.org/project/maxapi/)
|
|
||||||
[](https://pypi.org/project/maxapi/)
|
|
||||||
[](https://love-apples/maxapi/blob/main/LICENSE)
|
|
||||||
|
|
||||||
---
|
<p align="center">
|
||||||
|
<a href='https://github.com/love-apples/maxapi/wiki'>Документация</a> •
|
||||||
|
<a href='https://github.com/love-apples/maxapi/tree/main/examples'>Примеры</a> •
|
||||||
|
<a href='https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8'>MAX Чат</a> •
|
||||||
|
<a href='https://t.me/maxapi_github'>TG Чат</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## 📦 Установка
|
<p align="center">
|
||||||
|
<a href='https://pypi.org/project/maxapi/'>
|
||||||
|
<img src='https://img.shields.io/pypi/v/maxapi.svg' alt='PyPI version'>
|
||||||
|
</a>
|
||||||
|
<a href='https://pypi.org/project/maxapi/'>
|
||||||
|
<img src='https://img.shields.io/pypi/pyversions/maxapi.svg' alt='Python Version'>
|
||||||
|
</a>
|
||||||
|
<a href='https://love-apples/maxapi/blob/main/LICENSE'>
|
||||||
|
<img src='https://img.shields.io/github/license/love-apples/maxapi.svg' alt='License'>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## 📦 Установка из PyPi
|
||||||
|
|
||||||
|
Стабильная версия
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install maxapi
|
pip install maxapi
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 🐱👤 Установка из GitHub
|
||||||
|
|
||||||
|
Свежая версия, возможны баги. Рекомендуется только для ознакомления с новыми коммитами.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install git+https://github.com/love-apples/maxapi.git
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
## 🚀 Быстрый старт
|
||||||
|
|
||||||
|
Если вы тестируете бота в чате - не забудьте дать ему права администратора!
|
||||||
|
|
||||||
|
### Запуск Polling
|
||||||
|
|
||||||
|
Если у бота установлены подписки на Webhook - события не будут приходить при методе `start_polling`. При таком случае удалите подписки на Webhook через [MasterBot](https://web.max.ru/masterbot) или через `await bot.delete_webhook()` перед `start_polling`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
@@ -25,10 +57,10 @@ from maxapi.types import BotStarted, Command, MessageCreated
|
|||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot('f9LHodD0cOL5NY7All_9xJRh5ZhPw6bRvq_0Adm8-1bZZEHdRy6_ZHDMNVPejUYNZg7Zhty-wKHNv2X2WJBQ')
|
bot = Bot('тут_ваш_токен')
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
# Ответ бота при нажатии на кнопку "Начать"
|
||||||
@dp.bot_started()
|
@dp.bot_started()
|
||||||
async def bot_started(event: BotStarted):
|
async def bot_started(event: BotStarted):
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
@@ -36,7 +68,7 @@ async def bot_started(event: BotStarted):
|
|||||||
text='Привет! Отправь мне /start'
|
text='Привет! Отправь мне /start'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Ответ бота на команду /start
|
||||||
@dp.message_created(Command('start'))
|
@dp.message_created(Command('start'))
|
||||||
async def hello(event: MessageCreated):
|
async def hello(event: MessageCreated):
|
||||||
await event.message.answer(f"Пример чат-бота для MAX 💙")
|
await event.message.answer(f"Пример чат-бота для MAX 💙")
|
||||||
@@ -50,35 +82,42 @@ if __name__ == '__main__':
|
|||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Запуск Webhook
|
||||||
|
|
||||||
## 📚 Документация
|
Перед запуском бота через Webhook, вам нужно установить дополнительные зависимости (fastapi, uvicorn). Можно это сделать через команду:
|
||||||
|
```bash
|
||||||
|
pip install maxapi[webhook]
|
||||||
|
```
|
||||||
|
|
||||||
В разработке...
|
Указан пример простого запуска, для более низкого уровня можете рассмотреть [этот пример](https://github.com/love-apples/maxapi/blob/main/examples/webhook/low_level.py).
|
||||||
|
```python
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
---
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.types import BotStarted, Command, MessageCreated
|
||||||
|
|
||||||
## 🧩 Возможности
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
- ✅ Роутеры
|
bot = Bot('тут_ваш_токен')
|
||||||
- ✅ Билдер инлайн клавиатур
|
dp = Dispatcher()
|
||||||
- ✅ Простая загрузка медиафайлов
|
|
||||||
- ✅ MagicFilter
|
|
||||||
- ✅ Внутренние функции моделей
|
|
||||||
- ✅ Контекстный менеджер
|
|
||||||
- ✅ Поллинг
|
|
||||||
- ✅ Вебхук
|
|
||||||
- ✅ Логгирование
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 💬 Обратная связь и поддержка
|
# Команда /start боту
|
||||||
|
@dp.message_created(Command('start'))
|
||||||
|
async def hello(event: MessageCreated):
|
||||||
|
await event.message.answer(f"Привет из вебхука!")
|
||||||
|
|
||||||
- MAX: [Чат](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8)
|
|
||||||
- Telegram: [@loveappless](https://t.me/loveappless)
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 Лицензия
|
async def main():
|
||||||
|
await dp.handle_webhook(
|
||||||
|
bot=bot,
|
||||||
|
host='localhost',
|
||||||
|
port=8080,
|
||||||
|
log_level='critical' # Можно убрать для подробного логгирования
|
||||||
|
)
|
||||||
|
|
||||||
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для подробностей.
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from maxapi import Bot, Dispatcher
|
|
||||||
from maxapi.types import BotStarted, Command, MessageCreated
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
bot = Bot('тут_ваш_токен')
|
|
||||||
dp = Dispatcher()
|
|
||||||
|
|
||||||
|
|
||||||
@dp.bot_started()
|
|
||||||
async def bot_started(event: BotStarted):
|
|
||||||
await event.bot.send_message(
|
|
||||||
chat_id=event.chat_id,
|
|
||||||
text='Привет! Отправь мне /start'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message_created(Command('start'))
|
|
||||||
async def hello(event: MessageCreated):
|
|
||||||
await event.message.answer(f"Пример чат-бота для MAX 💙")
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
await dp.start_polling(bot)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
asyncio.run(main())
|
|
||||||
13
examples/README.md
Normal file
13
examples/README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
## ⭐️ Примеры
|
||||||
|
|
||||||
|
- [Эхо бот](https://github.com/love-apples/maxapi/blob/main/examples/echo/main.py)
|
||||||
|
- [Обработчик доступных событий](https://github.com/love-apples/maxapi/blob/main/examples/events/main.py)
|
||||||
|
- [Обработчики с MagicFilter](https://github.com/love-apples/maxapi/blob/main/examples/magic_filters/main.py)
|
||||||
|
- [Демонстрация роутинга, InputMedia и механика контекста](https://github.com/love-apples/maxapi/tree/main/examples/router_with_input_media) (audio.mp3 для команды /media)
|
||||||
|
- [Получение ID](https://github.com/love-apples/maxapi/tree/main/examples/get_ids/main.py)
|
||||||
|
- [Миддлварь в хендлерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_in_handlers/main.py)
|
||||||
|
- [Вебхуки](https://github.com/love-apples/maxapi/tree/main/examples/webhook)
|
||||||
|
- [Клавиатуры](https://github.com/love-apples/maxapi/tree/main/examples/keyboard/main.py)
|
||||||
|
- [Миддлварь в роутерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_for_router/main.py)
|
||||||
|
- [Свой фильтр на BaseFilter](https://github.com/love-apples/maxapi/tree/main/examples/base_filter/main.py)
|
||||||
|
- [Фильтр callback payload](https://github.com/love-apples/maxapi/tree/main/examples/callback_payload/main.py)
|
||||||
38
examples/base_filter/main.py
Normal file
38
examples/base_filter/main.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.types import MessageCreated, CommandStart, UpdateUnion
|
||||||
|
from maxapi.filters import BaseFilter
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot(token='тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
class FilterChat(BaseFilter):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Фильтр, который срабатывает только в чате с названием `Test`
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def __call__(self, event: UpdateUnion):
|
||||||
|
|
||||||
|
if not event.chat:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return event.chat == 'Test'
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(CommandStart(), FilterChat())
|
||||||
|
async def custom_data(event: MessageCreated):
|
||||||
|
await event.message.answer('Привет!')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
61
examples/callback_payload/main.py
Normal file
61
examples/callback_payload/main.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher, F
|
||||||
|
from maxapi.filters.callback_payload import CallbackPayload
|
||||||
|
from maxapi.filters.command import CommandStart
|
||||||
|
from maxapi.types import (
|
||||||
|
CallbackButton,
|
||||||
|
MessageCreated,
|
||||||
|
MessageCallback,
|
||||||
|
)
|
||||||
|
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
class MyPayload(CallbackPayload, prefix='mypayload'):
|
||||||
|
foo: str
|
||||||
|
action: str
|
||||||
|
|
||||||
|
|
||||||
|
class AnotherPayload(CallbackPayload, prefix='another'):
|
||||||
|
bar: str
|
||||||
|
value: int
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(CommandStart())
|
||||||
|
async def show_keyboard(event: MessageCreated):
|
||||||
|
kb = InlineKeyboardBuilder()
|
||||||
|
kb.row(
|
||||||
|
CallbackButton(
|
||||||
|
text='Первая кнопка',
|
||||||
|
payload=MyPayload(foo='123', action='edit').pack(),
|
||||||
|
),
|
||||||
|
CallbackButton(
|
||||||
|
text='Вторая кнопка',
|
||||||
|
payload=AnotherPayload(bar='abc', value=42).pack(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
await event.message.answer('Нажми кнопку!', attachments=[kb.as_markup()])
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_callback(MyPayload.filter(F.foo == '123'))
|
||||||
|
async def on_first_callback(event: MessageCallback, payload: MyPayload):
|
||||||
|
await event.answer(new_text=f'Первая кнопка: foo={payload.foo}, action={payload.action}')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_callback(AnotherPayload.filter())
|
||||||
|
async def on_second_callback(event: MessageCallback, payload: AnotherPayload):
|
||||||
|
await event.answer(new_text=f'Вторая кнопка: bar={payload.bar}, value={payload.value}')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
24
examples/echo/main.py
Normal file
24
examples/echo/main.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.filters import F
|
||||||
|
from maxapi.types import MessageCreated
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text)
|
||||||
|
async def echo(event: MessageCreated):
|
||||||
|
await event.message.answer(f"Повторяю за вами: {event.message.body.text}")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
180
examples/events/main.py
Normal file
180
examples/events/main.py
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.types import (
|
||||||
|
BotStarted,
|
||||||
|
Command,
|
||||||
|
MessageCreated,
|
||||||
|
CallbackButton,
|
||||||
|
MessageCallback,
|
||||||
|
BotAdded,
|
||||||
|
ChatTitleChanged,
|
||||||
|
MessageEdited,
|
||||||
|
MessageRemoved,
|
||||||
|
UserAdded,
|
||||||
|
UserRemoved,
|
||||||
|
BotStopped,
|
||||||
|
DialogCleared,
|
||||||
|
DialogMuted,
|
||||||
|
DialogUnmuted,
|
||||||
|
ChatButton,
|
||||||
|
MessageChatCreated
|
||||||
|
)
|
||||||
|
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('start'))
|
||||||
|
async def hello(event: MessageCreated):
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
CallbackButton(
|
||||||
|
text='Кнопка 1',
|
||||||
|
payload='btn_1'
|
||||||
|
),
|
||||||
|
CallbackButton(
|
||||||
|
text='Кнопка 2',
|
||||||
|
payload='btn_2',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.add(
|
||||||
|
ChatButton(
|
||||||
|
text='Создать чат',
|
||||||
|
chat_title='Тест чат'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await event.message.answer(
|
||||||
|
text='Привет!',
|
||||||
|
attachments=[
|
||||||
|
builder.as_markup(),
|
||||||
|
] # Для MAX клавиатура это вложение,
|
||||||
|
) # поэтому она в attachments
|
||||||
|
|
||||||
|
|
||||||
|
@dp.bot_added()
|
||||||
|
async def bot_added(event: BotAdded):
|
||||||
|
|
||||||
|
if not event.chat:
|
||||||
|
logging.info('Не удалось получить chat, возможно отключен auto_requests!')
|
||||||
|
return
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat.id,
|
||||||
|
text=f'Привет чат {event.chat.title}!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_removed()
|
||||||
|
async def message_removed(event: MessageRemoved):
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text='Я всё видел!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.bot_started()
|
||||||
|
async def bot_started(event: BotStarted):
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text='Привет! Отправь мне /start'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.chat_title_changed()
|
||||||
|
async def chat_title_changed(event: ChatTitleChanged):
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text=f'Крутое новое название "{event.title}"!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_callback()
|
||||||
|
async def message_callback(event: MessageCallback):
|
||||||
|
await event.answer(
|
||||||
|
new_text=f'Вы нажали на кнопку {event.callback.payload}!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_edited()
|
||||||
|
async def message_edited(event: MessageEdited):
|
||||||
|
await event.message.answer(
|
||||||
|
text='Вы отредактировали сообщение!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.user_removed()
|
||||||
|
async def user_removed(event: UserRemoved):
|
||||||
|
|
||||||
|
if not event.from_user:
|
||||||
|
return await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text=f'Неизвестный кикнул {event.user.first_name} 😢'
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.user_added()
|
||||||
|
async def user_added(event: UserAdded):
|
||||||
|
|
||||||
|
if not event.chat:
|
||||||
|
return await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text=f'Чат приветствует вас, {event.user.first_name}!'
|
||||||
|
)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat_id,
|
||||||
|
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.bot_stopped()
|
||||||
|
async def bot_stopped(event: BotStopped):
|
||||||
|
logging.info(event.from_user.full_name, 'остановил бота') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dp.dialog_cleared()
|
||||||
|
async def dialog_cleared(event: DialogCleared):
|
||||||
|
logging.info(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dp.dialog_muted()
|
||||||
|
async def dialog_muted(event: DialogMuted):
|
||||||
|
logging.info(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dp.dialog_unmuted()
|
||||||
|
async def dialog_unmuted(event: DialogUnmuted):
|
||||||
|
logging.info(event.from_user.full_name, 'включил оповещения от чата бота') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dp.dialog_unmuted()
|
||||||
|
async def dialog_removed(event: DialogUnmuted):
|
||||||
|
logging.info(event.from_user.full_name, 'удалил диалог с ботом') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_chat_created()
|
||||||
|
async def message_chat_created(event: MessageChatCreated):
|
||||||
|
await bot.send_message(
|
||||||
|
chat_id=event.chat.chat_id,
|
||||||
|
text=f'Чат создан! Ссылка: {event.chat.link}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
39
examples/get_ids/main.py
Normal file
39
examples/get_ids/main.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher, F
|
||||||
|
from maxapi.enums.parse_mode import ParseMode
|
||||||
|
from maxapi.types import MessageCreated
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.link.type == 'forward')
|
||||||
|
async def get_ids_from_forward(event: MessageCreated):
|
||||||
|
text = (
|
||||||
|
'Информация о пересланном сообщении:\n\n'
|
||||||
|
|
||||||
|
f'Из чата: <b>{event.message.link.chat_id}</b>\n'
|
||||||
|
f'От пользователя: <b>{event.message.link.sender.user_id}</b>'
|
||||||
|
)
|
||||||
|
await event.message.reply(text)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created()
|
||||||
|
async def get_ids(event: MessageCreated):
|
||||||
|
text = (
|
||||||
|
f'Ваш ID: <b>{event.from_user.user_id}</b>\n'
|
||||||
|
f'ID этого чата: <b>{event.chat.chat_id}</b>'
|
||||||
|
)
|
||||||
|
await event.message.answer(text, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
153
examples/keyboard/main.py
Normal file
153
examples/keyboard/main.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
|
||||||
|
# Кнопки
|
||||||
|
from maxapi.types import (
|
||||||
|
ChatButton,
|
||||||
|
LinkButton,
|
||||||
|
CallbackButton,
|
||||||
|
RequestGeoLocationButton,
|
||||||
|
MessageButton,
|
||||||
|
ButtonsPayload, # Для постройки клавиатуры без InlineKeyboardBuilder
|
||||||
|
RequestContactButton,
|
||||||
|
OpenAppButton,
|
||||||
|
)
|
||||||
|
|
||||||
|
from maxapi.types import (
|
||||||
|
MessageCreated,
|
||||||
|
MessageCallback,
|
||||||
|
MessageChatCreated,
|
||||||
|
CommandStart,
|
||||||
|
Command
|
||||||
|
)
|
||||||
|
|
||||||
|
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(CommandStart())
|
||||||
|
async def echo(event: MessageCreated):
|
||||||
|
await event.message.answer(
|
||||||
|
(
|
||||||
|
'Привет! Мои команды:\n\n'
|
||||||
|
|
||||||
|
'/builder - Клавиатура из InlineKeyboardBuilder\n'
|
||||||
|
'/pyaload - Клавиатура из pydantic моделей\n'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('builder'))
|
||||||
|
async def builder(event: MessageCreated):
|
||||||
|
builder = InlineKeyboardBuilder()
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
ChatButton(
|
||||||
|
text="Создать чат",
|
||||||
|
chat_title='Test',
|
||||||
|
chat_description='Test desc'
|
||||||
|
),
|
||||||
|
LinkButton(
|
||||||
|
text="Документация MAX",
|
||||||
|
url="https://dev.max.ru/docs"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
RequestGeoLocationButton(text="Геолокация"),
|
||||||
|
MessageButton(text="Сообщение"),
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
RequestContactButton(text="Контакт"),
|
||||||
|
OpenAppButton(
|
||||||
|
text="Приложение",
|
||||||
|
web_app=event.bot.me.username,
|
||||||
|
contact_id=event.bot.me.user_id
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
builder.row(
|
||||||
|
CallbackButton(
|
||||||
|
text='Callback',
|
||||||
|
payload='test',
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await event.message.answer(
|
||||||
|
text='Клавиатура из InlineKeyboardBuilder',
|
||||||
|
attachments=[
|
||||||
|
builder.as_markup()
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('payload'))
|
||||||
|
async def payload(event: MessageCreated):
|
||||||
|
buttons = [
|
||||||
|
[
|
||||||
|
# кнопку типа "chat" убрали из документации,
|
||||||
|
# возможны баги
|
||||||
|
ChatButton(
|
||||||
|
text="Создать чат",
|
||||||
|
chat_title='Test',
|
||||||
|
chat_description='Test desc'
|
||||||
|
),
|
||||||
|
LinkButton(
|
||||||
|
text="Документация MAX",
|
||||||
|
url="https://dev.max.ru/docs"
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
RequestGeoLocationButton(text="Геолокация"),
|
||||||
|
MessageButton(text="Сообщение"),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
RequestContactButton(text="Контакт"),
|
||||||
|
OpenAppButton(
|
||||||
|
text="Приложение",
|
||||||
|
web_app=event.bot.me.username,
|
||||||
|
contact_id=event.bot.me.user_id
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
CallbackButton(
|
||||||
|
text='Callback',
|
||||||
|
payload='test',
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
buttons_payload = ButtonsPayload(buttons=buttons).pack()
|
||||||
|
|
||||||
|
await event.message.answer(
|
||||||
|
text='Клавиатура из pydantic моделей',
|
||||||
|
attachments=[
|
||||||
|
buttons_payload
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_chat_created()
|
||||||
|
async def message_chat_created(obj: MessageChatCreated):
|
||||||
|
await obj.bot.send_message(
|
||||||
|
chat_id=obj.chat.chat_id,
|
||||||
|
text=f'Чат создан! Ссылка: {obj.chat.link}'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_callback()
|
||||||
|
async def message_callback(callback: MessageCallback):
|
||||||
|
await callback.message.answer('Вы нажали на Callback!')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
48
examples/magic_filters/main.py
Normal file
48
examples/magic_filters/main.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher, F
|
||||||
|
from maxapi.types import MessageCreated
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text == 'привет')
|
||||||
|
async def on_hello(event: MessageCreated):
|
||||||
|
await event.message.answer('Привет!')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text.lower().contains('помощь'))
|
||||||
|
async def on_help(event: MessageCreated):
|
||||||
|
await event.message.answer('Чем могу помочь?')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text.regexp(r'^\d{4}$'))
|
||||||
|
async def on_code(event: MessageCreated):
|
||||||
|
await event.message.answer('Принят 4-значный код')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.attachments)
|
||||||
|
async def on_attachment(event: MessageCreated):
|
||||||
|
await event.message.answer('Получено вложение')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text.len() > 20)
|
||||||
|
async def on_long_text(event: MessageCreated):
|
||||||
|
await event.message.answer('Слишком длинное сообщение')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(F.message.body.text.len() > 0)
|
||||||
|
async def on_non_empty(event: MessageCreated):
|
||||||
|
await event.message.answer('Вы что-то написали.')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
41
examples/middleware_for_router/main.py
Normal file
41
examples/middleware_for_router/main.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.types import MessageCreated, Command, UpdateUnion
|
||||||
|
from maxapi.filters.middleware import BaseMiddleware
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot(token='тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDataForRouterMiddleware(BaseMiddleware):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event_object: UpdateUnion,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
|
data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'
|
||||||
|
result = await handler(event_object, data)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('custom_data'))
|
||||||
|
async def custom_data(event: MessageCreated, custom_data: str):
|
||||||
|
await event.message.answer(custom_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
dp.middleware(CustomDataForRouterMiddleware())
|
||||||
|
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
62
examples/middleware_in_handlers/main.py
Normal file
62
examples/middleware_in_handlers/main.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.filters.middleware import BaseMiddleware
|
||||||
|
from maxapi.types import MessageCreated, Command, UpdateUnion
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot(token='тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
class CheckChatTitleMiddleware(BaseMiddleware):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event_object: UpdateUnion,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
|
if event_object.chat.title == 'MAXApi':
|
||||||
|
return await handler(event_object, data)
|
||||||
|
|
||||||
|
|
||||||
|
class CustomDataMiddleware(BaseMiddleware):
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
|
event_object: UpdateUnion,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
|
data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'
|
||||||
|
|
||||||
|
await handler(event_object, data)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('start'), CheckChatTitleMiddleware())
|
||||||
|
async def start(event: MessageCreated):
|
||||||
|
await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('custom_data'), CustomDataMiddleware())
|
||||||
|
async def custom_data(event: MessageCreated, custom_data: str):
|
||||||
|
await event.message.answer(custom_data)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created(Command('many_middlewares'), CheckChatTitleMiddleware(), CustomDataMiddleware())
|
||||||
|
async def many_middlewares(event: MessageCreated, custom_data: str):
|
||||||
|
await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
|
||||||
|
await event.message.answer(custom_data)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
@@ -6,7 +6,7 @@ from maxapi.context import MemoryContext, State, StatesGroup
|
|||||||
from maxapi.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
|
from maxapi.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
|
||||||
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from example.router_for_example import router
|
from router import router
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
@@ -158,4 +158,5 @@ async def main():
|
|||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
asyncio.run(main())
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
4
examples/webhook/README.md
Normal file
4
examples/webhook/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## Вебхуки
|
||||||
|
|
||||||
|
- [Высокоуровневый](https://github.com/love-apples/maxapi/tree/main/examples/webhook/high_level.py)
|
||||||
|
- [Низкоуровневый](https://github.com/love-apples/maxapi/tree/main/examples/webhook/low_level.py)
|
||||||
28
examples/webhook/high_level.py
Normal file
28
examples/webhook/high_level.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.types import MessageCreated
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created()
|
||||||
|
async def handle_message(event: MessageCreated):
|
||||||
|
await event.message.answer('Бот работает через вебхук!')
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
await dp.handle_webhook(
|
||||||
|
bot=bot,
|
||||||
|
host='localhost',
|
||||||
|
port=8080,
|
||||||
|
log_level='critical' # Можно убрать для подробного логгирования
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
63
examples/webhook/low_level.py
Normal file
63
examples/webhook/low_level.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
'\n\t Не установлен fastapi!'
|
||||||
|
'\n\t Выполните команду для установки fastapi: '
|
||||||
|
'\n\t pip install fastapi>=0.68.0'
|
||||||
|
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||||
|
'\n\t pip install maxapi[webhook]'
|
||||||
|
)
|
||||||
|
|
||||||
|
from maxapi import Bot, Dispatcher
|
||||||
|
from maxapi.methods.types.getted_updates import process_update_webhook
|
||||||
|
from maxapi.types import MessageCreated
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_created()
|
||||||
|
async def handle_message(event: MessageCreated):
|
||||||
|
await event.message.answer('Бот работает через вебхук!')
|
||||||
|
|
||||||
|
# Регистрация обработчика
|
||||||
|
# для вебхука
|
||||||
|
@dp.webhook_post('/')
|
||||||
|
async def _(request: Request):
|
||||||
|
|
||||||
|
# Сериализация полученного запроса
|
||||||
|
event_json = await request.json()
|
||||||
|
|
||||||
|
# Десериализация полученного запроса
|
||||||
|
# в pydantic
|
||||||
|
event_object = await process_update_webhook(
|
||||||
|
event_json=event_json,
|
||||||
|
bot=bot
|
||||||
|
)
|
||||||
|
|
||||||
|
# ...свой код
|
||||||
|
print(f'Информация из вебхука: {event_json}')
|
||||||
|
# ...свой код
|
||||||
|
|
||||||
|
# Окончательная обработка запроса
|
||||||
|
await dp.handle(event_object)
|
||||||
|
|
||||||
|
# Ответ вебхука
|
||||||
|
return JSONResponse(content={'ok': True}, status_code=200)
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
|
||||||
|
# Запуск сервера
|
||||||
|
await dp.init_serve(bot, log_level='critical')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
@@ -3,8 +3,8 @@ from .dispatcher import Dispatcher, Router
|
|||||||
from .filters import F
|
from .filters import F
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
Bot,
|
'Bot',
|
||||||
Dispatcher,
|
'Dispatcher',
|
||||||
F,
|
'F',
|
||||||
Router
|
'Router'
|
||||||
]
|
]
|
||||||
878
maxapi/bot.py
878
maxapi/bot.py
File diff suppressed because it is too large
Load Diff
27
maxapi/client/default.py
Normal file
27
maxapi/client/default.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from aiohttp import ClientTimeout
|
||||||
|
|
||||||
|
class DefaultConnectionProperties:
|
||||||
|
'''
|
||||||
|
Класс для хранения параметров соединения по умолчанию для aiohttp-клиента.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout (int): Таймаут всего соединения в секундах (по умолчанию 5 * 30).
|
||||||
|
sock_connect (int): Таймаут установки TCP-соединения в секундах (по умолчанию 30).
|
||||||
|
**kwargs: Дополнительные параметры, которые будут сохранены как есть.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
timeout (ClientTimeout): Экземпляр aiohttp.ClientTimeout с заданными параметрами.
|
||||||
|
kwargs (dict): Дополнительные параметры.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, timeout: int = 5 * 30, sock_connect: int = 30, **kwargs):
|
||||||
|
'''
|
||||||
|
Инициализация параметров соединения.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout (int): Таймаут всего соединения в секундах.
|
||||||
|
sock_connect (int): Таймаут установки TCP-соединения в секундах.
|
||||||
|
**kwargs: Дополнительные параметры.
|
||||||
|
'''
|
||||||
|
self.timeout = ClientTimeout(total=timeout, sock_connect=sock_connect)
|
||||||
|
self.kwargs = kwargs
|
||||||
@@ -1,14 +1,25 @@
|
|||||||
import os
|
from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
import os
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
|
import aiofiles
|
||||||
|
import puremagic
|
||||||
|
|
||||||
import aiohttp
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from aiohttp import ClientSession, ClientConnectionError, FormData
|
||||||
|
|
||||||
|
from ..exceptions.invalid_token import InvalidToken
|
||||||
|
from ..exceptions.max import MaxConnection
|
||||||
|
|
||||||
from ..types.errors import Error
|
from ..types.errors import Error
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
from ..enums.upload_type import UploadType
|
from ..enums.upload_type import UploadType
|
||||||
from ..loggers import logger_bot, logger_connection
|
|
||||||
|
from ..loggers import logger_bot
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
@@ -16,23 +27,69 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class BaseConnection:
|
class BaseConnection:
|
||||||
|
|
||||||
API_URL = 'https://botapi.max.ru'
|
"""
|
||||||
|
Базовый класс для всех методов API.
|
||||||
|
|
||||||
def __init__(self):
|
Содержит общую логику выполнения запроса (сериализация, отправка HTTP-запроса, обработка ответа).
|
||||||
self.bot: 'Bot' = None
|
"""
|
||||||
self.session: aiohttp.ClientSession = None
|
|
||||||
|
API_URL = 'https://botapi.max.ru'
|
||||||
|
RETRY_DELAY = 2
|
||||||
|
ATTEMPTS_COUNT = 5
|
||||||
|
AFTER_MEDIA_INPUT_DELAY = 2.0
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация BaseConnection.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
bot (Optional[Bot]): Экземпляр бота.
|
||||||
|
session (Optional[ClientSession]): aiohttp-сессия.
|
||||||
|
after_input_media_delay (float): Задержка после ввода медиа.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.bot: Optional[Bot] = None
|
||||||
|
self.session: Optional[ClientSession] = None
|
||||||
|
self.after_input_media_delay: float = self.AFTER_MEDIA_INPUT_DELAY
|
||||||
|
|
||||||
async def request(
|
async def request(
|
||||||
self,
|
self,
|
||||||
method: HTTPMethod,
|
method: HTTPMethod,
|
||||||
path: ApiPath,
|
path: ApiPath | str,
|
||||||
model: BaseModel = None,
|
model: BaseModel | Any = None,
|
||||||
is_return_raw: bool = False,
|
is_return_raw: bool = False,
|
||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет HTTP-запрос к API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (HTTPMethod): HTTP-метод (GET, POST и т.д.).
|
||||||
|
path (ApiPath | str): Путь до конечной точки.
|
||||||
|
model (BaseModel | Any, optional): Pydantic-модель для десериализации ответа, если is_return_raw=False.
|
||||||
|
is_return_raw (bool, optional): Если True — вернуть сырой ответ, иначе — результат десериализации.
|
||||||
|
**kwargs: Дополнительные параметры (query, headers, json).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
model | dict | Error: Объект модели, dict или ошибка.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: Если бот не инициализирован.
|
||||||
|
MaxConnection: Ошибка соединения.
|
||||||
|
InvalidToken: Ошибка авторизации (401).
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
if not self.bot.session:
|
if not self.bot.session:
|
||||||
self.bot.session = aiohttp.ClientSession(self.bot.API_URL)
|
self.bot.session = ClientSession(
|
||||||
|
base_url=self.bot.API_URL,
|
||||||
|
timeout=self.bot.default_connection.timeout,
|
||||||
|
**self.bot.default_connection.kwargs
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = await self.bot.session.request(
|
r = await self.bot.session.request(
|
||||||
@@ -40,8 +97,12 @@ class BaseConnection:
|
|||||||
url=path.value if isinstance(path, ApiPath) else path,
|
url=path.value if isinstance(path, ApiPath) else path,
|
||||||
**kwargs
|
**kwargs
|
||||||
)
|
)
|
||||||
except aiohttp.ClientConnectorDNSError as e:
|
except ClientConnectionError as e:
|
||||||
return logger_connection.error(f'Ошибка при отправке запроса: {e}')
|
raise MaxConnection(f'Ошибка при отправке запроса: {e}')
|
||||||
|
|
||||||
|
if r.status == 401:
|
||||||
|
await self.bot.session.close()
|
||||||
|
raise InvalidToken('Неверный токен!')
|
||||||
|
|
||||||
if not r.ok:
|
if not r.ok:
|
||||||
raw = await r.json()
|
raw = await r.json()
|
||||||
@@ -51,9 +112,10 @@ class BaseConnection:
|
|||||||
|
|
||||||
raw = await r.json()
|
raw = await r.json()
|
||||||
|
|
||||||
if is_return_raw: return raw
|
if is_return_raw:
|
||||||
|
return raw
|
||||||
|
|
||||||
model = model(**raw)
|
model = model(**raw) # type: ignore
|
||||||
|
|
||||||
if hasattr(model, 'message'):
|
if hasattr(model, 'message'):
|
||||||
attr = getattr(model, 'message')
|
attr = getattr(model, 'message')
|
||||||
@@ -71,13 +133,26 @@ class BaseConnection:
|
|||||||
path: str,
|
path: str,
|
||||||
type: UploadType
|
type: UploadType
|
||||||
):
|
):
|
||||||
with open(path, 'rb') as f:
|
|
||||||
file_data = f.read()
|
"""
|
||||||
|
Загружает файл на сервер.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): URL загрузки.
|
||||||
|
path (str): Путь к файлу.
|
||||||
|
type (UploadType): Тип файла.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Сырой .text() ответ от сервера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with aiofiles.open(path, 'rb') as f:
|
||||||
|
file_data = await f.read()
|
||||||
|
|
||||||
basename = os.path.basename(path)
|
basename = os.path.basename(path)
|
||||||
name, ext = os.path.splitext(basename)
|
_, ext = os.path.splitext(basename)
|
||||||
|
|
||||||
form = aiohttp.FormData()
|
form = FormData()
|
||||||
form.add_field(
|
form.add_field(
|
||||||
name='data',
|
name='data',
|
||||||
value=file_data,
|
value=file_data,
|
||||||
@@ -85,10 +160,60 @@ class BaseConnection:
|
|||||||
content_type=f"{type.value}/{ext.lstrip('.')}"
|
content_type=f"{type.value}/{ext.lstrip('.')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with ClientSession() as session:
|
||||||
response = await session.post(
|
response = await session.post(
|
||||||
url=url,
|
url=url,
|
||||||
data=form
|
data=form
|
||||||
)
|
)
|
||||||
|
|
||||||
return await response.text()
|
return await response.text()
|
||||||
|
|
||||||
|
async def upload_file_buffer(
|
||||||
|
self,
|
||||||
|
filename: str,
|
||||||
|
url: str,
|
||||||
|
buffer: bytes,
|
||||||
|
type: UploadType
|
||||||
|
):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Загружает файл из буфера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename (str): Имя файла.
|
||||||
|
url (str): URL загрузки.
|
||||||
|
buffer (bytes): Буфер данных.
|
||||||
|
type (UploadType): Тип файла.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Сырой .text() ответ от сервера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
matches = puremagic.magic_string(buffer[:4096])
|
||||||
|
if matches:
|
||||||
|
mime_type = matches[0][1]
|
||||||
|
ext = mimetypes.guess_extension(mime_type) or ''
|
||||||
|
else:
|
||||||
|
mime_type = f"{type.value}/*"
|
||||||
|
ext = ''
|
||||||
|
except Exception:
|
||||||
|
mime_type = f"{type.value}/*"
|
||||||
|
ext = ''
|
||||||
|
|
||||||
|
basename = f'{filename}{ext}'
|
||||||
|
|
||||||
|
form = FormData()
|
||||||
|
form.add_field(
|
||||||
|
name='data',
|
||||||
|
value=buffer,
|
||||||
|
filename=basename,
|
||||||
|
content_type=mime_type
|
||||||
|
)
|
||||||
|
|
||||||
|
async with ClientSession() as session:
|
||||||
|
response = await session.post(
|
||||||
|
url=url,
|
||||||
|
data=form
|
||||||
|
)
|
||||||
|
return await response.text()
|
||||||
@@ -1,39 +1,9 @@
|
|||||||
import asyncio
|
|
||||||
|
|
||||||
from typing import Any, Dict
|
|
||||||
|
|
||||||
from ..context.state_machine import State, StatesGroup
|
from ..context.state_machine import State, StatesGroup
|
||||||
|
from .context import MemoryContext
|
||||||
|
|
||||||
|
|
||||||
class MemoryContext:
|
__all__ = [
|
||||||
def __init__(self, chat_id: int, user_id: int):
|
'State',
|
||||||
self.chat_id = chat_id
|
'StatesGroup',
|
||||||
self.user_id = user_id
|
'MemoryContext'
|
||||||
self._context: Dict[str, Any] = {}
|
]
|
||||||
self._state: State | None = None
|
|
||||||
self._lock = asyncio.Lock()
|
|
||||||
|
|
||||||
async def get_data(self) -> dict[str, Any]:
|
|
||||||
async with self._lock:
|
|
||||||
return self._context
|
|
||||||
|
|
||||||
async def set_data(self, data: dict[str, Any]):
|
|
||||||
async with self._lock:
|
|
||||||
self._context = data
|
|
||||||
|
|
||||||
async def update_data(self, **kwargs):
|
|
||||||
async with self._lock:
|
|
||||||
self._context.update(kwargs)
|
|
||||||
|
|
||||||
async def set_state(self, state: State | str = None):
|
|
||||||
async with self._lock:
|
|
||||||
self._state = state
|
|
||||||
|
|
||||||
async def get_state(self):
|
|
||||||
async with self._lock:
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
async def clear(self):
|
|
||||||
async with self._lock:
|
|
||||||
self._state = None
|
|
||||||
self._context = {}
|
|
||||||
93
maxapi/context/context.py
Normal file
93
maxapi/context/context.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
from ..context.state_machine import State
|
||||||
|
|
||||||
|
|
||||||
|
class MemoryContext:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Контекст хранения данных пользователя с блокировками.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (int): Идентификатор чата
|
||||||
|
user_id (int): Идентификатор пользователя
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, chat_id: int, user_id: int):
|
||||||
|
self.chat_id = chat_id
|
||||||
|
self.user_id = user_id
|
||||||
|
self._context: Dict[str, Any] = {}
|
||||||
|
self._state: State | str | None = None
|
||||||
|
self._lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def get_data(self) -> dict[str, Any]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Возвращает текущий контекст данных.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Словарь с данными контекста
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
return self._context
|
||||||
|
|
||||||
|
async def set_data(self, data: dict[str, Any]):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Полностью заменяет контекст данных.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: Новый словарь контекста
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
self._context = data
|
||||||
|
|
||||||
|
async def update_data(self, **kwargs):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Обновляет контекст данных новыми значениями.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
**kwargs: Пары ключ-значение для обновления
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
self._context.update(kwargs)
|
||||||
|
|
||||||
|
async def set_state(self, state: Optional[Union[State, str]] = None):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Устанавливает новое состояние.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state: Новое состояние или None для сброса
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
self._state = state
|
||||||
|
|
||||||
|
async def get_state(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Возвращает текущее состояние.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Текущее состояние или None
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
return self._state
|
||||||
|
|
||||||
|
async def clear(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Очищает контекст и сбрасывает состояние.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async with self._lock:
|
||||||
|
self._state = None
|
||||||
|
self._context = {}
|
||||||
@@ -1,4 +1,14 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class State:
|
class State:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Представляет отдельное состояние в FSM-группе.
|
||||||
|
|
||||||
|
При использовании внутри StatesGroup, автоматически присваивает уникальное имя в формате 'ИмяКласса:имя_поля'.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.name = None
|
self.name = None
|
||||||
|
|
||||||
@@ -10,7 +20,22 @@ class State:
|
|||||||
|
|
||||||
|
|
||||||
class StatesGroup:
|
class StatesGroup:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Базовый класс для описания группы состояний FSM.
|
||||||
|
|
||||||
|
Атрибуты должны быть экземплярами State. Метод `states()` возвращает список всех состояний в виде строк.
|
||||||
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def states(cls) -> list[str]:
|
def states(cls) -> List[str]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Получить список всех состояний в формате 'ИмяКласса:имя_состояния'.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Список строковых представлений состояний
|
||||||
|
"""
|
||||||
|
|
||||||
return [str(getattr(cls, attr)) for attr in dir(cls)
|
return [str(getattr(cls, attr)) for attr in dir(cls)
|
||||||
if isinstance(getattr(cls, attr), State)]
|
if isinstance(getattr(cls, attr), State)]
|
||||||
@@ -1,17 +1,24 @@
|
|||||||
from typing import Callable, List
|
from __future__ import annotations
|
||||||
|
|
||||||
from fastapi import FastAPI, Request
|
import asyncio
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
from uvicorn import Config, Server
|
|
||||||
from aiohttp import ClientConnectorDNSError
|
|
||||||
|
|
||||||
|
import functools
|
||||||
|
from typing import Any, Awaitable, Callable, Dict, List, TYPE_CHECKING, Literal, Optional
|
||||||
|
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
||||||
|
|
||||||
|
from aiohttp import ClientConnectorError
|
||||||
|
|
||||||
|
from maxapi.exceptions.dispatcher import HandlerException
|
||||||
|
|
||||||
|
from .filters.filter import BaseFilter
|
||||||
|
from .filters.middleware import BaseMiddleware
|
||||||
from .filters.handler import Handler
|
from .filters.handler import Handler
|
||||||
|
|
||||||
from .context import MemoryContext
|
from .context import MemoryContext
|
||||||
from .types.updates import UpdateUnion
|
from .types.updates import UpdateUnion
|
||||||
from .types.errors import Error
|
from .types.errors import Error
|
||||||
|
|
||||||
from .methods.types.getted_updates import process_update_webhook, process_update_request
|
from .methods.types.getted_updates import process_update_request, process_update_webhook
|
||||||
|
|
||||||
from .filters import filter_attrs
|
from .filters import filter_attrs
|
||||||
|
|
||||||
@@ -20,20 +27,69 @@ from .enums.update import UpdateType
|
|||||||
from .loggers import logger_dp
|
from .loggers import logger_dp
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
try:
|
||||||
|
from fastapi import FastAPI, Request # type: ignore
|
||||||
|
from fastapi.responses import JSONResponse # type: ignore
|
||||||
|
FASTAPI_INSTALLED = True
|
||||||
|
except ImportError:
|
||||||
|
FASTAPI_INSTALLED = False
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from uvicorn import Config, Server # type: ignore
|
||||||
|
UVICORN_INSTALLED = True
|
||||||
|
except ImportError:
|
||||||
|
UVICORN_INSTALLED = False
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from magic_filter import MagicFilter
|
||||||
|
|
||||||
|
CONNECTION_RETRY_DELAY = 30
|
||||||
|
GET_UPDATES_RETRY_DELAY = 5
|
||||||
|
|
||||||
|
|
||||||
class Dispatcher:
|
class Dispatcher:
|
||||||
def __init__(self):
|
|
||||||
|
"""
|
||||||
|
Основной класс для обработки событий бота.
|
||||||
|
|
||||||
|
Обеспечивает запуск поллинга и вебхука, маршрутизацию событий,
|
||||||
|
применение middleware, фильтров и вызов соответствующих обработчиков.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, router_id: str | None = None) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация диспетчера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
router_id (str | None): Идентификатор роутера для логов.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.router_id = router_id
|
||||||
|
|
||||||
self.event_handlers: List[Handler] = []
|
self.event_handlers: List[Handler] = []
|
||||||
self.contexts: List[MemoryContext] = []
|
self.contexts: List[MemoryContext] = []
|
||||||
self.bot = None
|
self.routers: List[Router | Dispatcher] = []
|
||||||
self.on_started_func = None
|
self.filters: List[MagicFilter] = []
|
||||||
|
self.base_filters: List[BaseFilter] = []
|
||||||
|
self.middlewares: List[BaseMiddleware] = []
|
||||||
|
|
||||||
|
self.bot: Optional[Bot] = None
|
||||||
|
self.webhook_app: Optional[FastAPI] = None
|
||||||
|
self.on_started_func: Optional[Callable] = None
|
||||||
|
self.polling = False
|
||||||
|
|
||||||
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self)
|
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self)
|
||||||
self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self)
|
self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self)
|
||||||
self.bot_removed = Event(update_type=UpdateType.BOT_REMOVED, router=self)
|
self.bot_removed = Event(update_type=UpdateType.BOT_REMOVED, router=self)
|
||||||
self.bot_started = Event(update_type=UpdateType.BOT_STARTED, router=self)
|
self.bot_started = Event(update_type=UpdateType.BOT_STARTED, router=self)
|
||||||
|
self.bot_stopped = Event(update_type=UpdateType.BOT_STOPPED, router=self)
|
||||||
|
self.dialog_cleared = Event(update_type=UpdateType.DIALOG_CLEARED, router=self)
|
||||||
|
self.dialog_muted = Event(update_type=UpdateType.DIALOG_MUTED, router=self)
|
||||||
|
self.dialog_unmuted = Event(update_type=UpdateType.DIALOG_UNMUTED, router=self)
|
||||||
|
self.dialog_removed = Event(update_type=UpdateType.DIALOG_REMOVED, router=self)
|
||||||
self.chat_title_changed = Event(update_type=UpdateType.CHAT_TITLE_CHANGED, router=self)
|
self.chat_title_changed = Event(update_type=UpdateType.CHAT_TITLE_CHANGED, router=self)
|
||||||
self.message_callback = Event(update_type=UpdateType.MESSAGE_CALLBACK, router=self)
|
self.message_callback = Event(update_type=UpdateType.MESSAGE_CALLBACK, router=self)
|
||||||
self.message_chat_created = Event(update_type=UpdateType.MESSAGE_CHAT_CREATED, router=self)
|
self.message_chat_created = Event(update_type=UpdateType.MESSAGE_CHAT_CREATED, router=self)
|
||||||
@@ -43,16 +99,143 @@ class Dispatcher:
|
|||||||
self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self)
|
self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self)
|
||||||
self.on_started = Event(update_type=UpdateType.ON_STARTED, router=self)
|
self.on_started = Event(update_type=UpdateType.ON_STARTED, router=self)
|
||||||
|
|
||||||
|
def webhook_post(self, path: str):
|
||||||
|
def decorator(func):
|
||||||
|
if self.webhook_app is None:
|
||||||
|
try:
|
||||||
|
from fastapi import FastAPI # type: ignore
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(
|
||||||
|
'\n\t Не установлен fastapi!'
|
||||||
|
'\n\t Выполните команду для установки fastapi: '
|
||||||
|
'\n\t pip install fastapi>=0.68.0'
|
||||||
|
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||||
|
'\n\t pip install maxapi[webhook]'
|
||||||
|
)
|
||||||
|
self.webhook_app = FastAPI()
|
||||||
|
return self.webhook_app.post(path)(func)
|
||||||
|
return decorator
|
||||||
|
|
||||||
async def check_me(self):
|
async def check_me(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Проверяет и логирует информацию о боте.
|
||||||
|
"""
|
||||||
|
|
||||||
me = await self.bot.get_me()
|
me = await self.bot.get_me()
|
||||||
logger_dp.info(f'Бот: @{me.username} id={me.user_id}')
|
|
||||||
|
self.bot._me = me
|
||||||
|
|
||||||
|
logger_dp.info(f'Бот: @{me.username} first_name={me.first_name} id={me.user_id}')
|
||||||
|
|
||||||
|
def build_middleware_chain(
|
||||||
|
self,
|
||||||
|
middlewares: list[BaseMiddleware],
|
||||||
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]]
|
||||||
|
) -> Callable[[Any, dict[str, Any]], Awaitable[Any]]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Формирует цепочку вызова middleware вокруг хендлера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
middlewares (list[BaseMiddleware]): Список middleware.
|
||||||
|
handler (Callable): Финальный обработчик.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable: Обёрнутый обработчик.
|
||||||
|
"""
|
||||||
|
|
||||||
|
for mw in reversed(middlewares):
|
||||||
|
handler = functools.partial(mw, handler)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
def include_routers(self, *routers: 'Router'):
|
def include_routers(self, *routers: 'Router'):
|
||||||
for router in routers:
|
|
||||||
for event in router.event_handlers:
|
|
||||||
self.event_handlers.append(event)
|
|
||||||
|
|
||||||
def get_memory_context(self, chat_id: int, user_id: int):
|
"""
|
||||||
|
Добавляет указанные роутеры в диспетчер.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
*routers (Router): Роутеры для добавления.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.routers += [r for r in routers]
|
||||||
|
|
||||||
|
def outer_middleware(self, middleware: BaseMiddleware) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Добавляет Middleware на первое место в списке.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
middleware (BaseMiddleware): Middleware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.middlewares.insert(0, middleware)
|
||||||
|
|
||||||
|
def middleware(self, middleware: BaseMiddleware) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Добавляет Middleware в конец списка.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
middleware (BaseMiddleware): Middleware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.middlewares.append(middleware)
|
||||||
|
|
||||||
|
def filter(self, base_filter: BaseFilter) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Добавляет фильтр в список.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_filter (BaseFilter): Фильтр.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.base_filters.append(base_filter)
|
||||||
|
|
||||||
|
async def __ready(self, bot: Bot):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Подготавливает диспетчер: сохраняет бота, регистрирует обработчики, вызывает on_started.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
if self.polling and self.bot.auto_check_subscriptions:
|
||||||
|
response = await self.bot.get_subscriptions()
|
||||||
|
|
||||||
|
if response.subscriptions:
|
||||||
|
logger_subscriptions_text = ', '.join([s.url for s in response.subscriptions])
|
||||||
|
logger_dp.warning('БОТ ИГНОРИРУЕТ POLLING! Обнаружены установленные подписки: %s', logger_subscriptions_text)
|
||||||
|
|
||||||
|
await self.check_me()
|
||||||
|
|
||||||
|
self.routers += [self]
|
||||||
|
|
||||||
|
handlers_count = sum(len(router.event_handlers) for router in self.routers)
|
||||||
|
|
||||||
|
logger_dp.info(f'{handlers_count} событий на обработку')
|
||||||
|
|
||||||
|
if self.on_started_func:
|
||||||
|
await self.on_started_func()
|
||||||
|
|
||||||
|
def __get_memory_context(self, chat_id: int, user_id: int):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Возвращает существующий или создаёт новый MemoryContext по chat_id и user_id.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_id (int): Идентификатор пользователя.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MemoryContext: Контекст.
|
||||||
|
"""
|
||||||
|
|
||||||
for ctx in self.contexts:
|
for ctx in self.contexts:
|
||||||
if ctx.chat_id == chat_id and ctx.user_id == user_id:
|
if ctx.chat_id == chat_id and ctx.user_id == user_id:
|
||||||
return ctx
|
return ctx
|
||||||
@@ -61,10 +244,106 @@ class Dispatcher:
|
|||||||
self.contexts.append(new_ctx)
|
self.contexts.append(new_ctx)
|
||||||
return new_ctx
|
return new_ctx
|
||||||
|
|
||||||
|
async def call_handler(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
||||||
|
event_object: UpdateType,
|
||||||
|
data: Dict[str, Any]
|
||||||
|
):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Вызывает хендлер с нужными аргументами.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler: Handler.
|
||||||
|
event_object: Объект события.
|
||||||
|
data: Данные для хендлера.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
func_args = handler.func_event.__annotations__.keys()
|
||||||
|
kwargs_filtered = {k: v for k, v in data.items() if k in func_args}
|
||||||
|
|
||||||
|
if kwargs_filtered:
|
||||||
|
await handler.func_event(event_object, **kwargs_filtered)
|
||||||
|
else:
|
||||||
|
await handler.func_event(event_object)
|
||||||
|
|
||||||
|
async def process_base_filters(
|
||||||
|
self,
|
||||||
|
event: UpdateUnion,
|
||||||
|
filters: List[BaseFilter]
|
||||||
|
) -> Optional[Dict[str, Any]] | Literal[False]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Асинхронно применяет фильтры к событию.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (UpdateUnion): Событие.
|
||||||
|
filters (List[BaseFilter]): Список фильтров.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Dict[str, Any]] | Literal[False]: Словарь с результатом или False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
for _filter in filters:
|
||||||
|
result = await _filter(event)
|
||||||
|
|
||||||
|
if isinstance(result, dict):
|
||||||
|
data.update(result)
|
||||||
|
|
||||||
|
elif not result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
async def handle(self, event_object: UpdateUnion):
|
async def handle(self, event_object: UpdateUnion):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Основной обработчик события. Применяет фильтры, middleware и вызывает нужный handler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_object (UpdateUnion): Событие.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
ids = event_object.get_ids()
|
||||||
|
memory_context = self.__get_memory_context(*ids)
|
||||||
|
current_state = await memory_context.get_state()
|
||||||
|
kwargs = {'context': memory_context}
|
||||||
|
router_id = None
|
||||||
|
|
||||||
|
process_info = f'{event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}'
|
||||||
|
|
||||||
is_handled = False
|
is_handled = False
|
||||||
|
|
||||||
for handler in self.event_handlers:
|
for index, router in enumerate(self.routers):
|
||||||
|
|
||||||
|
if is_handled:
|
||||||
|
break
|
||||||
|
|
||||||
|
router_id = router.router_id or index
|
||||||
|
|
||||||
|
if router.filters:
|
||||||
|
if not filter_attrs(event_object, *router.filters):
|
||||||
|
continue
|
||||||
|
|
||||||
|
result_router_filter = await self.process_base_filters(
|
||||||
|
event=event_object,
|
||||||
|
filters=router.base_filters
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(result_router_filter, dict):
|
||||||
|
kwargs.update(result_router_filter)
|
||||||
|
|
||||||
|
elif not result_router_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for handler in router.event_handlers:
|
||||||
|
|
||||||
if not handler.update_type == event_object.update_type:
|
if not handler.update_type == event_object.update_type:
|
||||||
continue
|
continue
|
||||||
@@ -73,47 +352,86 @@ class Dispatcher:
|
|||||||
if not filter_attrs(event_object, *handler.filters):
|
if not filter_attrs(event_object, *handler.filters):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ids = event_object.get_ids()
|
if handler.states:
|
||||||
|
if current_state not in handler.states:
|
||||||
memory_context = self.get_memory_context(*ids)
|
|
||||||
|
|
||||||
if not handler.state == await memory_context.get_state() \
|
|
||||||
and handler.state:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
func_args = handler.func_event.__annotations__.keys()
|
func_args = handler.func_event.__annotations__.keys()
|
||||||
|
|
||||||
kwargs = {'context': memory_context}
|
if handler.base_filters:
|
||||||
|
result_filter = await self.process_base_filters(
|
||||||
|
event=event_object,
|
||||||
|
filters=handler.base_filters
|
||||||
|
)
|
||||||
|
|
||||||
for key in kwargs.copy().keys():
|
if isinstance(result_filter, dict):
|
||||||
if not key in func_args:
|
kwargs.update(result_filter)
|
||||||
del kwargs[key]
|
|
||||||
|
|
||||||
await handler.func_event(event_object, **kwargs)
|
elif not result_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
|
if isinstance(router, Router):
|
||||||
|
full_middlewares = self.middlewares + router.middlewares + handler.middlewares
|
||||||
|
elif isinstance(router, Dispatcher):
|
||||||
|
full_middlewares = self.middlewares + handler.middlewares
|
||||||
|
|
||||||
|
handler_chain = self.build_middleware_chain(
|
||||||
|
full_middlewares,
|
||||||
|
functools.partial(self.call_handler, handler)
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs_filtered = {k: v for k, v in kwargs.items() if k in func_args}
|
||||||
|
|
||||||
|
try:
|
||||||
|
await handler_chain(event_object, kwargs_filtered)
|
||||||
|
except:
|
||||||
|
raise HandlerException(
|
||||||
|
handler_title=handler.func_event.__name__,
|
||||||
|
memory_context={
|
||||||
|
'data': await memory_context.get_data(),
|
||||||
|
'state': current_state
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger_dp.info(f'Обработано: router_id: {router_id} | {process_info}')
|
||||||
|
|
||||||
is_handled = True
|
is_handled = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not is_handled:
|
if not is_handled:
|
||||||
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
|
logger_dp.info(f'Проигнорировано: router_id: {router_id} | {process_info}')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger_dp.error(f"Ошибка при обработке события: router_id: {router_id} | {process_info} | {e} ")
|
||||||
|
|
||||||
async def start_polling(self, bot: Bot):
|
async def start_polling(self, bot: Bot):
|
||||||
self.bot = bot
|
|
||||||
await self.check_me()
|
|
||||||
|
|
||||||
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
|
"""
|
||||||
|
Запускает цикл получения обновлений (long polling).
|
||||||
|
|
||||||
if self.on_started_func:
|
Args:
|
||||||
await self.on_started_func()
|
bot (Bot): Экземпляр бота.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.polling = True
|
||||||
|
|
||||||
|
await self.__ready(bot)
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
while self.polling:
|
||||||
|
|
||||||
|
try:
|
||||||
|
events: Dict = await self.bot.get_updates()
|
||||||
|
except AsyncioTimeoutError:
|
||||||
|
continue
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
events = await self.bot.get_updates()
|
|
||||||
|
|
||||||
if isinstance(events, Error):
|
if isinstance(events, Error):
|
||||||
logger_dp.info(f'Ошибка при получении обновлений: {events}')
|
logger_dp.info(f'Ошибка при получении обновлений: {events}, жду {GET_UPDATES_RETRY_DELAY} секунд')
|
||||||
|
await asyncio.sleep(GET_UPDATES_RETRY_DELAY)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self.bot.marker_updates = events.get('marker')
|
self.bot.marker_updates = events.get('marker')
|
||||||
@@ -124,59 +442,144 @@ class Dispatcher:
|
|||||||
)
|
)
|
||||||
|
|
||||||
for event in processed_events:
|
for event in processed_events:
|
||||||
try:
|
|
||||||
await self.handle(event)
|
await self.handle(event)
|
||||||
|
|
||||||
|
except ClientConnectorError:
|
||||||
|
logger_dp.error(f'Ошибка подключения, жду {CONNECTION_RETRY_DELAY} секунд')
|
||||||
|
await asyncio.sleep(CONNECTION_RETRY_DELAY)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_dp.error(f"Ошибка при обработке события: {event.update_type}: {e}")
|
logger_dp.error(f'Общая ошибка при обработке событий: {e.__class__} - {e}')
|
||||||
except ClientConnectorDNSError:
|
|
||||||
logger_dp.error(f'Ошибка подключения: {e}')
|
|
||||||
except Exception as e:
|
|
||||||
logger_dp.error(f'Общая ошибка при обработке событий: {e}')
|
|
||||||
|
|
||||||
async def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080):
|
async def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
|
||||||
self.bot = bot
|
|
||||||
await self.check_me()
|
|
||||||
|
|
||||||
if self.on_started_func:
|
"""
|
||||||
await self.on_started_func()
|
Запускает FastAPI-приложение для приёма обновлений через вебхук.
|
||||||
|
|
||||||
@app.post('/')
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
host (str): Хост сервера.
|
||||||
|
port (int): Порт сервера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not FASTAPI_INSTALLED:
|
||||||
|
raise ImportError(
|
||||||
|
'\n\t Не установлен fastapi!'
|
||||||
|
'\n\t Выполните команду для установки fastapi: '
|
||||||
|
'\n\t pip install fastapi>=0.68.0'
|
||||||
|
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||||
|
'\n\t pip install maxapi[webhook]'
|
||||||
|
)
|
||||||
|
|
||||||
|
elif not UVICORN_INSTALLED:
|
||||||
|
raise ImportError(
|
||||||
|
'\n\t Не установлен uvicorn!'
|
||||||
|
'\n\t Выполните команду для установки uvicorn: '
|
||||||
|
'\n\t pip install uvicorn>=0.15.0'
|
||||||
|
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||||
|
'\n\t pip install maxapi[webhook]'
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.webhook_post('/')
|
||||||
async def _(request: Request):
|
async def _(request: Request):
|
||||||
try:
|
|
||||||
event_json = await request.json()
|
event_json = await request.json()
|
||||||
|
|
||||||
event_object = await process_update_webhook(
|
event_object = await process_update_webhook(
|
||||||
event_json=event_json,
|
event_json=event_json,
|
||||||
bot=self.bot
|
bot=bot
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.handle(event_object)
|
await self.handle(event_object)
|
||||||
|
|
||||||
return JSONResponse(content={'ok': True}, status_code=200)
|
return JSONResponse(content={'ok': True}, status_code=200)
|
||||||
except Exception as e:
|
|
||||||
logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}")
|
|
||||||
|
|
||||||
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
|
|
||||||
config = Config(app=app, host=host, port=port, log_level="critical")
|
await self.init_serve(
|
||||||
|
bot=bot,
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
async def init_serve(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Запускает сервер для обработки вебхуков.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
host (str): Хост.
|
||||||
|
port (int): Порт.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not UVICORN_INSTALLED:
|
||||||
|
raise ImportError(
|
||||||
|
'\n\t Не установлен uvicorn!'
|
||||||
|
'\n\t Выполните команду для установки uvicorn: '
|
||||||
|
'\n\t pip install uvicorn>=0.15.0'
|
||||||
|
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||||
|
'\n\t pip install maxapi[webhook]'
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.webhook_app is None:
|
||||||
|
raise RuntimeError('webhook_app не инициализирован')
|
||||||
|
|
||||||
|
config = Config(app=self.webhook_app, host=host, port=port, **kwargs)
|
||||||
server = Server(config)
|
server = Server(config)
|
||||||
|
|
||||||
|
await self.__ready(bot)
|
||||||
|
|
||||||
await server.serve()
|
await server.serve()
|
||||||
|
|
||||||
|
|
||||||
class Router(Dispatcher):
|
class Router(Dispatcher):
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
"""
|
||||||
|
Роутер для группировки обработчиков событий.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, router_id: str | None = None):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация роутера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
router_id (str | None): Идентификатор роутера для логов.
|
||||||
|
"""
|
||||||
|
|
||||||
|
super().__init__(router_id)
|
||||||
|
|
||||||
|
|
||||||
class Event:
|
class Event:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Декоратор для регистрации обработчиков событий.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, update_type: UpdateType, router: Dispatcher | Router):
|
def __init__(self, update_type: UpdateType, router: Dispatcher | Router):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализирует событие-декоратор.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
update_type (UpdateType): Тип события.
|
||||||
|
router (Dispatcher | Router): Экземпляр роутера или диспетчера.
|
||||||
|
"""
|
||||||
|
|
||||||
self.update_type = update_type
|
self.update_type = update_type
|
||||||
self.router = router
|
self.router = router
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Регистрирует функцию как обработчик события.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Callable: Исходная функция.
|
||||||
|
"""
|
||||||
|
|
||||||
def decorator(func_event: Callable):
|
def decorator(func_event: Callable):
|
||||||
|
|
||||||
if self.update_type == UpdateType.ON_STARTED:
|
if self.update_type == UpdateType.ON_STARTED:
|
||||||
self.router.on_started_func = func_event
|
self.router.on_started_func = func_event
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.router.event_handlers.append(
|
self.router.event_handlers.append(
|
||||||
Handler(
|
Handler(
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class ApiPath(str, Enum):
|
class ApiPath(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Перечисление всех доступных API-эндпоинтов.
|
||||||
|
|
||||||
|
Используется для унифицированного указания путей при отправке запросов.
|
||||||
|
"""
|
||||||
|
|
||||||
ME = '/me'
|
ME = '/me'
|
||||||
CHATS = '/chats'
|
CHATS = '/chats'
|
||||||
MESSAGES = '/messages'
|
MESSAGES = '/messages'
|
||||||
@@ -12,3 +20,4 @@ class ApiPath(str, Enum):
|
|||||||
MEMBERS = '/members'
|
MEMBERS = '/members'
|
||||||
ADMINS = '/admins'
|
ADMINS = '/admins'
|
||||||
UPLOADS = '/uploads'
|
UPLOADS = '/uploads'
|
||||||
|
SUBSCRIPTIONS = '/subscriptions'
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class AttachmentType(str, Enum):
|
class AttachmentType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Типы вложений, поддерживаемые в сообщениях.
|
||||||
|
|
||||||
|
Используется для указания типа содержимого при отправке или обработке вложений.
|
||||||
|
"""
|
||||||
|
|
||||||
IMAGE = 'image'
|
IMAGE = 'image'
|
||||||
VIDEO = 'video'
|
VIDEO = 'video'
|
||||||
AUDIO = 'audio'
|
AUDIO = 'audio'
|
||||||
@@ -9,3 +17,4 @@ class AttachmentType(str, Enum):
|
|||||||
CONTACT = 'contact'
|
CONTACT = 'contact'
|
||||||
INLINE_KEYBOARD = 'inline_keyboard'
|
INLINE_KEYBOARD = 'inline_keyboard'
|
||||||
LOCATION = 'location'
|
LOCATION = 'location'
|
||||||
|
SHARE = 'share'
|
||||||
@@ -2,8 +2,17 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class ButtonType(str, Enum):
|
class ButtonType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Типы кнопок, доступных в интерфейсе бота.
|
||||||
|
|
||||||
|
Определяют поведение при нажатии на кнопку в сообщении.
|
||||||
|
"""
|
||||||
|
|
||||||
REQUEST_CONTACT = 'request_contact'
|
REQUEST_CONTACT = 'request_contact'
|
||||||
CALLBACK = 'callback'
|
CALLBACK = 'callback'
|
||||||
LINK = 'link'
|
LINK = 'link'
|
||||||
REQUEST_GEO_LOCATION = 'request_geo_location'
|
REQUEST_GEO_LOCATION = 'request_geo_location'
|
||||||
CHAT = 'chat'
|
CHAT = 'chat'
|
||||||
|
MESSAGE = 'message'
|
||||||
|
OPEN_APP = 'open_app'
|
||||||
@@ -2,6 +2,13 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class ChatPermission(str, Enum):
|
class ChatPermission(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Права доступа пользователя в чате.
|
||||||
|
|
||||||
|
Используются для управления разрешениями при добавлении участников или изменении настроек чата.
|
||||||
|
"""
|
||||||
|
|
||||||
READ_ALL_MESSAGES = 'read_all_messages'
|
READ_ALL_MESSAGES = 'read_all_messages'
|
||||||
ADD_REMOVE_MEMBERS = 'add_remove_members'
|
ADD_REMOVE_MEMBERS = 'add_remove_members'
|
||||||
ADD_ADMINS = 'add_admins'
|
ADD_ADMINS = 'add_admins'
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class ChatStatus(str, Enum):
|
class ChatStatus(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Статус чата относительно пользователя или системы.
|
||||||
|
|
||||||
|
Используется для отображения текущего состояния чата или определения доступных действий.
|
||||||
|
"""
|
||||||
|
|
||||||
ACTIVE = 'active'
|
ACTIVE = 'active'
|
||||||
REMOVED = 'removed'
|
REMOVED = 'removed'
|
||||||
LEFT = 'left'
|
LEFT = 'left'
|
||||||
|
|||||||
@@ -2,5 +2,12 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class ChatType(str, Enum):
|
class ChatType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Тип чата.
|
||||||
|
|
||||||
|
Используется для различения личных и групповых чатов.
|
||||||
|
"""
|
||||||
|
|
||||||
DIALOG = 'dialog'
|
DIALOG = 'dialog'
|
||||||
CHAT = 'chat'
|
CHAT = 'chat'
|
||||||
@@ -2,6 +2,13 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class HTTPMethod(str, Enum):
|
class HTTPMethod(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
HTTP-методы, поддерживаемые клиентом API.
|
||||||
|
|
||||||
|
Используются при выполнении запросов к серверу.
|
||||||
|
"""
|
||||||
|
|
||||||
POST = 'POST'
|
POST = 'POST'
|
||||||
GET = 'GET'
|
GET = 'GET'
|
||||||
PATCH = 'PATCH'
|
PATCH = 'PATCH'
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class Intent(str, Enum):
|
class Intent(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Тип интента (намерения) кнопки.
|
||||||
|
|
||||||
|
Используется для стилизации и логической классификации пользовательских действий.
|
||||||
|
"""
|
||||||
|
|
||||||
DEFAULT = 'default'
|
DEFAULT = 'default'
|
||||||
POSITIVE = 'positive'
|
POSITIVE = 'positive'
|
||||||
NEGATIVE = 'negative'
|
NEGATIVE = 'negative'
|
||||||
@@ -2,5 +2,12 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class MessageLinkType(str, Enum):
|
class MessageLinkType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Тип связи между сообщениями.
|
||||||
|
|
||||||
|
Используется для указания типа привязки: пересылка или ответ.
|
||||||
|
"""
|
||||||
|
|
||||||
FORWARD = 'forward'
|
FORWARD = 'forward'
|
||||||
REPLY = 'reply'
|
REPLY = 'reply'
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class ParseMode(str, Enum):
|
class ParseMode(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Формат разметки текста сообщений.
|
||||||
|
|
||||||
|
Используется для указания способа интерпретации стилей (жирный, курсив, ссылки и т.д.).
|
||||||
|
"""
|
||||||
|
|
||||||
MARKDOWN = 'markdown'
|
MARKDOWN = 'markdown'
|
||||||
HTML = 'html'
|
HTML = 'html'
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class SenderAction(str, Enum):
|
class SenderAction(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Действия отправителя, отображаемые получателю в интерфейсе.
|
||||||
|
|
||||||
|
Используются для имитации активности (например, "печатает...") перед отправкой сообщения или медиа.
|
||||||
|
"""
|
||||||
|
|
||||||
TYPING_ON = 'typing_on'
|
TYPING_ON = 'typing_on'
|
||||||
SENDING_PHOTO = 'sending_photo'
|
SENDING_PHOTO = 'sending_photo'
|
||||||
SENDING_VIDEO = 'sending_video'
|
SENDING_VIDEO = 'sending_video'
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class TextStyle(Enum):
|
class TextStyle(Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Стили текста, применяемые в сообщениях.
|
||||||
|
|
||||||
|
Используются для форматирования и выделения частей текста в сообщении.
|
||||||
|
"""
|
||||||
|
|
||||||
UNDERLINE = 'underline'
|
UNDERLINE = 'underline'
|
||||||
STRONG = 'strong'
|
STRONG = 'strong'
|
||||||
EMPHASIZED = 'emphasized'
|
EMPHASIZED = 'emphasized'
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class UpdateType(str, Enum):
|
class UpdateType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Типы обновлений (ивентов) от API.
|
||||||
|
|
||||||
|
Используются для обработки различных событий в боте или чате.
|
||||||
|
"""
|
||||||
|
|
||||||
MESSAGE_CREATED = 'message_created'
|
MESSAGE_CREATED = 'message_created'
|
||||||
BOT_ADDED = 'bot_added'
|
BOT_ADDED = 'bot_added'
|
||||||
BOT_REMOVED = 'bot_removed'
|
BOT_REMOVED = 'bot_removed'
|
||||||
@@ -12,5 +20,11 @@ class UpdateType(str, Enum):
|
|||||||
MESSAGE_REMOVED = 'message_removed'
|
MESSAGE_REMOVED = 'message_removed'
|
||||||
USER_ADDED = 'user_added'
|
USER_ADDED = 'user_added'
|
||||||
USER_REMOVED = 'user_removed'
|
USER_REMOVED = 'user_removed'
|
||||||
|
BOT_STOPPED = 'bot_stopped'
|
||||||
|
DIALOG_CLEARED = 'dialog_cleared'
|
||||||
|
DIALOG_MUTED = 'dialog_muted'
|
||||||
|
DIALOG_UNMUTED = 'dialog_unmuted'
|
||||||
|
DIALOG_REMOVED = 'dialog_removed'
|
||||||
|
|
||||||
|
# Для начинки диспатчера
|
||||||
ON_STARTED = 'on_started'
|
ON_STARTED = 'on_started'
|
||||||
@@ -2,6 +2,13 @@ from enum import Enum
|
|||||||
|
|
||||||
|
|
||||||
class UploadType(str, Enum):
|
class UploadType(str, Enum):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Типы загружаемых файлов.
|
||||||
|
|
||||||
|
Используются для указания категории контента при загрузке на сервер.
|
||||||
|
"""
|
||||||
|
|
||||||
IMAGE = 'image'
|
IMAGE = 'image'
|
||||||
VIDEO = 'video'
|
VIDEO = 'video'
|
||||||
AUDIO = 'audio'
|
AUDIO = 'audio'
|
||||||
|
|||||||
17
maxapi/exceptions/dispatcher.py
Normal file
17
maxapi/exceptions/dispatcher.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class HandlerException(Exception):
|
||||||
|
def __init__(self, handler_title: str, *args, **kwargs):
|
||||||
|
|
||||||
|
self.handler_title = handler_title
|
||||||
|
self.extra = kwargs
|
||||||
|
|
||||||
|
message = f'Обработчик: {handler_title!r}'
|
||||||
|
|
||||||
|
if args:
|
||||||
|
message += f', детали: {args}'
|
||||||
|
|
||||||
|
if kwargs:
|
||||||
|
message += f', другое: {kwargs}'
|
||||||
|
|
||||||
|
super().__init__(message)
|
||||||
4
maxapi/exceptions/download_file.py
Normal file
4
maxapi/exceptions/download_file.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class NotAvailableForDownload(Exception):
|
||||||
|
...
|
||||||
4
maxapi/exceptions/invalid_token.py
Normal file
4
maxapi/exceptions/invalid_token.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
class InvalidToken(Exception):
|
||||||
|
...
|
||||||
11
maxapi/exceptions/max.py
Normal file
11
maxapi/exceptions/max.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
class MaxConnection(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MaxUploadFileFailed(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
class MaxIconParamsException(Exception):
|
||||||
|
...
|
||||||
@@ -1,53 +1,27 @@
|
|||||||
from magic_filter import MagicFilter
|
from magic_filter import MagicFilter
|
||||||
from magic_filter.operations.call import CallOperation as mf_call
|
from .filter import BaseFilter
|
||||||
from magic_filter.operations.function import FunctionOperation as mf_func
|
|
||||||
from magic_filter.operations.comparator import ComparatorOperation as mf_comparator
|
|
||||||
|
|
||||||
F = MagicFilter()
|
F = MagicFilter()
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BaseFilter'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Применяет один или несколько фильтров MagicFilter к объекту.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
obj (object): Объект, к которому применяются фильтры (например, event или message).
|
||||||
|
*filters (MagicFilter): Один или несколько выражений MagicFilter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True, если все фильтры возвращают True, иначе False.
|
||||||
|
"""
|
||||||
|
|
||||||
def filter_attrs(obj, *magic_args):
|
|
||||||
try:
|
try:
|
||||||
for arg in magic_args:
|
return all(f.resolve(obj) for f in filters)
|
||||||
|
except Exception:
|
||||||
attr_last = None
|
|
||||||
method_found = False
|
|
||||||
|
|
||||||
operations = arg._operations
|
|
||||||
if isinstance(operations[-1], mf_call):
|
|
||||||
operations = operations[:len(operations)-2]
|
|
||||||
method_found = True
|
|
||||||
elif isinstance(operations[-1], mf_func):
|
|
||||||
operations = operations[:len(operations)-1]
|
|
||||||
method_found = True
|
|
||||||
elif isinstance(operations[-1], mf_comparator):
|
|
||||||
operations = operations[:len(operations)-1]
|
|
||||||
|
|
||||||
for element in operations:
|
|
||||||
if attr_last is None:
|
|
||||||
attr_last = getattr(obj, element.name)
|
|
||||||
else:
|
|
||||||
attr_last = getattr(attr_last, element.name)
|
|
||||||
|
|
||||||
if attr_last is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
if isinstance(arg._operations[-1], mf_comparator):
|
|
||||||
return attr_last == arg._operations[-1].right
|
|
||||||
|
|
||||||
if not method_found:
|
|
||||||
return bool(attr_last)
|
|
||||||
|
|
||||||
if attr_last is None:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if isinstance(arg._operations[-1], mf_func):
|
|
||||||
func_operation: mf_func = arg._operations[-1]
|
|
||||||
return func_operation.resolve(attr_last, attr_last)
|
|
||||||
else:
|
|
||||||
method = getattr(attr_last, arg._operations[-2].name)
|
|
||||||
args = arg._operations[-1].args
|
|
||||||
|
|
||||||
return method(*args)
|
|
||||||
except Exception as e:
|
|
||||||
...
|
|
||||||
175
maxapi/filters/callback_payload.py
Normal file
175
maxapi/filters/callback_payload.py
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any, ClassVar, List, Optional, Type, TYPE_CHECKING
|
||||||
|
from magic_filter import MagicFilter
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from ..types.updates.message_callback import MessageCallback
|
||||||
|
from ..types.updates import UpdateUnion
|
||||||
|
from .filter import BaseFilter
|
||||||
|
|
||||||
|
PAYLOAD_MAX = 1024
|
||||||
|
|
||||||
|
|
||||||
|
class CallbackPayload(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Базовый класс для сериализации/десериализации callback payload.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
prefix (str): Префикс для payload (используется при pack/unpack) (по умолчанию название класса).
|
||||||
|
separator (str): Разделитель между значениями (по умолчанию '|').
|
||||||
|
"""
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
prefix: ClassVar[str]
|
||||||
|
separator: ClassVar[str]
|
||||||
|
|
||||||
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Автоматически проставляет prefix и separator при наследовании.
|
||||||
|
"""
|
||||||
|
|
||||||
|
cls.prefix = kwargs.get('prefix', str(cls.__name__))
|
||||||
|
cls.separator = kwargs.get('separator', '|')
|
||||||
|
|
||||||
|
def pack(self) -> str:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Собирает данные payload в строку для передачи в callback payload.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Если в значении встречается разделитель или payload слишком длинный.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Сериализованный payload.
|
||||||
|
"""
|
||||||
|
|
||||||
|
values = [self.prefix]
|
||||||
|
|
||||||
|
for name in self.attrs():
|
||||||
|
value = getattr(self, name)
|
||||||
|
str_value = '' if value is None else str(value)
|
||||||
|
if self.separator in str_value:
|
||||||
|
raise ValueError(
|
||||||
|
f'Символ разделителя "{self.separator}" не должен встречаться в значении поля {name}'
|
||||||
|
)
|
||||||
|
|
||||||
|
values.append(str_value)
|
||||||
|
|
||||||
|
data = self.separator.join(values)
|
||||||
|
|
||||||
|
if len(data.encode()) > PAYLOAD_MAX:
|
||||||
|
raise ValueError(
|
||||||
|
f'Payload слишком длинный! Максимум: {PAYLOAD_MAX} байт'
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unpack(cls, data: str):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Десериализует payload из строки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str): Строка payload (из callback payload).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: Некорректный prefix или количество аргументов.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
CallbackPayload: Экземпляр payload с заполненными полями.
|
||||||
|
"""
|
||||||
|
|
||||||
|
parts = data.split(cls.separator)
|
||||||
|
|
||||||
|
if not parts[0] == cls.prefix:
|
||||||
|
raise ValueError('Некорректный prefix')
|
||||||
|
|
||||||
|
field_names = cls.attrs()
|
||||||
|
|
||||||
|
if not len(parts) - 1 == len(field_names):
|
||||||
|
raise ValueError(
|
||||||
|
f'Ожидалось {len(field_names)} аргументов, получено {len(parts) - 1}'
|
||||||
|
)
|
||||||
|
|
||||||
|
kwargs = dict(zip(field_names, parts[1:]))
|
||||||
|
return cls(**kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def attrs(cls) -> List[str]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Возвращает список полей для сериализации/десериализации (исключая prefix и separator).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[str]: Имена полей модели.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return [
|
||||||
|
k for k in cls.model_fields.keys()
|
||||||
|
if k not in ('prefix', 'separator')
|
||||||
|
]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def filter(cls, rule: Optional[MagicFilter] = None) -> PayloadFilter:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Создаёт PayloadFilter для фильтрации callback-ивентов по payload.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
rule (Optional[MagicFilter]): Фильтр на payload.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PayloadFilter: Экземпляр фильтра для хэндлера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return PayloadFilter(model=cls, rule=rule)
|
||||||
|
|
||||||
|
|
||||||
|
class PayloadFilter(BaseFilter):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Фильтр для MessageCallback по payload.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, model: Type[CallbackPayload], rule: Optional[MagicFilter]):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
model (Type[CallbackPayload]): Класс payload для распаковки.
|
||||||
|
rule (Optional[MagicFilter]): Фильтр (условие) для payload.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.model = model
|
||||||
|
self.rule = rule
|
||||||
|
|
||||||
|
async def __call__(self, event: UpdateUnion):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Проверяет event на MessageCallback и применяет фильтр к payload.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (UpdateUnion): Обновление/событие.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict | bool: dict с payload при совпадении, иначе False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(event, MessageCallback):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not event.callback.payload:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = self.model.unpack(event.callback.payload)
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.rule or self.rule.resolve(payload):
|
||||||
|
return {'payload': payload}
|
||||||
|
|
||||||
|
return False
|
||||||
116
maxapi/filters/command.py
Normal file
116
maxapi/filters/command.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from ..types.updates import UpdateUnion
|
||||||
|
from ..filters.filter import BaseFilter
|
||||||
|
|
||||||
|
from ..types.updates.message_created import MessageCreated
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseFilter):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Фильтр сообщений на соответствие команде.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
commands (str | List[str]): Ожидаемая команда или список команд без префикса.
|
||||||
|
prefix (str, optional): Префикс команды (по умолчанию '/').
|
||||||
|
check_case (bool, optional): Учитывать регистр при сравнении (по умолчанию False).
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
commands (List[str]): Список команд без префикса.
|
||||||
|
prefix (str): Префикс команды.
|
||||||
|
check_case (bool): Флаг чувствительности к регистру.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, commands: str | List[str], prefix: str = '/', check_case: bool = False):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация фильтра команд.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(commands, str):
|
||||||
|
self.commands = [commands]
|
||||||
|
else:
|
||||||
|
self.commands = commands
|
||||||
|
|
||||||
|
self.prefix = prefix
|
||||||
|
self.check_case = check_case
|
||||||
|
|
||||||
|
if not check_case:
|
||||||
|
self.commands = [cmd.lower() for cmd in self.commands]
|
||||||
|
|
||||||
|
def parse_command(self, text: str) -> Tuple[str, List[str]]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Извлекает команду из текста.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): Текст сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[str]: Найденная команда с префиксом, либо None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
args = text.split()
|
||||||
|
first = args[0]
|
||||||
|
|
||||||
|
if not first.startswith(self.prefix):
|
||||||
|
return '', []
|
||||||
|
|
||||||
|
return first[len(self.prefix):], args
|
||||||
|
|
||||||
|
async def __call__(self, event: UpdateUnion):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Проверяет, соответствует ли сообщение заданной(ым) команде(ам).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event (MessageCreated): Событие сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True, если команда совпадает, иначе False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not isinstance(event, MessageCreated):
|
||||||
|
return False
|
||||||
|
|
||||||
|
text = event.message.body.text
|
||||||
|
|
||||||
|
if not text:
|
||||||
|
return False
|
||||||
|
|
||||||
|
parsed_command, args = self.parse_command(text)
|
||||||
|
if not parsed_command:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.check_case:
|
||||||
|
if parsed_command.lower() in [commands.lower() for commands in self.commands]:
|
||||||
|
return {'args': args}
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if parsed_command in self.commands:
|
||||||
|
return {'args': args}
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class CommandStart(Command):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Фильтр для команды /start.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
prefix (str, optional): Префикс команды (по умолчанию '/').
|
||||||
|
check_case (bool, optional): Учитывать регистр (по умолчанию False).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, prefix = '/', check_case = False):
|
||||||
|
super().__init__(
|
||||||
|
'start',
|
||||||
|
prefix,
|
||||||
|
check_case
|
||||||
|
)
|
||||||
|
|
||||||
|
async def __call__(self, event):
|
||||||
|
return await super().__call__(event)
|
||||||
21
maxapi/filters/filter.py
Normal file
21
maxapi/filters/filter.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..types.updates import UpdateUnion
|
||||||
|
|
||||||
|
|
||||||
|
class BaseFilter:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Базовый класс для фильтров.
|
||||||
|
|
||||||
|
Определяет интерфейс фильтрации событий.
|
||||||
|
Потомки должны переопределять метод __call__.
|
||||||
|
|
||||||
|
Methods:
|
||||||
|
__call__(event): Асинхронная проверка события на соответствие фильтру.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def __call__(self, event: UpdateUnion) -> bool | dict:
|
||||||
|
return True
|
||||||
@@ -1,15 +1,25 @@
|
|||||||
from typing import Callable
|
from typing import Callable, List, Optional
|
||||||
|
|
||||||
from magic_filter import F, MagicFilter
|
from magic_filter import MagicFilter
|
||||||
|
|
||||||
|
from ..filters.filter import BaseFilter
|
||||||
|
from ..filters.middleware import BaseMiddleware
|
||||||
|
|
||||||
from ..types.command import Command
|
|
||||||
from ..context.state_machine import State
|
from ..context.state_machine import State
|
||||||
|
|
||||||
from ..enums.update import UpdateType
|
from ..enums.update import UpdateType
|
||||||
|
|
||||||
from ..loggers import logger_dp
|
from ..loggers import logger_dp
|
||||||
|
|
||||||
|
|
||||||
class Handler:
|
class Handler:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Обработчик события.
|
||||||
|
|
||||||
|
Связывает функцию-обработчик с типом события, состояниями и фильтрами.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*args,
|
*args,
|
||||||
@@ -18,18 +28,32 @@ class Handler:
|
|||||||
**kwargs
|
**kwargs
|
||||||
):
|
):
|
||||||
|
|
||||||
self.func_event = func_event
|
"""
|
||||||
self.update_type = update_type
|
Создаёт обработчик события.
|
||||||
self.filters = []
|
|
||||||
self.state = None
|
Args:
|
||||||
|
*args: Список фильтров (MagicFilter, State, Command, BaseFilter, BaseMiddleware).
|
||||||
|
func_event (Callable): Функция-обработчик.
|
||||||
|
update_type (UpdateType): Тип обновления.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.func_event: Callable = func_event
|
||||||
|
self.update_type: UpdateType = update_type
|
||||||
|
self.filters: Optional[List[MagicFilter]] = []
|
||||||
|
self.base_filters: Optional[List[BaseFilter]] = []
|
||||||
|
self.states: Optional[List[State]] = []
|
||||||
|
self.middlewares: List[BaseMiddleware] = []
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, MagicFilter):
|
if isinstance(arg, MagicFilter):
|
||||||
self.filters.append(arg)
|
self.filters.append(arg)
|
||||||
elif isinstance(arg, State):
|
elif isinstance(arg, State):
|
||||||
self.state = arg
|
self.states.append(arg)
|
||||||
elif isinstance(arg, Command):
|
elif isinstance(arg, BaseMiddleware):
|
||||||
self.filters.insert(0, F.message.body.text.startswith(arg.command))
|
self.middlewares.append(arg)
|
||||||
|
elif isinstance(arg, BaseFilter):
|
||||||
|
self.base_filters.append(arg)
|
||||||
else:
|
else:
|
||||||
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
|
logger_dp.info(
|
||||||
f'регистрации функции `{func_event.__name__}`')
|
f'Неизвестный фильтр `{arg}` при регистрации `{func_event.__name__}`'
|
||||||
|
)
|
||||||
|
|||||||
30
maxapi/filters/middleware.py
Normal file
30
maxapi/filters/middleware.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from typing import Any, Callable, Awaitable
|
||||||
|
|
||||||
|
class BaseMiddleware:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Базовый класс для мидлварей.
|
||||||
|
|
||||||
|
Используется для обработки события до и после вызова хендлера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def __call__(
|
||||||
|
self,
|
||||||
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
||||||
|
event_object: Any,
|
||||||
|
data: dict[str, Any]
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Вызывает хендлер с переданным событием и данными.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (Callable): Хендлер события.
|
||||||
|
event_object (Any): Событие.
|
||||||
|
data (dict): Дополнительные данные.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Результат работы хендлера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await handler(event_object, data)
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||||
|
|
||||||
from .types.added_admin_chat import AddedListAdminChat
|
from .types.added_admin_chat import AddedListAdminChat
|
||||||
|
|
||||||
from ..types.users import ChatAdmin
|
from ..types.users import ChatAdmin
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
@@ -16,20 +15,43 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class AddAdminChat(BaseConnection):
|
class AddAdminChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для добавления списка администраторов в чат через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота, через который выполняется запрос.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
admins (List[ChatAdmin]): Список администраторов для добавления.
|
||||||
|
marker (int, optional): Маркер для пагинации или дополнительных настроек. По умолчанию None.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int,
|
chat_id: int,
|
||||||
admins: List[ChatAdmin],
|
admins: List[ChatAdmin],
|
||||||
marker: int = None
|
marker: Optional[int] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.admins = admins
|
self.admins = admins
|
||||||
self.marker = marker
|
self.marker = marker
|
||||||
|
|
||||||
async def request(self) -> AddedListAdminChat:
|
async def fetch(self) -> AddedListAdminChat:
|
||||||
json = {}
|
|
||||||
|
"""
|
||||||
|
Выполняет HTTP POST запрос для добавления администраторов в чат.
|
||||||
|
|
||||||
|
Формирует JSON с данными администраторов и отправляет запрос на соответствующий API-эндпоинт.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AddedListAdminChat: Результат операции с информацией об успешности.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
json['admins'] = [admin.model_dump() for admin in self.admins]
|
json['admins'] = [admin.model_dump() for admin in self.admins]
|
||||||
json['marker'] = self.marker
|
json['marker'] = self.marker
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, Any, Dict, List
|
||||||
|
|
||||||
from ..methods.types.added_members_chat import AddedMembersChat
|
from ..methods.types.added_members_chat import AddedMembersChat
|
||||||
|
|
||||||
@@ -14,6 +14,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class AddMembersChat(BaseConnection):
|
class AddMembersChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для добавления участников в чат через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота, через который выполняется запрос.
|
||||||
|
chat_id (int): Идентификатор целевого чата.
|
||||||
|
user_ids (List[int]): Список ID пользователей для добавления в чат.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -25,8 +34,21 @@ class AddMembersChat(BaseConnection):
|
|||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.user_ids = user_ids
|
self.user_ids = user_ids
|
||||||
|
|
||||||
async def request(self) -> AddedMembersChat:
|
async def fetch(self) -> AddedMembersChat:
|
||||||
json = {}
|
|
||||||
|
"""
|
||||||
|
Отправляет POST-запрос на добавление пользователей в чат.
|
||||||
|
|
||||||
|
Формирует JSON с ID пользователей и вызывает базовый метод запроса.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
AddedMembersChat: Результат операции с информацией об успешности добавления.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
json['user_ids'] = self.user_ids
|
json['user_ids'] = self.user_ids
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from typing import Any, Dict, List, TYPE_CHECKING
|
from typing import Any, Dict, List, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from ..types.attachments.image import PhotoAttachmentRequestPayload
|
||||||
|
|
||||||
from ..types.users import User
|
from ..types.users import User
|
||||||
from ..types.command import BotCommand
|
from ..types.command import BotCommand
|
||||||
@@ -14,13 +16,25 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class ChangeInfo(BaseConnection):
|
class ChangeInfo(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для изменения информации о боте.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Объект бота
|
||||||
|
name (str, optional): Новое имя бота
|
||||||
|
description (str, optional): Новое описание
|
||||||
|
commands (List[BotCommand], optional): Список команд
|
||||||
|
photo (PhotoAttachmentRequestPayload, optional): Данные фото
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
name: str = None,
|
name: Optional[str] = None,
|
||||||
description: str = None,
|
description: Optional[str] = None,
|
||||||
commands: List[BotCommand] = None,
|
commands: Optional[List[BotCommand]] = None,
|
||||||
photo: Dict[str, Any] = None
|
photo: Optional[PhotoAttachmentRequestPayload] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -28,13 +42,27 @@ class ChangeInfo(BaseConnection):
|
|||||||
self.commands = commands
|
self.commands = commands
|
||||||
self.photo = photo
|
self.photo = photo
|
||||||
|
|
||||||
async def request(self) -> User:
|
async def fetch(self) -> User:
|
||||||
json = {}
|
|
||||||
|
|
||||||
if self.name: json['name'] = self.name
|
"""Отправляет запрос на изменение информации о боте.
|
||||||
if self.description: json['description'] = self.description
|
|
||||||
if self.commands: json['commands'] = [command.model_dump() for command in self.commands]
|
Returns:
|
||||||
if self.photo: json['photo'] = self.photo
|
User: Объект с обновленными данными бота
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
if self.name:
|
||||||
|
json['name'] = self.name
|
||||||
|
if self.description:
|
||||||
|
json['description'] = self.description
|
||||||
|
if self.commands:
|
||||||
|
json['commands'] = [command.model_dump() for command in self.commands]
|
||||||
|
if self.photo:
|
||||||
|
json['photo'] = self.photo.model_dump()
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.PATCH,
|
method=HTTPMethod.PATCH,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..methods.types.deleted_bot_from_chat import DeletedBotFromChat
|
from ..methods.types.deleted_bot_from_chat import DeletedBotFromChat
|
||||||
from ..methods.types.deleted_message import DeletedMessage
|
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -13,6 +13,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class DeleteMeFromMessage(BaseConnection):
|
class DeleteMeFromMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для удаления бота из участников указанного чата.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата, из которого нужно удалить бота.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -21,7 +30,17 @@ class DeleteMeFromMessage(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> DeletedBotFromChat:
|
async def fetch(self) -> DeletedBotFromChat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отправляет DELETE-запрос для удаления бота из чата.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeletedBotFromChat: Результат операции удаления.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.DELETE,
|
method=HTTPMethod.DELETE,
|
||||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class DeleteChat(BaseConnection):
|
class DeleteChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для удаления чата через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата, который необходимо удалить.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -20,7 +29,18 @@ class DeleteChat(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> DeletedChat:
|
async def fetch(self) -> DeletedChat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отправляет DELETE-запрос для удаления указанного чата.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeletedChat: Результат операции удаления чата.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.DELETE,
|
method=HTTPMethod.DELETE,
|
||||||
path=ApiPath.CHATS.value + '/' + str(self.chat_id),
|
path=ApiPath.CHATS.value + '/' + str(self.chat_id),
|
||||||
|
|||||||
@@ -12,6 +12,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class DeleteMessage(BaseConnection):
|
class DeleteMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для удаления сообщения через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
message_id (str): Идентификатор сообщения, которое нужно удалить.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -20,7 +29,20 @@ class DeleteMessage(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
|
|
||||||
async def request(self) -> DeletedMessage:
|
async def fetch(self) -> DeletedMessage:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет DELETE-запрос для удаления сообщения.
|
||||||
|
|
||||||
|
Использует параметр message_id для идентификации сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeletedMessage: Результат операции удаления сообщения.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['message_id'] = self.message_id
|
params['message_id'] = self.message_id
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from ..methods.types.deleted_pin_message import DeletedPinMessage
|
|||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -12,15 +13,35 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class DeletePinMessage(BaseConnection):
|
class DeletePinMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для удаления закреплённого сообщения в чате через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата, из которого нужно удалить закреплённое сообщение.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: str,
|
chat_id: int,
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> DeletedPinMessage:
|
async def fetch(self) -> DeletedPinMessage:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет DELETE-запрос для удаления закреплённого сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DeletedPinMessage: Результат операции удаления закреплённого сообщения.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.DELETE,
|
method=HTTPMethod.DELETE,
|
||||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
||||||
|
|||||||
46
maxapi/methods/download_media.py
Normal file
46
maxapi/methods/download_media.py
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class DownloadMedia(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для скачивания медиафайлов.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
media_url (str): Ссылка на медиа.
|
||||||
|
media_token (str): Токен медиа.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: 'Bot',
|
||||||
|
path: str,
|
||||||
|
media_url: str,
|
||||||
|
media_token: str
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
self.path = path
|
||||||
|
self.media_url = media_url
|
||||||
|
self.media_token = media_token
|
||||||
|
|
||||||
|
async def fetch(self) -> int:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для скачивания медиафайла
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: Код операции.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await super().download_file(
|
||||||
|
path=self.path,
|
||||||
|
url=self.media_url,
|
||||||
|
token=self.media_token
|
||||||
|
)
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
from typing import Any, Dict, List, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
|
|
||||||
|
from ..exceptions.max import MaxIconParamsException
|
||||||
|
|
||||||
from ..types.attachments.image import PhotoAttachmentRequestPayload
|
from ..types.attachments.image import PhotoAttachmentRequestPayload
|
||||||
from ..types.chats import Chat
|
from ..types.chats import Chat
|
||||||
from ..types.command import Command
|
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
@@ -21,14 +22,27 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class EditChat(BaseConnection):
|
class EditChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для редактирования информации о чате через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата для редактирования.
|
||||||
|
icon (PhotoAttachmentRequestPayload, optional): Новый значок (иконка) чата.
|
||||||
|
title (str, optional): Новое название чата.
|
||||||
|
pin (str, optional): Идентификатор закреплённого сообщения.
|
||||||
|
notify (bool, optional): Включение или отключение уведомлений (по умолчанию True).
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int,
|
chat_id: int,
|
||||||
icon: PhotoAttachmentRequestPayload = None,
|
icon: Optional[PhotoAttachmentRequestPayload] = None,
|
||||||
title: str = None,
|
title: Optional[str] = None,
|
||||||
pin: str = None,
|
pin: Optional[str] = None,
|
||||||
notify: bool = True,
|
notify: Optional[bool] = None,
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
@@ -37,25 +51,44 @@ class EditChat(BaseConnection):
|
|||||||
self.pin = pin
|
self.pin = pin
|
||||||
self.notify = notify
|
self.notify = notify
|
||||||
|
|
||||||
async def request(self) -> Chat:
|
async def fetch(self) -> Chat:
|
||||||
json = {}
|
|
||||||
|
"""
|
||||||
|
Выполняет PATCH-запрос для обновления параметров чата.
|
||||||
|
|
||||||
|
Валидация:
|
||||||
|
- Проверяется, что в `icon` атрибуты модели взаимоисключающие (в модели должно быть ровно 2 поля с None).
|
||||||
|
- Если условие не выполнено, логируется ошибка и запрос не отправляется.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Chat: Обновлённый объект чата.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
if self.icon:
|
if self.icon:
|
||||||
dump = self.icon.model_dump()
|
dump = self.icon.model_dump()
|
||||||
counter = Counter(dump.values())
|
counter = Counter(dump.values())
|
||||||
|
|
||||||
if not None in counter or \
|
if None not in counter or \
|
||||||
not counter[None] == 2:
|
not counter[None] == 2:
|
||||||
return logger.error(
|
|
||||||
|
raise MaxIconParamsException(
|
||||||
'Все атрибуты модели Icon являются взаимоисключающими | '
|
'Все атрибуты модели Icon являются взаимоисключающими | '
|
||||||
'https://dev.max.ru/docs-api/methods/PATCH/chats/-chatId-'
|
'https://dev.max.ru/docs-api/methods/PATCH/chats/-chatId-'
|
||||||
)
|
)
|
||||||
|
|
||||||
json['icon'] = dump
|
json['icon'] = dump
|
||||||
|
|
||||||
if self.title: json['title'] = self.title
|
if self.title:
|
||||||
if self.pin: json['pin'] = self.pin
|
json['title'] = self.title
|
||||||
if self.notify: json['notify'] = self.notify
|
if self.pin:
|
||||||
|
json['pin'] = self.pin
|
||||||
|
if self.notify:
|
||||||
|
json['notify'] = self.notify
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.PATCH,
|
method=HTTPMethod.PATCH,
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
from typing import List, TYPE_CHECKING
|
from __future__ import annotations
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from ..types.errors import Error
|
||||||
|
from typing import Any, Dict, List, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from ..utils.message import process_input_media
|
||||||
|
|
||||||
from .types.edited_message import EditedMessage
|
from .types.edited_message import EditedMessage
|
||||||
from ..types.message import NewMessageLink
|
from ..types.message import NewMessageLink
|
||||||
from ..types.attachments.attachment import Attachment
|
from ..types.attachments.attachment import Attachment
|
||||||
|
from ..types.input_media import InputMedia, InputMediaBuffer
|
||||||
from ..enums.parse_mode import ParseMode
|
from ..enums.parse_mode import ParseMode
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
from ..loggers import logger_bot
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -14,15 +23,29 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class EditMessage(BaseConnection):
|
class EditMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для редактирования существующего сообщения через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
message_id (str): Идентификатор сообщения для редактирования.
|
||||||
|
text (str, optional): Новый текст сообщения.
|
||||||
|
attachments (List[Attachment | InputMedia | InputMediaBuffer], optional): Список вложений для сообщения.
|
||||||
|
link (NewMessageLink, optional): Связь с другим сообщением (ответ или пересылка).
|
||||||
|
notify (bool, optional): Отправлять ли уведомление о сообщении (по умолчанию True).
|
||||||
|
parse_mode (ParseMode, optional): Формат разметки текста (markdown, html и т.д.).
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: Bot,
|
||||||
message_id: str,
|
message_id: str,
|
||||||
text: str = None,
|
text: Optional[str] = None,
|
||||||
attachments: List['Attachment'] = None,
|
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
|
||||||
link: 'NewMessageLink' = None,
|
link: Optional[NewMessageLink] = None,
|
||||||
notify: bool = True,
|
notify: Optional[bool] = None,
|
||||||
parse_mode: ParseMode = None
|
parse_mode: Optional[ParseMode] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
@@ -32,24 +55,69 @@ class EditMessage(BaseConnection):
|
|||||||
self.notify = notify
|
self.notify = notify
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
|
|
||||||
async def request(self) -> EditedMessage:
|
async def fetch(self) -> Optional[EditedMessage | Error]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет PUT-запрос для обновления сообщения.
|
||||||
|
|
||||||
|
Формирует тело запроса на основе переданных параметров и отправляет запрос к API.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
EditedMessage: Обновлённое сообщение.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
json = {}
|
json: Dict[str, Any] = {'attachments': []}
|
||||||
|
|
||||||
params['message_id'] = self.message_id
|
params['message_id'] = self.message_id
|
||||||
|
|
||||||
if not self.text is None: json['text'] = self.text
|
if self.text is not None:
|
||||||
if self.attachments: json['attachments'] = \
|
json['text'] = self.text
|
||||||
[att.model_dump() for att in self.attachments]
|
|
||||||
if not self.link is None: json['link'] = self.link.model_dump()
|
|
||||||
if not self.notify is None: json['notify'] = self.notify
|
|
||||||
if not self.parse_mode is None: json['format'] = self.parse_mode.value
|
|
||||||
|
|
||||||
return await super().request(
|
if self.attachments:
|
||||||
|
|
||||||
|
for att in self.attachments:
|
||||||
|
|
||||||
|
if isinstance(att, InputMedia) or isinstance(att, InputMediaBuffer):
|
||||||
|
input_media = await process_input_media(
|
||||||
|
base_connection=self,
|
||||||
|
bot=self.bot,
|
||||||
|
att=att
|
||||||
|
)
|
||||||
|
json['attachments'].append(
|
||||||
|
input_media.model_dump()
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
json['attachments'].append(att.model_dump())
|
||||||
|
|
||||||
|
if self.link is not None:
|
||||||
|
json['link'] = self.link.model_dump()
|
||||||
|
if self.notify is not None:
|
||||||
|
json['notify'] = self.notify
|
||||||
|
if self.parse_mode is not None:
|
||||||
|
json['format'] = self.parse_mode.value
|
||||||
|
|
||||||
|
await asyncio.sleep(self.bot.after_input_media_delay)
|
||||||
|
|
||||||
|
response = None
|
||||||
|
for attempt in range(self.ATTEMPTS_COUNT):
|
||||||
|
response = await super().request(
|
||||||
method=HTTPMethod.PUT,
|
method=HTTPMethod.PUT,
|
||||||
path=ApiPath.MESSAGES,
|
path=ApiPath.MESSAGES,
|
||||||
model=EditedMessage,
|
model=EditedMessage,
|
||||||
params=params,
|
params=params,
|
||||||
json=json
|
json=json
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(response, Error):
|
||||||
|
if response.raw.get('code') == 'attachment.not.ready':
|
||||||
|
logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду {self.RETRY_DELAY} секунды')
|
||||||
|
await asyncio.sleep(self.RETRY_DELAY)
|
||||||
|
continue
|
||||||
|
|
||||||
|
return response
|
||||||
|
return response
|
||||||
@@ -14,6 +14,14 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class GetChatById(BaseConnection):
|
class GetChatById(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения информации о чате по его идентификатору.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
id (int): Идентификатор чата.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -22,7 +30,18 @@ class GetChatById(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
||||||
async def request(self) -> Chat:
|
async def fetch(self) -> Chat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения данных чата.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Chat: Объект чата с полной информацией.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.CHATS.value + '/' + str(self.id),
|
path=ApiPath.CHATS.value + '/' + str(self.id),
|
||||||
|
|||||||
@@ -15,6 +15,18 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class GetChatByLink(BaseConnection):
|
class GetChatByLink(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения информации о чате по ссылке.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
link (str): Ссылка на чат (с содержанием @ или без).
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
link (list[str]): Список валидных частей ссылки.
|
||||||
|
PATTERN_LINK (str): Регулярное выражение для парсинга ссылки.
|
||||||
|
"""
|
||||||
|
|
||||||
PATTERN_LINK = r'@?[a-zA-Z]+[a-zA-Z0-9-_]*'
|
PATTERN_LINK = r'@?[a-zA-Z]+[a-zA-Z0-9-_]*'
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -28,7 +40,18 @@ class GetChatByLink(BaseConnection):
|
|||||||
if not self.link:
|
if not self.link:
|
||||||
return
|
return
|
||||||
|
|
||||||
async def request(self) -> Chat:
|
async def fetch(self) -> Chat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения данных чата по ссылке.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Chat: Объект с информацией о чате.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.CHATS.value + '/' + self.link[-1],
|
path=ApiPath.CHATS.value + '/' + self.link[-1],
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from ..types.chats import Chats
|
from ..types.chats import Chats
|
||||||
|
|
||||||
@@ -15,17 +13,43 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetChats(BaseConnection):
|
class GetChats(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения списка чатов через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
count (int, optional): Максимальное количество чатов для получения. По умолчанию 50.
|
||||||
|
marker (int, optional): Маркер для постраничной навигации. По умолчанию None.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
count (int): Количество чатов для запроса.
|
||||||
|
marker (int | None): Маркер для пагинации.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
count: int = 50,
|
count: int = 50,
|
||||||
marker: int = None
|
marker: Optional[int] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.count = count
|
self.count = count
|
||||||
self.marker = marker
|
self.marker = marker
|
||||||
|
|
||||||
async def request(self) -> Chats:
|
async def fetch(self) -> Chats:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения списка чатов.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Chats: Объект с данными по списку чатов.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['count'] = self.count
|
params['count'] = self.count
|
||||||
|
|||||||
@@ -14,6 +14,18 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class GetListAdminChat(BaseConnection):
|
class GetListAdminChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения списка администраторов чата через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -22,7 +34,18 @@ class GetListAdminChat(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> GettedListAdminChat:
|
async def fetch(self) -> GettedListAdminChat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения списка администраторов указанного чата.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedListAdminChat: Объект с информацией о администраторах чата.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,
|
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..types.chats import Chats
|
|
||||||
|
|
||||||
from ..types.users import User
|
from ..types.users import User
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
@@ -17,10 +13,29 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetMe(BaseConnection):
|
class GetMe(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения информации о боте.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, bot: 'Bot'):
|
def __init__(self, bot: 'Bot'):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
async def request(self) -> User:
|
async def fetch(self) -> User:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения данных о боте.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User: Объект пользователя с полной информацией.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.ME,
|
path=ApiPath.ME,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..types.chats import ChatMember
|
from ..types.chats import ChatMember
|
||||||
@@ -15,6 +13,19 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetMeFromChat(BaseConnection):
|
class GetMeFromChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения информации о текущем боте в конкретном чате.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -23,7 +34,18 @@ class GetMeFromChat(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> ChatMember:
|
async def fetch(self) -> ChatMember:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения информации о боте в указанном чате.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ChatMember: Информация о боте как участнике чата.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, List, Optional
|
||||||
|
|
||||||
from ..methods.types.getted_members_chat import GettedMembersChat
|
from ..methods.types.getted_members_chat import GettedMembersChat
|
||||||
|
|
||||||
@@ -14,13 +14,31 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class GetMembersChat(BaseConnection):
|
class GetMembersChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения списка участников чата через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_ids (List[str], optional): Список ID пользователей для фильтрации. По умолчанию None.
|
||||||
|
marker (int, optional): Маркер для пагинации (начальная позиция). По умолчанию None.
|
||||||
|
count (int, optional): Максимальное количество участников для получения. По умолчанию None.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_ids (List[int] | None): Список ID пользователей для фильтра.
|
||||||
|
marker (int | None): Позиция для пагинации.
|
||||||
|
count (int | None): Максимальное количество участников.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int,
|
chat_id: int,
|
||||||
user_ids: List[str] = None,
|
user_ids: Optional[List[int]] = None,
|
||||||
marker: int = None,
|
marker: Optional[int] = None,
|
||||||
count: int = None,
|
count: Optional[int] = None,
|
||||||
|
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
@@ -29,12 +47,29 @@ class GetMembersChat(BaseConnection):
|
|||||||
self.marker = marker
|
self.marker = marker
|
||||||
self.count = count
|
self.count = count
|
||||||
|
|
||||||
async def request(self) -> GettedMembersChat:
|
async def fetch(self) -> GettedMembersChat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения участников чата с опциональной фильтрацией.
|
||||||
|
|
||||||
|
Формирует параметры запроса с учётом фильтров и передаёт их базовому методу.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedMembersChat: Объект с данными по участникам чата.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
if self.user_ids: params['user_ids'] = ','.join(self.user_ids)
|
if self.user_ids:
|
||||||
if self.marker: params['marker'] = self.marker
|
params['user_ids'] = ','.join([str(user_id) for user_id in self.user_ids])
|
||||||
if self.count: params['marker'] = self.count
|
|
||||||
|
if self.marker:
|
||||||
|
params['marker'] = self.marker
|
||||||
|
if self.count:
|
||||||
|
params['marker'] = self.count
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
|
|||||||
49
maxapi/methods/get_message.py
Normal file
49
maxapi/methods/get_message.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
|
from ..types.message import Message
|
||||||
|
from ..enums.http_method import HTTPMethod
|
||||||
|
from ..enums.api_path import ApiPath
|
||||||
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class GetMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения сообщения.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
message_id (str, optional): ID сообщения (mid), чтобы получить одно сообщение в чате.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: 'Bot',
|
||||||
|
message_id: Optional[str] = None,
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
self.message_id = message_id
|
||||||
|
|
||||||
|
async def fetch(self) -> Message:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Message: Объект с полученным сообщением.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
return await super().request(
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
path=ApiPath.MESSAGES + '/' + self.message_id,
|
||||||
|
model=Message,
|
||||||
|
params=self.bot.params
|
||||||
|
)
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING, List, Optional, Union
|
||||||
|
|
||||||
from ..types.message import Messages
|
from ..types.message import Messages
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
@@ -14,13 +12,34 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetMessages(BaseConnection):
|
class GetMessages(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения сообщений из чата через API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
message_ids (List[str], optional): Список идентификаторов сообщений для выборки. По умолчанию None.
|
||||||
|
from_time (datetime | int, optional): Временная метка начала выборки сообщений (timestamp или datetime). По умолчанию None.
|
||||||
|
to_time (datetime | int, optional): Временная метка конца выборки сообщений (timestamp или datetime). По умолчанию None.
|
||||||
|
count (int, optional): Максимальное количество сообщений для получения. По умолчанию 50.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
message_ids (List[str] | None): Фильтр по идентификаторам сообщений.
|
||||||
|
from_time (datetime | int | None): Начальная временная метка.
|
||||||
|
to_time (datetime | int | None): Конечная временная метка.
|
||||||
|
count (int): Максимальное число сообщений.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int,
|
chat_id: Optional[int] = None,
|
||||||
message_ids: List[str] = None,
|
message_ids: Optional[List[str]] = None,
|
||||||
from_time: datetime | int = None,
|
from_time: Optional[Union[datetime, int]] = None,
|
||||||
to_time: datetime | int = None,
|
to_time: Optional[Union[datetime, int]] = None,
|
||||||
count: int = 50,
|
count: int = 50,
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
@@ -30,10 +49,24 @@ class GetMessages(BaseConnection):
|
|||||||
self.to_time = to_time
|
self.to_time = to_time
|
||||||
self.count = count
|
self.count = count
|
||||||
|
|
||||||
async def request(self) -> Messages:
|
async def fetch(self) -> Messages:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения сообщений с учётом параметров фильтрации.
|
||||||
|
|
||||||
|
Преобразует datetime в UNIX timestamp при необходимости.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Messages: Объект с полученными сообщениями.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
if self.chat_id: params['chat_id'] = self.chat_id
|
if self.chat_id:
|
||||||
|
params['chat_id'] = self.chat_id
|
||||||
|
|
||||||
if self.message_ids:
|
if self.message_ids:
|
||||||
params['message_ids'] = ','.join(self.message_ids)
|
params['message_ids'] = ','.join(self.message_ids)
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import TYPE_CHECKING, List
|
|
||||||
|
|
||||||
from .types.getted_pineed_message import GettedPin
|
from .types.getted_pineed_message import GettedPin
|
||||||
|
|
||||||
@@ -15,6 +12,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetPinnedMessage(BaseConnection):
|
class GetPinnedMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения закреплённого сообщения в указанном чате.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -23,7 +29,18 @@ class GetPinnedMessage(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
|
||||||
async def request(self) -> GettedPin:
|
async def fetch(self) -> GettedPin:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения закреплённого сообщения.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedPin: Объект с информацией о закреплённом сообщении.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
||||||
|
|||||||
44
maxapi/methods/get_subscriptions.py
Normal file
44
maxapi/methods/get_subscriptions.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ..methods.types.getted_subscriptions import GettedSubscriptions
|
||||||
|
|
||||||
|
from ..enums.http_method import HTTPMethod
|
||||||
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class GetSubscriptions(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Если ваш бот получает данные через WebHook, этот класс возвращает список всех подписок.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: 'Bot',
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
async def fetch(self) -> GettedSubscriptions:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отправляет запрос на получение списка всех подписок.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedSubscriptions: Объект со списком подписок
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
return await super().request(
|
||||||
|
method=HTTPMethod.GET,
|
||||||
|
path=ApiPath.SUBSCRIPTIONS,
|
||||||
|
model=GettedSubscriptions,
|
||||||
|
params=self.bot.params
|
||||||
|
)
|
||||||
@@ -1,16 +1,9 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING, Dict
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import TYPE_CHECKING, List
|
|
||||||
|
|
||||||
from ..types.updates import UpdateUnion
|
|
||||||
|
|
||||||
from ..methods.types.getted_updates import process_update_request
|
|
||||||
|
|
||||||
|
|
||||||
from ..types.message import Messages
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -19,15 +12,41 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetUpdates(BaseConnection):
|
class GetUpdates(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения обновлений (updates) из API.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
limit (int, optional): Максимальное количество обновлений для получения. По умолчанию 100.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
limit (int): Лимит на количество обновлений.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: Bot,
|
||||||
limit: int = 100,
|
limit: int = 100,
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
|
|
||||||
async def request(self) -> UpdateUnion:
|
async def fetch(self) -> Dict:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения обновлений с указанным лимитом.
|
||||||
|
|
||||||
|
Возвращает необработанный JSON с обновлениями.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
UpdateUnion: Объединённый тип данных обновлений.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['limit'] = self.limit
|
params['limit'] = self.limit
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..methods.types.getted_upload_url import GettedUploadUrl
|
from ..methods.types.getted_upload_url import GettedUploadUrl
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
from ..enums.upload_type import UploadType
|
from ..enums.upload_type import UploadType
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -14,6 +16,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetUploadURL(BaseConnection):
|
class GetUploadURL(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения URL загрузки файла определённого типа.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
type (UploadType): Тип загружаемого файла (например, image, video и т.д.).
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -22,7 +33,20 @@ class GetUploadURL(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.type = type
|
self.type = type
|
||||||
|
|
||||||
async def request(self) -> GettedUploadUrl:
|
async def fetch(self) -> GettedUploadUrl:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет POST-запрос для получения URL загрузки файла.
|
||||||
|
|
||||||
|
Возвращает объект с данными URL.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedUploadUrl: Результат с URL для загрузки.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['type'] = self.type.value
|
params['type'] = self.type.value
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
from typing import List, TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from ..types.attachments.video import Video
|
from ..types.attachments.video import Video
|
||||||
|
|
||||||
from .types.edited_message import EditedMessage
|
|
||||||
from ..types.message import NewMessageLink
|
|
||||||
from ..types.attachments.attachment import Attachment
|
|
||||||
from ..enums.parse_mode import ParseMode
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -16,6 +13,15 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class GetVideo(BaseConnection):
|
class GetVideo(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для получения информации о видео по его токену.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
video_token (str): Токен видео для запроса.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -24,7 +30,17 @@ class GetVideo(BaseConnection):
|
|||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.video_token = video_token
|
self.video_token = video_token
|
||||||
|
|
||||||
async def request(self) -> Video:
|
async def fetch(self) -> Video:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет GET-запрос для получения данных видео по токену.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Video: Объект с информацией о видео.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.GET,
|
method=HTTPMethod.GET,
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import TYPE_CHECKING, List
|
|
||||||
|
|
||||||
from .types.pinned_message import PinnedMessage
|
from .types.pinned_message import PinnedMessage
|
||||||
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -15,20 +13,50 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class PinMessage(BaseConnection):
|
class PinMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для закрепления сообщения в чате.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата, в котором закрепляется сообщение.
|
||||||
|
message_id (str): Идентификатор сообщения для закрепления.
|
||||||
|
notify (bool, optional): Отправлять ли уведомление о закреплении (по умолчанию True).
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
message_id (str): Идентификатор закрепляемого сообщения.
|
||||||
|
notify (bool): Флаг отправки уведомления.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int,
|
chat_id: int,
|
||||||
message_id: str,
|
message_id: str,
|
||||||
notify: bool = True
|
notify: Optional[bool] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.message_id = message_id
|
self.message_id = message_id
|
||||||
self.notify = notify
|
self.notify = notify
|
||||||
|
|
||||||
async def request(self) -> PinnedMessage:
|
async def fetch(self) -> PinnedMessage:
|
||||||
json = {}
|
|
||||||
|
"""
|
||||||
|
Выполняет PUT-запрос для закрепления сообщения в чате.
|
||||||
|
|
||||||
|
Формирует тело запроса с ID сообщения и флагом уведомления.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
PinnedMessage: Объект с информацией о закреплённом сообщении.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
json['message_id'] = self.message_id
|
json['message_id'] = self.message_id
|
||||||
json['notify'] = self.notify
|
json['notify'] = self.notify
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .types.removed_admin import RemovedAdmin
|
from .types.removed_admin import RemovedAdmin
|
||||||
@@ -16,6 +14,20 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class RemoveAdmin(BaseConnection):
|
class RemoveAdmin(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для отмены прав администратора в чате.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_id (int): Идентификатор пользователя.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_id (int): Идентификатор пользователя.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -26,10 +38,21 @@ class RemoveAdmin(BaseConnection):
|
|||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
|
|
||||||
async def request(self) -> RemovedAdmin:
|
async def fetch(self) -> RemovedAdmin:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет DELETE-запрос для отмены прав администратора в чате.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RemovedAdmin: Объект с результатом отмены прав администратора.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.DELETE,
|
method=HTTPMethod.DELETE,
|
||||||
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + \
|
path=ApiPath.CHATS + '/' + str(self.chat_id) + \
|
||||||
ApiPath.MEMBERS + ApiPath.ADMINS + '/' + str(self.user_id),
|
ApiPath.MEMBERS + ApiPath.ADMINS + '/' + str(self.user_id),
|
||||||
model=RemovedAdmin,
|
model=RemovedAdmin,
|
||||||
params=self.bot.params,
|
params=self.bot.params,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .types.removed_member_chat import RemovedMemberChat
|
from .types.removed_member_chat import RemovedMemberChat
|
||||||
|
|
||||||
@@ -14,6 +14,22 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class RemoveMemberChat(BaseConnection):
|
class RemoveMemberChat(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для удаления участника из чата с опцией блокировки.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_id (int): Идентификатор пользователя, которого необходимо удалить.
|
||||||
|
block (bool, optional): Блокировать пользователя после удаления. По умолчанию False.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user_id (int): Идентификатор пользователя.
|
||||||
|
block (bool): Флаг блокировки пользователя.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
@@ -27,7 +43,20 @@ class RemoveMemberChat(BaseConnection):
|
|||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.block = block
|
self.block = block
|
||||||
|
|
||||||
async def request(self) -> RemovedMemberChat:
|
async def fetch(self) -> RemovedMemberChat:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Выполняет DELETE-запрос для удаления пользователя из чата.
|
||||||
|
|
||||||
|
Параметр `block` определяет, будет ли пользователь заблокирован после удаления.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
RemovedMemberChat: Результат удаления участника.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['chat_id'] = self.chat_id
|
params['chat_id'] = self.chat_id
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
|
|
||||||
|
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
|
|
||||||
from ..enums.sender_action import SenderAction
|
|
||||||
from ..methods.types.sended_action import SendedAction
|
from ..methods.types.sended_action import SendedAction
|
||||||
|
|
||||||
from .types.sended_message import SendedMessage
|
from ..enums.sender_action import SenderAction
|
||||||
from ..types.message import NewMessageLink
|
|
||||||
from ..types.attachments.attachment import Attachment
|
|
||||||
from ..enums.parse_mode import ParseMode
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -19,18 +16,44 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class SendAction(BaseConnection):
|
class SendAction(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для отправки действия пользователя (например, индикатора печати) в чат.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int | None): Идентификатор чата. Если None, действие не отправляется.
|
||||||
|
action (SenderAction, optional): Тип действия. По умолчанию SenderAction.TYPING_ON.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
chat_id (int | None): Идентификатор чата.
|
||||||
|
action (SenderAction): Тип действия.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int = None,
|
chat_id: Optional[int] = None,
|
||||||
action: SenderAction = SenderAction.TYPING_ON
|
action: SenderAction = SenderAction.TYPING_ON
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.action = action
|
self.action = action
|
||||||
|
|
||||||
async def request(self) -> SendedAction:
|
async def fetch(self) -> SendedAction:
|
||||||
json = {}
|
|
||||||
|
"""
|
||||||
|
Выполняет POST-запрос для отправки действия в указанный чат.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SendedAction: Результат выполнения запроса.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
json['action'] = self.action.value
|
json['action'] = self.action.value
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
||||||
from typing import List, TYPE_CHECKING
|
|
||||||
|
|
||||||
from ..methods.types.sended_callback import SendedCallback
|
from ..methods.types.sended_callback import SendedCallback
|
||||||
|
|
||||||
from .types.sended_message import SendedMessage
|
|
||||||
from ..types.attachments.attachment import Attachment
|
|
||||||
from ..enums.parse_mode import ParseMode
|
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
|
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
@@ -18,28 +15,59 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class SendCallback(BaseConnection):
|
class SendCallback(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для отправки callback-ответа с опциональным сообщением и уведомлением.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
callback_id (str): Идентификатор callback.
|
||||||
|
message (Message, optional): Сообщение для отправки в ответе.
|
||||||
|
notification (str, optional): Текст уведомления.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
callback_id (str): Идентификатор callback.
|
||||||
|
message (Message | None): Сообщение для отправки.
|
||||||
|
notification (str | None): Текст уведомления.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
callback_id: str,
|
callback_id: str,
|
||||||
message: 'Message' = None,
|
message: Optional[Message] = None,
|
||||||
notification: str = None
|
notification: Optional[str] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.callback_id = callback_id
|
self.callback_id = callback_id
|
||||||
self.message = message
|
self.message = message
|
||||||
self.notification = notification
|
self.notification = notification
|
||||||
|
|
||||||
async def request(self) -> SendedCallback:
|
async def fetch(self) -> SendedCallback:
|
||||||
try:
|
|
||||||
|
"""
|
||||||
|
Выполняет POST-запрос для отправки callback-ответа.
|
||||||
|
|
||||||
|
Возвращает результат отправки.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
SendedCallback: Объект с результатом отправки callback.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
params['callback_id'] = self.callback_id
|
params['callback_id'] = self.callback_id
|
||||||
|
|
||||||
json = {}
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
if self.message: json['message'] = self.message.model_dump()
|
if self.message:
|
||||||
if self.notification: json['notification'] = self.notification
|
json['message'] = self.message.model_dump()
|
||||||
|
if self.notification:
|
||||||
|
json['notification'] = self.notification
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.POST,
|
method=HTTPMethod.POST,
|
||||||
@@ -48,6 +76,3 @@ class SendCallback(BaseConnection):
|
|||||||
params=params,
|
params=params,
|
||||||
json=json
|
json=json
|
||||||
)
|
)
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
...
|
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
|
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import List, TYPE_CHECKING
|
from typing import Any, Dict, List, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from json import loads as json_loads
|
from ..utils.message import process_input_media
|
||||||
|
|
||||||
from ..enums.upload_type import UploadType
|
|
||||||
|
|
||||||
from ..types.attachments.upload import AttachmentPayload, AttachmentUpload
|
|
||||||
from ..types.errors import Error
|
|
||||||
from .types.sended_message import SendedMessage
|
from .types.sended_message import SendedMessage
|
||||||
|
from ..types.errors import Error
|
||||||
from ..types.message import NewMessageLink
|
from ..types.message import NewMessageLink
|
||||||
from ..types.input_media import InputMedia
|
from ..types.input_media import InputMedia, InputMediaBuffer
|
||||||
from ..types.attachments.attachment import Attachment
|
from ..types.attachments.attachment import Attachment
|
||||||
|
|
||||||
from ..enums.parse_mode import ParseMode
|
from ..enums.parse_mode import ParseMode
|
||||||
from ..enums.http_method import HTTPMethod
|
from ..enums.http_method import HTTPMethod
|
||||||
from ..enums.api_path import ApiPath
|
from ..enums.api_path import ApiPath
|
||||||
from ..connection.base import BaseConnection
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
from ..loggers import logger_bot
|
from ..loggers import logger_bot
|
||||||
|
|
||||||
|
|
||||||
@@ -24,93 +23,100 @@ if TYPE_CHECKING:
|
|||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
class UploadResponse:
|
|
||||||
token: str = None
|
|
||||||
|
|
||||||
|
|
||||||
class SendMessage(BaseConnection):
|
class SendMessage(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Класс для отправки сообщения в чат или пользователю с поддержкой вложений и форматирования.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
chat_id (int, optional): Идентификатор чата, куда отправлять сообщение.
|
||||||
|
user_id (int, optional): Идентификатор пользователя, если нужно отправить личное сообщение.
|
||||||
|
text (str, optional): Текст сообщения.
|
||||||
|
attachments (List[Attachment | InputMedia | InputMediaBuffer], optional): Список вложений к сообщению.
|
||||||
|
link (NewMessageLink, optional): Связь с другим сообщением (например, ответ или пересылка).
|
||||||
|
notify (bool, optional): Отправлять ли уведомление о сообщении. По умолчанию True.
|
||||||
|
parse_mode (ParseMode, optional): Режим разбора текста (например, Markdown, HTML).
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
bot: 'Bot',
|
bot: 'Bot',
|
||||||
chat_id: int = None,
|
chat_id: Optional[int] = None,
|
||||||
user_id: int = None,
|
user_id: Optional[int] = None,
|
||||||
disable_link_preview: bool = False,
|
text: Optional[str] = None,
|
||||||
text: str = None,
|
attachments: Optional[List[Attachment | InputMedia | InputMediaBuffer]] = None,
|
||||||
attachments: List[Attachment | InputMedia] = None,
|
link: Optional[NewMessageLink] = None,
|
||||||
link: NewMessageLink = None,
|
notify: Optional[bool] = None,
|
||||||
notify: bool = True,
|
parse_mode: Optional[ParseMode] = None
|
||||||
parse_mode: ParseMode = None
|
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
self.user_id = user_id
|
self.user_id = user_id
|
||||||
self.disable_link_preview = disable_link_preview
|
|
||||||
self.text = text
|
self.text = text
|
||||||
self.attachments = attachments
|
self.attachments = attachments
|
||||||
self.link = link
|
self.link = link
|
||||||
self.notify = notify
|
self.notify = notify
|
||||||
self.parse_mode = parse_mode
|
self.parse_mode = parse_mode
|
||||||
|
|
||||||
async def __process_input_media(
|
async def fetch(self) -> Optional[SendedMessage | Error]:
|
||||||
self,
|
|
||||||
att: InputMedia
|
|
||||||
):
|
|
||||||
upload = await self.bot.get_upload_url(att.type)
|
|
||||||
|
|
||||||
upload_file_response = await self.upload_file(
|
"""
|
||||||
url=upload.url,
|
Отправляет сообщение с вложениями (если есть), с обработкой задержки готовности вложений.
|
||||||
path=att.path,
|
|
||||||
type=att.type
|
|
||||||
)
|
|
||||||
|
|
||||||
if att.type in (UploadType.VIDEO, UploadType.AUDIO):
|
Возвращает результат отправки или ошибку.
|
||||||
token = upload.token
|
|
||||||
|
|
||||||
elif att.type == UploadType.FILE:
|
Возвращаемое значение:
|
||||||
json_r = json_loads(upload_file_response)
|
SendedMessage или Error
|
||||||
token = json_r['token']
|
"""
|
||||||
|
|
||||||
elif att.type == UploadType.IMAGE:
|
if self.bot is None:
|
||||||
json_r = json_loads(upload_file_response)
|
raise RuntimeError('Bot не инициализирован')
|
||||||
json_r_keys = list(json_r['photos'].keys())
|
|
||||||
token = json_r['photos'][json_r_keys[0]]['token']
|
|
||||||
|
|
||||||
return AttachmentUpload(
|
|
||||||
type=att.type,
|
|
||||||
payload=AttachmentPayload(
|
|
||||||
token=token
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def request(self) -> SendedMessage:
|
|
||||||
params = self.bot.params.copy()
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
json = {'attachments': []}
|
json: Dict[str, Any] = {'attachments': []}
|
||||||
|
|
||||||
if self.chat_id: params['chat_id'] = self.chat_id
|
if self.chat_id:
|
||||||
elif self.user_id: params['user_id'] = self.user_id
|
params['chat_id'] = self.chat_id
|
||||||
|
elif self.user_id:
|
||||||
|
params['user_id'] = self.user_id
|
||||||
|
|
||||||
json['text'] = self.text
|
json['text'] = self.text
|
||||||
json['disable_link_preview'] = str(self.disable_link_preview).lower()
|
|
||||||
|
HAS_INPUT_MEDIA = False
|
||||||
|
|
||||||
if self.attachments:
|
if self.attachments:
|
||||||
|
|
||||||
for att in self.attachments:
|
for att in self.attachments:
|
||||||
|
|
||||||
if isinstance(att, InputMedia):
|
if isinstance(att, (InputMedia, InputMediaBuffer)):
|
||||||
input_media = await self.__process_input_media(att)
|
HAS_INPUT_MEDIA = True
|
||||||
|
|
||||||
|
input_media = await process_input_media(
|
||||||
|
base_connection=self,
|
||||||
|
bot=self.bot,
|
||||||
|
att=att
|
||||||
|
)
|
||||||
json['attachments'].append(
|
json['attachments'].append(
|
||||||
input_media.model_dump()
|
input_media.model_dump()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
json['attachments'].append(att.model_dump())
|
json['attachments'].append(att.model_dump())
|
||||||
|
|
||||||
if not self.link is None: json['link'] = self.link.model_dump()
|
if self.link is not None:
|
||||||
if not self.notify is None: json['notify'] = self.notify
|
json['link'] = self.link.model_dump()
|
||||||
if not self.parse_mode is None: json['format'] = self.parse_mode.value
|
|
||||||
|
json['notify'] = self.notify
|
||||||
|
|
||||||
|
if self.parse_mode is not None:
|
||||||
|
json['format'] = self.parse_mode.value
|
||||||
|
|
||||||
|
if HAS_INPUT_MEDIA:
|
||||||
|
await asyncio.sleep(self.bot.after_input_media_delay)
|
||||||
|
|
||||||
response = None
|
response = None
|
||||||
for attempt in range(5):
|
for attempt in range(self.ATTEMPTS_COUNT):
|
||||||
response = await super().request(
|
response = await super().request(
|
||||||
method=HTTPMethod.POST,
|
method=HTTPMethod.POST,
|
||||||
path=ApiPath.MESSAGES,
|
path=ApiPath.MESSAGES,
|
||||||
@@ -121,8 +127,8 @@ class SendMessage(BaseConnection):
|
|||||||
|
|
||||||
if isinstance(response, Error):
|
if isinstance(response, Error):
|
||||||
if response.raw.get('code') == 'attachment.not.ready':
|
if response.raw.get('code') == 'attachment.not.ready':
|
||||||
logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду 2 секунды')
|
logger_bot.info(f'Ошибка при отправке загруженного медиа, попытка {attempt+1}, жду {self.RETRY_DELAY} секунды')
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(self.RETRY_DELAY)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
70
maxapi/methods/subscribe_webhook.py
Normal file
70
maxapi/methods/subscribe_webhook.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
||||||
|
|
||||||
|
from ..methods.types.subscribed import Subscribed
|
||||||
|
|
||||||
|
from ..enums.http_method import HTTPMethod
|
||||||
|
from ..enums.api_path import ApiPath
|
||||||
|
from ..enums.update import UpdateType
|
||||||
|
|
||||||
|
from ..connection.base import BaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class SubscribeWebhook(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Подписывает бота на получение обновлений через WebHook.
|
||||||
|
После вызова этого метода бот будет получать уведомления о новых событиях в чатах на указанный URL.
|
||||||
|
Ваш сервер должен прослушивать один из следующих портов: `80`, `8080`, `443`, `8443`, `16384`-`32383`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
url (str): URL HTTP(S)-эндпойнта вашего бота. Должен начинаться с http(s)://
|
||||||
|
update_types (Optional[List[str]]): Список типов обновлений, которые ваш бот хочет получать. Для полного списка типов см. объект
|
||||||
|
secret (str): От 5 до 256 символов. Cекрет, который должен быть отправлен в заголовке X-Max-Bot-Api-Secret в каждом запросе Webhook. Разрешены только символы A-Z, a-z, 0-9, и дефис. Заголовок рекомендован, чтобы запрос поступал из установленного веб-узла
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: 'Bot',
|
||||||
|
url: str,
|
||||||
|
update_types: Optional[List[UpdateType]] = None,
|
||||||
|
secret: Optional[str] = None
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
self.url = url
|
||||||
|
self.update_types = update_types
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
async def fetch(self) -> Subscribed:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отправляет запрос на подписку бота на получение обновлений через WebHook
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Subscribed: Объект с информацией об операции
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
json: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
json['url'] = self.url
|
||||||
|
|
||||||
|
if self.update_types:
|
||||||
|
json['update_types'] = self.update_types
|
||||||
|
|
||||||
|
if self.secret:
|
||||||
|
json['secret'] = self.secret
|
||||||
|
|
||||||
|
return await super().request(
|
||||||
|
method=HTTPMethod.POST,
|
||||||
|
path=ApiPath.SUBSCRIPTIONS,
|
||||||
|
model=Subscribed,
|
||||||
|
params=self.bot.params,
|
||||||
|
json=json
|
||||||
|
)
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...types.chats import ChatMember
|
|
||||||
|
|
||||||
|
|
||||||
class AddedListAdminChat(BaseModel):
|
class AddedListAdminChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при добавлении списка администраторов в чат.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...types.chats import ChatMember
|
|
||||||
|
|
||||||
|
|
||||||
class AddedMembersChat(BaseModel):
|
class AddedMembersChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при добавлении списка пользователей в чат.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class DeletedBotFromChat(BaseModel):
|
class DeletedBotFromChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при удалении бота из чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class DeletedChat(BaseModel):
|
class DeletedChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при удалении чата (?).
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class DeletedMessage(BaseModel):
|
class DeletedMessage(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при удалении сообщения.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class DeletedPinMessage(BaseModel):
|
class DeletedPinMessage(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при удалении закрепленного в чате сообщения.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class EditedMessage(BaseModel):
|
class EditedMessage(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при изменении сообщения.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -5,5 +5,14 @@ from ...types.chats import ChatMember
|
|||||||
|
|
||||||
|
|
||||||
class GettedListAdminChat(BaseModel):
|
class GettedListAdminChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API с полученным списком администраторов чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
members (List[ChatMember]): Список участников с правами администратора.
|
||||||
|
marker (Optional[int]): Маркер для постраничной навигации (если есть).
|
||||||
|
"""
|
||||||
|
|
||||||
members: List[ChatMember]
|
members: List[ChatMember]
|
||||||
marker: Optional[int] = None
|
marker: Optional[int] = None
|
||||||
@@ -5,5 +5,14 @@ from ...types.chats import ChatMember
|
|||||||
|
|
||||||
|
|
||||||
class GettedMembersChat(BaseModel):
|
class GettedMembersChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API с полученным списком участников чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
members (List[ChatMember]): Список участников с правами администратора.
|
||||||
|
marker (Optional[int]): Маркер для постраничной навигации (если есть).
|
||||||
|
"""
|
||||||
|
|
||||||
members: List[ChatMember]
|
members: List[ChatMember]
|
||||||
marker: Optional[int] = None
|
marker: Optional[int] = None
|
||||||
@@ -5,4 +5,12 @@ from ...types.message import Message
|
|||||||
|
|
||||||
|
|
||||||
class GettedPin(BaseModel):
|
class GettedPin(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API с информацией о закреплённом сообщении.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
message (Optional[Message]): Закреплённое сообщение, если оно есть.
|
||||||
|
"""
|
||||||
|
|
||||||
message: Optional[Message] = None
|
message: Optional[Message] = None
|
||||||
16
maxapi/methods/types/getted_subscriptions.py
Normal file
16
maxapi/methods/types/getted_subscriptions.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import List
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from ...types.subscription import Subscription
|
||||||
|
|
||||||
|
|
||||||
|
class GettedSubscriptions(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API с отправленным сообщением.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
message (Message): Объект отправленного сообщения.
|
||||||
|
"""
|
||||||
|
|
||||||
|
subscriptions: List[Subscription]
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ...utils.updates import enrich_event
|
||||||
|
|
||||||
from ...enums.update import UpdateType
|
from ...enums.update import UpdateType
|
||||||
from ...types.updates.bot_added import BotAdded
|
from ...types.updates.bot_added import BotAdded
|
||||||
from ...types.updates.bot_removed import BotRemoved
|
from ...types.updates.bot_removed import BotRemoved
|
||||||
from ...types.updates.bot_started import BotStarted
|
from ...types.updates.bot_started import BotStarted
|
||||||
|
from ...types.updates.bot_stopped import BotStopped
|
||||||
from ...types.updates.chat_title_changed import ChatTitleChanged
|
from ...types.updates.chat_title_changed import ChatTitleChanged
|
||||||
from ...types.updates.message_callback import MessageCallback
|
from ...types.updates.message_callback import MessageCallback
|
||||||
from ...types.updates.message_chat_created import MessageChatCreated
|
from ...types.updates.message_chat_created import MessageChatCreated
|
||||||
@@ -12,60 +15,56 @@ from ...types.updates.message_edited import MessageEdited
|
|||||||
from ...types.updates.message_removed import MessageRemoved
|
from ...types.updates.message_removed import MessageRemoved
|
||||||
from ...types.updates.user_added import UserAdded
|
from ...types.updates.user_added import UserAdded
|
||||||
from ...types.updates.user_removed import UserRemoved
|
from ...types.updates.user_removed import UserRemoved
|
||||||
|
from ...types.updates.dialog_cleared import DialogCleared
|
||||||
|
from ...types.updates.dialog_muted import DialogMuted
|
||||||
|
from ...types.updates.dialog_unmuted import DialogUnmuted
|
||||||
|
from ...types.updates.dialog_removed import DialogRemoved
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...bot import Bot
|
from ...bot import Bot
|
||||||
|
|
||||||
|
|
||||||
async def get_update_model(event: dict, bot: 'Bot'):
|
UPDATE_MODEL_MAPPING = {
|
||||||
event_object = None
|
UpdateType.BOT_ADDED: BotAdded,
|
||||||
match event['update_type']:
|
UpdateType.BOT_REMOVED: BotRemoved,
|
||||||
case UpdateType.BOT_ADDED:
|
UpdateType.BOT_STARTED: BotStarted,
|
||||||
event_object = BotAdded(**event)
|
UpdateType.CHAT_TITLE_CHANGED: ChatTitleChanged,
|
||||||
case UpdateType.BOT_REMOVED:
|
UpdateType.MESSAGE_CALLBACK: MessageCallback,
|
||||||
event_object = BotRemoved(**event)
|
UpdateType.MESSAGE_CHAT_CREATED: MessageChatCreated,
|
||||||
case UpdateType.BOT_STARTED:
|
UpdateType.MESSAGE_CREATED: MessageCreated,
|
||||||
event_object = BotStarted(**event)
|
UpdateType.MESSAGE_EDITED: MessageEdited,
|
||||||
case UpdateType.CHAT_TITLE_CHANGED:
|
UpdateType.MESSAGE_REMOVED: MessageRemoved,
|
||||||
event_object = ChatTitleChanged(**event)
|
UpdateType.USER_ADDED: UserAdded,
|
||||||
case UpdateType.MESSAGE_CALLBACK:
|
UpdateType.USER_REMOVED: UserRemoved,
|
||||||
event_object = MessageCallback(**event)
|
UpdateType.BOT_STOPPED: BotStopped,
|
||||||
case UpdateType.MESSAGE_CHAT_CREATED:
|
UpdateType.DIALOG_CLEARED: DialogCleared,
|
||||||
event_object = MessageChatCreated(**event)
|
UpdateType.DIALOG_MUTED: DialogMuted,
|
||||||
case UpdateType.MESSAGE_CREATED:
|
UpdateType.DIALOG_UNMUTED: DialogUnmuted,
|
||||||
event_object = MessageCreated(**event)
|
UpdateType.DIALOG_REMOVED: DialogRemoved
|
||||||
case UpdateType.MESSAGE_EDITED:
|
}
|
||||||
event_object = MessageEdited(**event)
|
|
||||||
case UpdateType.MESSAGE_REMOVED:
|
|
||||||
event_object = MessageRemoved(**event)
|
|
||||||
case UpdateType.USER_ADDED:
|
|
||||||
event_object = UserAdded(**event)
|
|
||||||
case UpdateType.USER_REMOVED:
|
|
||||||
event_object = UserRemoved(**event)
|
|
||||||
|
|
||||||
if hasattr(event_object, 'bot'):
|
|
||||||
event_object.bot = bot
|
async def get_update_model(event: dict, bot: 'Bot'):
|
||||||
if hasattr(event_object, 'message'):
|
update_type = event['update_type']
|
||||||
event_object.message.bot = bot
|
model_cls = UPDATE_MODEL_MAPPING.get(update_type)
|
||||||
|
|
||||||
|
if not model_cls:
|
||||||
|
raise ValueError(f'Unknown update type: {update_type}')
|
||||||
|
|
||||||
|
event_object = await enrich_event(
|
||||||
|
event_object=model_cls(**event),
|
||||||
|
bot=bot
|
||||||
|
)
|
||||||
|
|
||||||
return event_object
|
return event_object
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def process_update_request(events: dict, bot: 'Bot'):
|
async def process_update_request(events: dict, bot: 'Bot'):
|
||||||
events = [event for event in events['updates']]
|
return [
|
||||||
|
await get_update_model(event, bot)
|
||||||
objects = []
|
for event in events['updates']
|
||||||
|
]
|
||||||
for event in events:
|
|
||||||
|
|
||||||
objects.append(
|
|
||||||
await get_update_model(
|
|
||||||
bot=bot,
|
|
||||||
event=event
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return objects
|
|
||||||
|
|
||||||
|
|
||||||
async def process_update_webhook(event_json: dict, bot: 'Bot'):
|
async def process_update_webhook(event_json: dict, bot: 'Bot'):
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from typing import Any, Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...types.message import Message
|
|
||||||
|
|
||||||
|
|
||||||
class GettedUploadUrl(BaseModel):
|
class GettedUploadUrl(BaseModel):
|
||||||
url: Optional[str] = None
|
url: str
|
||||||
token: Optional[str] = None
|
token: Optional[str] = None
|
||||||
@@ -3,5 +3,14 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class PinnedMessage(BaseModel):
|
class PinnedMessage(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при добавлении списка администраторов в чат.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class RemovedAdmin(BaseModel):
|
class RemovedAdmin(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при отмене прав администратора у пользователя в чате
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from ...types.chats import ChatMember
|
|
||||||
|
|
||||||
|
|
||||||
class RemovedMemberChat(BaseModel):
|
class RemovedMemberChat(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Ответ API при удалении участника из чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или описание ошибки.
|
||||||
|
"""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
message: Optional[str] = None
|
message: Optional[str] = None
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user