Compare commits
46 Commits
6f86d15de4
...
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 |
106
README.md
106
README.md
@@ -1,23 +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
|
||||||
@@ -30,7 +60,7 @@ logging.basicConfig(level=logging.INFO)
|
|||||||
bot = Bot('тут_ваш_токен')
|
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(
|
||||||
@@ -38,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 💙")
|
||||||
@@ -52,44 +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/wiki)
|
Указан пример простого запуска, для более низкого уровня можете рассмотреть [этот пример](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)
|
||||||
|
|
||||||
[Тут](https://github.com/love-apples/maxapi/tree/main/examples)
|
bot = Bot('тут_ваш_токен')
|
||||||
|
dp = Dispatcher()
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 🧩 Возможности
|
# Команда /start боту
|
||||||
|
@dp.message_created(Command('start'))
|
||||||
- ✅ Middleware
|
async def hello(event: MessageCreated):
|
||||||
- ✅ Роутеры
|
await event.message.answer(f"Привет из вебхука!")
|
||||||
- ✅ Билдер инлайн клавиатур
|
|
||||||
- ✅ Простая загрузка медиафайлов
|
|
||||||
- ✅ MagicFilter
|
|
||||||
- ✅ Внутренние функции моделей
|
|
||||||
- ✅ Контекстный менеджер
|
|
||||||
- ✅ Поллинг
|
|
||||||
- ✅ Вебхук
|
|
||||||
- ✅ Логгирование
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
## 💬 Обратная связь и поддержка
|
async def main():
|
||||||
|
await dp.handle_webhook(
|
||||||
|
bot=bot,
|
||||||
|
host='localhost',
|
||||||
|
port=8080,
|
||||||
|
log_level='critical' # Можно убрать для подробного логгирования
|
||||||
|
)
|
||||||
|
|
||||||
- MAX: [Чат](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8)
|
|
||||||
- Telegram: [@loveappless](https://t.me/loveappless)
|
|
||||||
- Telegram чат: [MAXApi | Обсуждение](https://t.me/maxapi_github)
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 Лицензия
|
if __name__ == '__main__':
|
||||||
|
asyncio.run(main())
|
||||||
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](https://github.com/love-apples/maxapi/blob/main/LICENSE) для подробностей.
|
```
|
||||||
@@ -9,3 +9,5 @@
|
|||||||
- [Вебхуки](https://github.com/love-apples/maxapi/tree/main/examples/webhook)
|
- [Вебхуки](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/keyboard/main.py)
|
||||||
- [Миддлварь в роутерах](https://github.com/love-apples/maxapi/tree/main/examples/middleware_for_router/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())
|
||||||
@@ -55,12 +55,17 @@ async def hello(event: MessageCreated):
|
|||||||
attachments=[
|
attachments=[
|
||||||
builder.as_markup(),
|
builder.as_markup(),
|
||||||
] # Для MAX клавиатура это вложение,
|
] # Для MAX клавиатура это вложение,
|
||||||
) # поэтому она в списке вложений
|
) # поэтому она в attachments
|
||||||
|
|
||||||
|
|
||||||
@dp.bot_added()
|
@dp.bot_added()
|
||||||
async def bot_added(event: BotAdded):
|
async def bot_added(event: BotAdded):
|
||||||
await event.bot.send_message(
|
|
||||||
|
if not event.chat:
|
||||||
|
logging.info('Не удалось получить chat, возможно отключен auto_requests!')
|
||||||
|
return
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
chat_id=event.chat.id,
|
chat_id=event.chat.id,
|
||||||
text=f'Привет чат {event.chat.title}!'
|
text=f'Привет чат {event.chat.title}!'
|
||||||
)
|
)
|
||||||
@@ -68,7 +73,7 @@ async def bot_added(event: BotAdded):
|
|||||||
|
|
||||||
@dp.message_removed()
|
@dp.message_removed()
|
||||||
async def message_removed(event: MessageRemoved):
|
async def message_removed(event: MessageRemoved):
|
||||||
await event.bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
text='Я всё видел!'
|
text='Я всё видел!'
|
||||||
)
|
)
|
||||||
@@ -76,7 +81,7 @@ async def message_removed(event: MessageRemoved):
|
|||||||
|
|
||||||
@dp.bot_started()
|
@dp.bot_started()
|
||||||
async def bot_started(event: BotStarted):
|
async def bot_started(event: BotStarted):
|
||||||
await event.bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
text='Привет! Отправь мне /start'
|
text='Привет! Отправь мне /start'
|
||||||
)
|
)
|
||||||
@@ -84,9 +89,9 @@ async def bot_started(event: BotStarted):
|
|||||||
|
|
||||||
@dp.chat_title_changed()
|
@dp.chat_title_changed()
|
||||||
async def chat_title_changed(event: ChatTitleChanged):
|
async def chat_title_changed(event: ChatTitleChanged):
|
||||||
await event.bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
text=f'Крутое новое название "{event.chat.title}"!'
|
text=f'Крутое новое название "{event.title}"!'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -106,7 +111,14 @@ async def message_edited(event: MessageEdited):
|
|||||||
|
|
||||||
@dp.user_removed()
|
@dp.user_removed()
|
||||||
async def user_removed(event: UserRemoved):
|
async def user_removed(event: UserRemoved):
|
||||||
await event.bot.send_message(
|
|
||||||
|
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,
|
chat_id=event.chat_id,
|
||||||
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
|
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
|
||||||
)
|
)
|
||||||
@@ -114,7 +126,14 @@ async def user_removed(event: UserRemoved):
|
|||||||
|
|
||||||
@dp.user_added()
|
@dp.user_added()
|
||||||
async def user_added(event: UserAdded):
|
async def user_added(event: UserAdded):
|
||||||
await event.bot.send_message(
|
|
||||||
|
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,
|
chat_id=event.chat_id,
|
||||||
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
||||||
)
|
)
|
||||||
@@ -122,27 +141,32 @@ async def user_added(event: UserAdded):
|
|||||||
|
|
||||||
@dp.bot_stopped()
|
@dp.bot_stopped()
|
||||||
async def bot_stopped(event: BotStopped):
|
async def bot_stopped(event: BotStopped):
|
||||||
print(event.from_user.full_name, 'остановил бота') # type: ignore
|
logging.info(event.from_user.full_name, 'остановил бота') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@dp.dialog_cleared()
|
@dp.dialog_cleared()
|
||||||
async def dialog_cleared(event: DialogCleared):
|
async def dialog_cleared(event: DialogCleared):
|
||||||
print(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
|
logging.info(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@dp.dialog_muted()
|
@dp.dialog_muted()
|
||||||
async def dialog_muted(event: DialogMuted):
|
async def dialog_muted(event: DialogMuted):
|
||||||
print(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
|
logging.info(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@dp.dialog_unmuted()
|
@dp.dialog_unmuted()
|
||||||
async def dialog_unmuted(event: DialogUnmuted):
|
async def dialog_unmuted(event: DialogUnmuted):
|
||||||
print(event.from_user.full_name, 'включил оповещения от чата бота') # type: ignore
|
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()
|
@dp.message_chat_created()
|
||||||
async def message_chat_created(event: MessageChatCreated):
|
async def message_chat_created(event: MessageChatCreated):
|
||||||
await event.bot.send_message(
|
await bot.send_message(
|
||||||
chat_id=event.chat.chat_id,
|
chat_id=event.chat.chat_id,
|
||||||
text=f'Чат создан! Ссылка: {event.chat.link}'
|
text=f'Чат создан! Ссылка: {event.chat.link}'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ async def builder(event: MessageCreated):
|
|||||||
chat_description='Test desc'
|
chat_description='Test desc'
|
||||||
),
|
),
|
||||||
LinkButton(
|
LinkButton(
|
||||||
text="Канал разработчика",
|
text="Документация MAX",
|
||||||
url="https://t.me/loveapples_dev"
|
url="https://dev.max.ru/docs"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -99,8 +99,8 @@ async def payload(event: MessageCreated):
|
|||||||
chat_description='Test desc'
|
chat_description='Test desc'
|
||||||
),
|
),
|
||||||
LinkButton(
|
LinkButton(
|
||||||
text="Канал разработчика",
|
text="Документация MAX",
|
||||||
url="https://t.me/loveapples_dev"
|
url="https://dev.max.ru/docs"
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
from maxapi import Bot, Dispatcher
|
from maxapi import Bot, Dispatcher
|
||||||
from maxapi.types import MessageCreated, Command, UpdateUnion
|
from maxapi.types import MessageCreated, Command, UpdateUnion
|
||||||
@@ -16,13 +16,14 @@ dp = Dispatcher()
|
|||||||
class CustomDataForRouterMiddleware(BaseMiddleware):
|
class CustomDataForRouterMiddleware(BaseMiddleware):
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
event: UpdateUnion,
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
data: Dict[str, Any]
|
event_object: UpdateUnion,
|
||||||
):
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
data['custom_data'] = f'Это ID того кто вызвал команду: {event.from_user.user_id}'
|
data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'
|
||||||
|
result = await handler(event_object, data)
|
||||||
return data
|
return result
|
||||||
|
|
||||||
|
|
||||||
@dp.message_created(Command('custom_data'))
|
@dp.message_created(Command('custom_data'))
|
||||||
@@ -31,9 +32,8 @@ async def custom_data(event: MessageCreated, custom_data: str):
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
dp.middlewares = [
|
dp.middleware(CustomDataForRouterMiddleware())
|
||||||
CustomDataForRouterMiddleware()
|
|
||||||
]
|
|
||||||
await dp.start_polling(bot)
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any, Awaitable, Callable, Dict
|
||||||
|
|
||||||
from maxapi import Bot, Dispatcher
|
from maxapi import Bot, Dispatcher
|
||||||
from maxapi.filters.middleware import BaseMiddleware
|
from maxapi.filters.middleware import BaseMiddleware
|
||||||
from maxapi.types import MessageCreated, Command, UpdateUnion
|
from maxapi.types import MessageCreated, Command, UpdateUnion
|
||||||
from maxapi.types.command import Command
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
@@ -17,27 +16,31 @@ dp = Dispatcher()
|
|||||||
class CheckChatTitleMiddleware(BaseMiddleware):
|
class CheckChatTitleMiddleware(BaseMiddleware):
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
event: UpdateUnion,
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
):
|
event_object: UpdateUnion,
|
||||||
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
return event.chat.title == 'MAXApi'
|
if event_object.chat.title == 'MAXApi':
|
||||||
|
return await handler(event_object, data)
|
||||||
|
|
||||||
@dp.message_created(Command('start'), CheckChatTitleMiddleware())
|
|
||||||
async def start(event: MessageCreated):
|
|
||||||
await event.message.answer('Это сообщение было отправлено, так как ваш чат называется "MAXApi"!')
|
|
||||||
|
|
||||||
|
|
||||||
class CustomDataMiddleware(BaseMiddleware):
|
class CustomDataMiddleware(BaseMiddleware):
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
event: UpdateUnion,
|
handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]],
|
||||||
data: Dict[str, Any]
|
event_object: UpdateUnion,
|
||||||
):
|
data: Dict[str, Any],
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
data['custom_data'] = f'Это ID того кто вызвал команду: {event.from_user.user_id}'
|
data['custom_data'] = f'Это ID того кто вызвал команду: {event_object.from_user.user_id}'
|
||||||
|
|
||||||
return data
|
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())
|
@dp.message_created(Command('custom_data'), CustomDataMiddleware())
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ async def handle_message(event: MessageCreated):
|
|||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await dp.handle_webhook(bot, log_level='critical')
|
await dp.handle_webhook(
|
||||||
|
bot=bot,
|
||||||
|
host='localhost',
|
||||||
|
port=8080,
|
||||||
|
log_level='critical' # Можно убрать для подробного логгирования
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
418
maxapi/bot.py
418
maxapi/bot.py
@@ -9,9 +9,12 @@ from .types.errors import Error
|
|||||||
from .types.input_media import InputMedia, InputMediaBuffer
|
from .types.input_media import InputMedia, InputMediaBuffer
|
||||||
|
|
||||||
from .connection.base import BaseConnection
|
from .connection.base import BaseConnection
|
||||||
|
from .loggers import logger_bot
|
||||||
|
|
||||||
from .enums.parse_mode import ParseMode
|
from .enums.parse_mode import ParseMode
|
||||||
from .enums.sender_action import SenderAction
|
from .enums.sender_action import SenderAction
|
||||||
from .enums.upload_type import UploadType
|
from .enums.upload_type import UploadType
|
||||||
|
from .enums.update import UpdateType
|
||||||
|
|
||||||
from .methods.add_admin_chat import AddAdminChat
|
from .methods.add_admin_chat import AddAdminChat
|
||||||
from .methods.add_members_chat import AddMembersChat
|
from .methods.add_members_chat import AddMembersChat
|
||||||
@@ -20,7 +23,6 @@ from .methods.delete_bot_from_chat import DeleteMeFromMessage
|
|||||||
from .methods.delete_chat import DeleteChat
|
from .methods.delete_chat import DeleteChat
|
||||||
from .methods.delete_message import DeleteMessage
|
from .methods.delete_message import DeleteMessage
|
||||||
from .methods.delete_pin_message import DeletePinMessage
|
from .methods.delete_pin_message import DeletePinMessage
|
||||||
from .methods.download_media import DownloadMedia
|
|
||||||
from .methods.edit_chat import EditChat
|
from .methods.edit_chat import EditChat
|
||||||
from .methods.edit_message import EditMessage
|
from .methods.edit_message import EditMessage
|
||||||
from .methods.get_chat_by_id import GetChatById
|
from .methods.get_chat_by_id import GetChatById
|
||||||
@@ -41,6 +43,13 @@ from .methods.remove_member_chat import RemoveMemberChat
|
|||||||
from .methods.send_action import SendAction
|
from .methods.send_action import SendAction
|
||||||
from .methods.send_callback import SendCallback
|
from .methods.send_callback import SendCallback
|
||||||
from .methods.send_message import SendMessage
|
from .methods.send_message import SendMessage
|
||||||
|
from .methods.get_subscriptions import GetSubscriptions
|
||||||
|
from .methods.types.getted_subscriptions import GettedSubscriptions
|
||||||
|
from .methods.subscribe_webhook import SubscribeWebhook
|
||||||
|
from .methods.types.subscribed import Subscribed
|
||||||
|
from .methods.types.unsubscribed import Unsubscribed
|
||||||
|
from .methods.unsubscribe_webhook import UnsubscribeWebhook
|
||||||
|
from .methods.get_message import GetMessage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .types.attachments.attachment import Attachment
|
from .types.attachments.attachment import Attachment
|
||||||
@@ -72,7 +81,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class Bot(BaseConnection):
|
class Bot(BaseConnection):
|
||||||
|
|
||||||
"""Основной класс для работы с API бота.
|
"""
|
||||||
|
Основной класс для работы с API бота.
|
||||||
|
|
||||||
Предоставляет методы для взаимодействия с чатами, сообщениями,
|
Предоставляет методы для взаимодействия с чатами, сообщениями,
|
||||||
пользователями и другими функциями бота.
|
пользователями и другими функциями бота.
|
||||||
@@ -85,26 +95,29 @@ class Bot(BaseConnection):
|
|||||||
notify: Optional[bool] = None,
|
notify: Optional[bool] = None,
|
||||||
auto_requests: bool = True,
|
auto_requests: bool = True,
|
||||||
default_connection: Optional[DefaultConnectionProperties] = None,
|
default_connection: Optional[DefaultConnectionProperties] = None,
|
||||||
after_input_media_delay: Optional[float] = None
|
after_input_media_delay: Optional[float] = None,
|
||||||
|
auto_check_subscriptions: bool = True
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Инициализирует экземпляр бота с указанным токеном.
|
Инициализирует экземпляр бота.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
token (str): Токен доступа к API бота.
|
||||||
|
parse_mode (Optional[ParseMode]): Форматирование по умолчанию.
|
||||||
|
notify (Optional[bool]): Отключение уведомлений при отправке сообщений.
|
||||||
|
auto_requests (bool): Автоматическое заполнение chat/from_user через API (по умолчанию True).
|
||||||
|
default_connection (Optional[DefaultConnectionProperties]): Настройки соединения.
|
||||||
|
after_input_media_delay (Optional[float]): Задержка после загрузки файла.
|
||||||
|
auto_check_subscriptions (bool): Проверка подписок для метода start_polling.
|
||||||
|
|
||||||
:param token: Токен доступа к API бота
|
|
||||||
:param parse_mode: Форматирование по умолчанию
|
|
||||||
:param notify: Отключение уведомлений при отправке сообщений (по умолчанию игнорируется) (не работает на стороне MAX)
|
|
||||||
:param auto_requests: Автоматическое заполнение полей chat и from_user в Update
|
|
||||||
:param default_connection: Настройки aiohttp
|
|
||||||
:param after_input_media_delay: Задержка в секундах после загрузки файла на сервера MAX (без этого чаще всего MAX не успевает обработать вложение и выдает ошибку `errors.process.attachment.file.not.processed`)
|
|
||||||
с помощью API запросов если они не заложены как полноценные объекты в Update (по умолчанию True, при False chat и from_user в некоторых событиях будут выдавать None)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.bot = self
|
self.bot = self
|
||||||
self.default_connection = default_connection or DefaultConnectionProperties()
|
self.default_connection = default_connection or DefaultConnectionProperties()
|
||||||
self.after_input_media_delay = after_input_media_delay or 2.0
|
self.after_input_media_delay = after_input_media_delay or 2.0
|
||||||
|
self.auto_check_subscriptions = auto_check_subscriptions
|
||||||
|
|
||||||
self.__token = token
|
self.__token = token
|
||||||
self.params: Dict[str, Any] = {'access_token': self.__token}
|
self.params: Dict[str, Any] = {'access_token': self.__token}
|
||||||
@@ -118,14 +131,56 @@ class Bot(BaseConnection):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def me(self):
|
def me(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Возвращает объект пользователя (бота).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
User | None: Объект пользователя или None.
|
||||||
|
"""
|
||||||
|
|
||||||
return self._me
|
return self._me
|
||||||
|
|
||||||
def _resolve_notify(self, notify: Optional[bool]) -> Optional[bool]:
|
def _resolve_notify(self, notify: Optional[bool]) -> Optional[bool]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Определяет флаг уведомления.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
notify (Optional[bool]): Локальный флаг.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[bool]: Итоговый флаг.
|
||||||
|
"""
|
||||||
|
|
||||||
return notify if notify is not None else self.notify
|
return notify if notify is not None else self.notify
|
||||||
|
|
||||||
def _resolve_parse_mode(self, mode: Optional[ParseMode]) -> Optional[ParseMode]:
|
def _resolve_parse_mode(self, mode: Optional[ParseMode]) -> Optional[ParseMode]:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Определяет режим форматирования.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mode (Optional[ParseMode]): Локальный режим.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[ParseMode]: Итоговый режим.
|
||||||
|
"""
|
||||||
|
|
||||||
return mode if mode is not None else self.parse_mode
|
return mode if mode is not None else self.parse_mode
|
||||||
|
|
||||||
|
async def close_session(self) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Закрывает текущую сессию aiohttp.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.session is not None:
|
||||||
|
await self.session.close()
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(
|
||||||
self,
|
self,
|
||||||
chat_id: Optional[int] = None,
|
chat_id: Optional[int] = None,
|
||||||
@@ -140,15 +195,17 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Отправляет сообщение в чат или пользователю.
|
Отправляет сообщение в чат или пользователю.
|
||||||
|
|
||||||
:param chat_id: ID чата для отправки (обязателен, если не указан user_id)
|
Args:
|
||||||
:param user_id: ID пользователя для отправки (обязателен, если не указан chat_id)
|
chat_id (Optional[int]): ID чата для отправки (если не user_id).
|
||||||
:param text: Текст сообщения
|
user_id (Optional[int]): ID пользователя (если не chat_id).
|
||||||
:param attachments: Список вложений к сообщению
|
text (Optional[str]): Текст сообщения.
|
||||||
:param link: Данные ссылки сообщения
|
attachments (Optional[List[Attachment | InputMedia | InputMediaBuffer]]): Вложения.
|
||||||
:param notify: Отправлять уведомление получателю (по умолчанию берется значение из бота)
|
link (Optional[NewMessageLink]): Данные ссылки сообщения.
|
||||||
:param parse_mode: Режим форматирования текста
|
notify (Optional[bool]): Флаг уведомления.
|
||||||
|
parse_mode (Optional[ParseMode]): Режим форматирования текста.
|
||||||
|
|
||||||
:return: Объект отправленного сообщения
|
Returns:
|
||||||
|
Optional[SendedMessage | Error]: Отправленное сообщение или ошибка.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await SendMessage(
|
return await SendMessage(
|
||||||
@@ -171,10 +228,12 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Отправляет действие в чат (например, "печатает").
|
Отправляет действие в чат (например, "печатает").
|
||||||
|
|
||||||
:param chat_id: ID чата для отправки действия
|
Args:
|
||||||
:param action: Тип действия (по умолчанию SenderAction.TYPING_ON)
|
chat_id (Optional[int]): ID чата.
|
||||||
|
action (SenderAction): Тип действия.
|
||||||
|
|
||||||
:return: Результат отправки действия
|
Returns:
|
||||||
|
SendedAction: Результат отправки действия.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await SendAction(
|
return await SendAction(
|
||||||
@@ -196,14 +255,16 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Редактирует существующее сообщение.
|
Редактирует существующее сообщение.
|
||||||
|
|
||||||
:param message_id: ID сообщения для редактирования
|
Args:
|
||||||
:param text: Новый текст сообщения
|
message_id (str): ID сообщения.
|
||||||
:param attachments: Новые вложения
|
text (Optional[str]): Новый текст.
|
||||||
:param link: Новая ссылка сообщения
|
attachments (Optional[List[Attachment | InputMedia | InputMediaBuffer]]): Новые вложения.
|
||||||
:param notify: Отправлять уведомление получателю (по умолчанию берется значение из бота)
|
link (Optional[NewMessageLink]): Новая ссылка.
|
||||||
:param parse_mode: Режим форматирования текста
|
notify (Optional[bool]): Флаг уведомления.
|
||||||
|
parse_mode (Optional[ParseMode]): Режим форматирования текста.
|
||||||
|
|
||||||
:return: Объект отредактированного сообщения
|
Returns:
|
||||||
|
Optional[EditedMessage | Error]: Отредактированное сообщение или ошибка.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await EditMessage(
|
return await EditMessage(
|
||||||
@@ -224,9 +285,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Удаляет сообщение.
|
Удаляет сообщение.
|
||||||
|
|
||||||
:param message_id: ID сообщения для удаления
|
Args:
|
||||||
|
message_id (str): ID сообщения.
|
||||||
|
|
||||||
:return: Результат удаления сообщения
|
Returns:
|
||||||
|
DeletedMessage: Результат удаления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await DeleteMessage(
|
return await DeleteMessage(
|
||||||
@@ -242,9 +305,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Удаляет чат.
|
Удаляет чат.
|
||||||
|
|
||||||
:param chat_id: ID чата для удаления
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Результат удаления чата
|
Returns:
|
||||||
|
DeletedChat: Результат удаления чата.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await DeleteChat(
|
return await DeleteChat(
|
||||||
@@ -264,13 +329,15 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает сообщения из чата.
|
Получает сообщения из чата.
|
||||||
|
|
||||||
:param chat_id: ID чата (обязателен, если не указаны message_ids)
|
Args:
|
||||||
:param message_ids: Список ID сообщений для получения
|
chat_id (Optional[int]): ID чата.
|
||||||
:param from_time: Время начала периода (datetime или timestamp)
|
message_ids (Optional[List[str]]): ID сообщений.
|
||||||
:param to_time: Время конца периода (datetime или timestamp)
|
from_time (Optional[datetime | int]): Начало периода.
|
||||||
:param count: Количество сообщений (по умолчанию 50)
|
to_time (Optional[datetime | int]): Конец периода.
|
||||||
|
count (int): Количество сообщений.
|
||||||
|
|
||||||
:return: Список сообщений
|
Returns:
|
||||||
|
Messages: Список сообщений.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetMessages(
|
return await GetMessages(
|
||||||
@@ -285,26 +352,30 @@ class Bot(BaseConnection):
|
|||||||
async def get_message(
|
async def get_message(
|
||||||
self,
|
self,
|
||||||
message_id: str
|
message_id: str
|
||||||
) -> Messages:
|
) -> Message:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Получает одно сообщение по ID.
|
Получает одно сообщение по ID.
|
||||||
|
|
||||||
:param message_id: ID сообщения
|
Args:
|
||||||
|
message_id (str): ID сообщения.
|
||||||
|
|
||||||
:return: Объект сообщения
|
Returns:
|
||||||
|
Message: Объект сообщения.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await self.get_messages(
|
return await GetMessage(
|
||||||
message_ids=[message_id]
|
bot=self,
|
||||||
)
|
message_id=message_id
|
||||||
|
).fetch()
|
||||||
|
|
||||||
async def get_me(self) -> User:
|
async def get_me(self) -> User:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Получает информацию о текущем боте.
|
Получает информацию о текущем боте.
|
||||||
|
|
||||||
:return: Объект пользователя бота
|
Returns:
|
||||||
|
User: Объект пользователя бота.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetMe(self).fetch()
|
return await GetMe(self).fetch()
|
||||||
@@ -317,9 +388,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает закрепленное сообщение в чате.
|
Получает закрепленное сообщение в чате.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Закрепленное сообщение
|
Returns:
|
||||||
|
GettedPin: Закрепленное сообщение.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetPinnedMessage(
|
return await GetPinnedMessage(
|
||||||
@@ -332,18 +405,20 @@ class Bot(BaseConnection):
|
|||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
commands: Optional[List[BotCommand]] = None,
|
commands: Optional[List[BotCommand]] = None,
|
||||||
photo: Optional[Dict[str, Any]] = None
|
photo: Optional[PhotoAttachmentRequestPayload] = None
|
||||||
) -> User:
|
) -> User:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Изменяет информацию о боте.
|
Изменяет информацию о боте.
|
||||||
|
|
||||||
:param name: Новое имя бота
|
Args:
|
||||||
:param description: Новое описание бота
|
name (Optional[str]): Новое имя бота.
|
||||||
:param commands: Список команд бота
|
description (Optional[str]): Новое описание.
|
||||||
:param photo: Данные фотографии бота
|
commands (Optional[List[BotCommand]]): Команды бота.
|
||||||
|
photo (Optional[PhotoAttachmentRequestPayload]): Фото бота.
|
||||||
|
|
||||||
:return: Обновленная информация о боте
|
Returns:
|
||||||
|
User: Обновленная информация о боте.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await ChangeInfo(
|
return await ChangeInfo(
|
||||||
@@ -363,10 +438,12 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает список чатов бота.
|
Получает список чатов бота.
|
||||||
|
|
||||||
:param count: Количество чатов (по умолчанию 50)
|
Args:
|
||||||
:param marker: Маркер для пагинации
|
count (int): Количество чатов (по умолчанию 50).
|
||||||
|
marker (Optional[int]): Маркер для пагинации.
|
||||||
|
|
||||||
:return: Список чатов
|
Returns:
|
||||||
|
Chats: Список чатов.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetChats(
|
return await GetChats(
|
||||||
@@ -383,9 +460,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает чат по ссылке.
|
Получает чат по ссылке.
|
||||||
|
|
||||||
:param link: Ссылка на чат
|
Args:
|
||||||
|
link (str): Ссылка на чат.
|
||||||
|
|
||||||
:return: Объект чата
|
Returns:
|
||||||
|
Chat: Объект чата.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetChatByLink(bot=self, link=link).fetch()
|
return await GetChatByLink(bot=self, link=link).fetch()
|
||||||
@@ -398,9 +477,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает чат по ID.
|
Получает чат по ID.
|
||||||
|
|
||||||
:param id: ID чата
|
Args:
|
||||||
|
id (int): ID чата.
|
||||||
|
|
||||||
:return: Объект чата
|
Returns:
|
||||||
|
Chat: Объект чата.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetChatById(bot=self, id=id).fetch()
|
return await GetChatById(bot=self, id=id).fetch()
|
||||||
@@ -417,13 +498,15 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Редактирует параметры чата.
|
Редактирует параметры чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param icon: Данные иконки чата
|
chat_id (int): ID чата.
|
||||||
:param title: Новый заголовок чата
|
icon (Optional[PhotoAttachmentRequestPayload]): Иконка.
|
||||||
:param pin: ID сообщения для закрепления
|
title (Optional[str]): Новый заголовок.
|
||||||
:param notify: Отправлять уведомление получателю (по умолчанию берется значение из бота)
|
pin (Optional[str]): ID сообщения для закрепления.
|
||||||
|
notify (Optional[bool]): Флаг уведомления.
|
||||||
|
|
||||||
:return: Обновленный объект чата
|
Returns:
|
||||||
|
Chat: Обновленный объект чата.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await EditChat(
|
return await EditChat(
|
||||||
@@ -443,9 +526,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает видео по токену.
|
Получает видео по токену.
|
||||||
|
|
||||||
:param video_token: Токен видео
|
Args:
|
||||||
|
video_token (str): Токен видео.
|
||||||
|
|
||||||
:return: Объект видео
|
Returns:
|
||||||
|
Video: Объект видео.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetVideo(
|
return await GetVideo(
|
||||||
@@ -463,11 +548,13 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Отправляет callback ответ.
|
Отправляет callback ответ.
|
||||||
|
|
||||||
:param callback_id: ID callback
|
Args:
|
||||||
:param message: Сообщение для отправки
|
callback_id (str): ID callback.
|
||||||
:param notification: Текст уведомления
|
message (Optional[Message]): Сообщение для отправки.
|
||||||
|
notification (Optional[str]): Текст уведомления.
|
||||||
|
|
||||||
:return: Результат отправки callback
|
Returns:
|
||||||
|
SendedCallback: Результат отправки callback.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await SendCallback(
|
return await SendCallback(
|
||||||
@@ -487,11 +574,13 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Закрепляет сообщение в чате.
|
Закрепляет сообщение в чате.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param message_id: ID сообщения
|
chat_id (int): ID чата.
|
||||||
:param notify: Отправлять уведомление получателю (по умолчанию берется значение из бота)
|
message_id (str): ID сообщения.
|
||||||
|
notify (Optional[bool]): Флаг уведомления.
|
||||||
|
|
||||||
:return: Закрепленное сообщение
|
Returns:
|
||||||
|
PinnedMessage: Закрепленное сообщение.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await PinMessage(
|
return await PinMessage(
|
||||||
@@ -509,9 +598,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Удаляет закрепленное сообщение в чате.
|
Удаляет закрепленное сообщение в чате.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Результат удаления
|
Returns:
|
||||||
|
DeletedPinMessage: Результат удаления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await DeletePinMessage(
|
return await DeletePinMessage(
|
||||||
@@ -525,11 +616,13 @@ class Bot(BaseConnection):
|
|||||||
) -> ChatMember:
|
) -> ChatMember:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Получает информацию о боте в конкретном чате.
|
Получает информацию о боте в чате.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Информация о боте в чате
|
Returns:
|
||||||
|
ChatMember: Информация о боте в чате.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetMeFromChat(
|
return await GetMeFromChat(
|
||||||
@@ -545,9 +638,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Удаляет бота из чата.
|
Удаляет бота из чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Результат удаления
|
Returns:
|
||||||
|
DeletedBotFromChat: Результат удаления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await DeleteMeFromMessage(
|
return await DeleteMeFromMessage(
|
||||||
@@ -563,9 +658,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает список администраторов чата.
|
Получает список администраторов чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
|
chat_id (int): ID чата.
|
||||||
|
|
||||||
:return: Список администраторов
|
Returns:
|
||||||
|
GettedListAdminChat: Список администраторов.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetListAdminChat(
|
return await GetListAdminChat(
|
||||||
@@ -583,11 +680,13 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Добавляет администраторов в чат.
|
Добавляет администраторов в чат.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param admins: Список администраторов
|
chat_id (int): ID чата.
|
||||||
:param marker: Маркер для пагинации
|
admins (List[ChatAdmin]): Список администраторов.
|
||||||
|
marker (Optional[int]): Маркер для пагинации.
|
||||||
|
|
||||||
:return: Результат добавления
|
Returns:
|
||||||
|
AddedListAdminChat: Результат добавления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await AddAdminChat(
|
return await AddAdminChat(
|
||||||
@@ -606,10 +705,12 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Удаляет администратора из чата.
|
Удаляет администратора из чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param user_id: ID пользователя
|
chat_id (int): ID чата.
|
||||||
|
user_id (int): ID пользователя.
|
||||||
|
|
||||||
:return: Результат удаления
|
Returns:
|
||||||
|
RemovedAdmin: Результат удаления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await RemoveAdmin(
|
return await RemoveAdmin(
|
||||||
@@ -629,12 +730,14 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает участников чата.
|
Получает участников чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param user_ids: Список ID участников
|
chat_id (int): ID чата.
|
||||||
:param marker: Маркер для пагинации
|
user_ids (Optional[List[int]]): Список ID участников.
|
||||||
:param count: Количество участников
|
marker (Optional[int]): Маркер для пагинации.
|
||||||
|
count (Optional[int]): Количество участников.
|
||||||
|
|
||||||
:return: Список участников
|
Returns:
|
||||||
|
GettedMembersChat: Список участников.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetMembersChat(
|
return await GetMembersChat(
|
||||||
@@ -654,10 +757,12 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает участника чата.
|
Получает участника чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param user_id: ID участника
|
chat_id (int): ID чата.
|
||||||
|
user_id (int): ID участника.
|
||||||
|
|
||||||
:return: Участник
|
Returns:
|
||||||
|
Optional[ChatMember]: Участник.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
members = await self.get_chat_members(
|
members = await self.get_chat_members(
|
||||||
@@ -679,10 +784,12 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Добавляет участников в чат.
|
Добавляет участников в чат.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param user_ids: Список ID пользователей
|
chat_id (int): ID чата.
|
||||||
|
user_ids (List[int]): Список ID пользователей.
|
||||||
|
|
||||||
:return: Результат добавления
|
Returns:
|
||||||
|
AddedMembersChat: Результат добавления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await AddMembersChat(
|
return await AddMembersChat(
|
||||||
@@ -701,11 +808,13 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Исключает участника из чата.
|
Исключает участника из чата.
|
||||||
|
|
||||||
:param chat_id: ID чата
|
Args:
|
||||||
:param user_id: ID пользователя
|
chat_id (int): ID чата.
|
||||||
:param block: Блокировать пользователя (по умолчанию False)
|
user_id (int): ID пользователя.
|
||||||
|
block (bool): Блокировать пользователя (по умолчанию False).
|
||||||
|
|
||||||
:return: Результат исключения
|
Returns:
|
||||||
|
RemovedMemberChat: Результат исключения.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await RemoveMemberChat(
|
return await RemoveMemberChat(
|
||||||
@@ -722,7 +831,8 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает обновления для бота.
|
Получает обновления для бота.
|
||||||
|
|
||||||
:return: Список обновлений
|
Returns:
|
||||||
|
Dict: Список обновлений.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetUpdates(
|
return await GetUpdates(
|
||||||
@@ -737,9 +847,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Получает URL для загрузки файлов.
|
Получает URL для загрузки файлов.
|
||||||
|
|
||||||
:param type: Тип загружаемого файла
|
Args:
|
||||||
|
type (UploadType): Тип загружаемого файла.
|
||||||
|
|
||||||
:return: URL для загрузки
|
Returns:
|
||||||
|
GettedUploadUrl: URL для загрузки.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await GetUploadURL(
|
return await GetUploadURL(
|
||||||
@@ -755,9 +867,11 @@ class Bot(BaseConnection):
|
|||||||
"""
|
"""
|
||||||
Устанавливает список команд бота.
|
Устанавливает список команд бота.
|
||||||
|
|
||||||
:param commands: Список команд
|
Args:
|
||||||
|
*commands (BotCommand): Список команд.
|
||||||
|
|
||||||
:return: Обновленная информация о боте
|
Returns:
|
||||||
|
User: Обновленная информация о боте.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await ChangeInfo(
|
return await ChangeInfo(
|
||||||
@@ -765,26 +879,76 @@ class Bot(BaseConnection):
|
|||||||
commands=list(commands)
|
commands=list(commands)
|
||||||
).fetch()
|
).fetch()
|
||||||
|
|
||||||
async def download_file(
|
async def get_subscriptions(self) -> GettedSubscriptions:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Получает список всех подписок.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
GettedSubscriptions: Объект со списком подписок.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await GetSubscriptions(bot=self).fetch()
|
||||||
|
|
||||||
|
async def subscribe_webhook(
|
||||||
self,
|
self,
|
||||||
path: str,
|
|
||||||
url: str,
|
url: str,
|
||||||
token: str
|
update_types: Optional[List[UpdateType]] = None,
|
||||||
):
|
secret: Optional[str] = None
|
||||||
|
) -> Subscribed:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
|
Подписывает бота на получение обновлений через WebHook.
|
||||||
|
|
||||||
:param path: Путь сохранения медиа
|
Args:
|
||||||
:param url: Ссылка на медиа
|
url (str): URL HTTP(S)-эндпойнта вашего бота.
|
||||||
:param token: Токен медиа
|
update_types (Optional[List[UpdateType]]): Список типов обновлений.
|
||||||
|
secret (Optional[str]): Секрет для Webhook.
|
||||||
|
|
||||||
:return: Числовой статус
|
Returns:
|
||||||
|
Subscribed: Результат подписки.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await DownloadMedia(
|
return await SubscribeWebhook(
|
||||||
bot=self,
|
bot=self,
|
||||||
path=path,
|
url=url,
|
||||||
media_url=url,
|
update_types=update_types,
|
||||||
media_token=token
|
secret=secret
|
||||||
).fetch()
|
).fetch()
|
||||||
|
|
||||||
|
async def unsubscribe_webhook(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
) -> Unsubscribed:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отписывает бота от получения обновлений через WebHook.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): URL HTTP(S)-эндпойнта вашего бота.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Unsubscribed: Результат отписки.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return await UnsubscribeWebhook(
|
||||||
|
bot=self,
|
||||||
|
url=url,
|
||||||
|
).fetch()
|
||||||
|
|
||||||
|
async def delete_webhook(self):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Удаляет все подписки на Webhook.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
subs = await self.get_subscriptions()
|
||||||
|
if subs.subscriptions:
|
||||||
|
|
||||||
|
for sub in subs.subscriptions:
|
||||||
|
|
||||||
|
await self.unsubscribe_webhook(sub.url)
|
||||||
|
logger_bot.info('Удалена подписка на Webhook: %s', sub.url)
|
||||||
|
|||||||
@@ -1,10 +1,27 @@
|
|||||||
|
|
||||||
|
|
||||||
from aiohttp import ClientTimeout
|
from aiohttp import ClientTimeout
|
||||||
|
|
||||||
|
|
||||||
class DefaultConnectionProperties:
|
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):
|
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.timeout = ClientTimeout(total=timeout, sock_connect=sock_connect)
|
||||||
self.kwargs = kwargs
|
self.kwargs = kwargs
|
||||||
@@ -30,9 +30,7 @@ class BaseConnection:
|
|||||||
"""
|
"""
|
||||||
Базовый класс для всех методов API.
|
Базовый класс для всех методов API.
|
||||||
|
|
||||||
Содержит общую логику выполнения запроса (например, сериализацию, отправку HTTP-запроса, обработку ответа).
|
Содержит общую логику выполнения запроса (сериализация, отправка HTTP-запроса, обработка ответа).
|
||||||
|
|
||||||
Метод request() может быть переопределён в потомках при необходимости.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
API_URL = 'https://botapi.max.ru'
|
API_URL = 'https://botapi.max.ru'
|
||||||
@@ -41,6 +39,16 @@ class BaseConnection:
|
|||||||
AFTER_MEDIA_INPUT_DELAY = 2.0
|
AFTER_MEDIA_INPUT_DELAY = 2.0
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация BaseConnection.
|
||||||
|
|
||||||
|
Атрибуты:
|
||||||
|
bot (Optional[Bot]): Экземпляр бота.
|
||||||
|
session (Optional[ClientSession]): aiohttp-сессия.
|
||||||
|
after_input_media_delay (float): Задержка после ввода медиа.
|
||||||
|
"""
|
||||||
|
|
||||||
self.bot: Optional[Bot] = None
|
self.bot: Optional[Bot] = None
|
||||||
self.session: Optional[ClientSession] = None
|
self.session: Optional[ClientSession] = None
|
||||||
self.after_input_media_delay: float = self.AFTER_MEDIA_INPUT_DELAY
|
self.after_input_media_delay: float = self.AFTER_MEDIA_INPUT_DELAY
|
||||||
@@ -55,18 +63,22 @@ class BaseConnection:
|
|||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Выполняет HTTP-запрос к API, используя указанные параметры.
|
Выполняет HTTP-запрос к API.
|
||||||
|
|
||||||
:param method: HTTP-метод запроса (GET, POST и т.д.)
|
Args:
|
||||||
:param path: Путь к конечной точке API
|
method (HTTPMethod): HTTP-метод (GET, POST и т.д.).
|
||||||
:param model: Pydantic-модель, в которую будет десериализован ответ (если is_return_raw=False)
|
path (ApiPath | str): Путь до конечной точки.
|
||||||
:param is_return_raw: Если True — вернуть "сырое" тело ответа, иначе — результат десериализации в model
|
model (BaseModel | Any, optional): Pydantic-модель для десериализации ответа, если is_return_raw=False.
|
||||||
:param kwargs: Дополнительные параметры (например, query, headers, json)
|
is_return_raw (bool, optional): Если True — вернуть сырой ответ, иначе — результат десериализации.
|
||||||
|
**kwargs: Дополнительные параметры (query, headers, json).
|
||||||
|
|
||||||
:return:
|
Returns:
|
||||||
- Объект model (если is_return_raw=False и model задан)
|
model | dict | Error: Объект модели, dict или ошибка.
|
||||||
|
|
||||||
- dict (если is_return_raw=True)
|
Raises:
|
||||||
|
RuntimeError: Если бот не инициализирован.
|
||||||
|
MaxConnection: Ошибка соединения.
|
||||||
|
InvalidToken: Ошибка авторизации (401).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.bot is None:
|
if self.bot is None:
|
||||||
@@ -121,14 +133,17 @@ class BaseConnection:
|
|||||||
path: str,
|
path: str,
|
||||||
type: UploadType
|
type: UploadType
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Загружает файл на указанный URL.
|
Загружает файл на сервер.
|
||||||
|
|
||||||
:param url: Конечная точка загрузки файла
|
Args:
|
||||||
:param path: Путь к локальному файлу
|
url (str): URL загрузки.
|
||||||
:param type: Тип файла (video, image, audio, file)
|
path (str): Путь к файлу.
|
||||||
|
type (UploadType): Тип файла.
|
||||||
|
|
||||||
:return: Сырой .text() ответ от сервера после загрузки файла
|
Returns:
|
||||||
|
str: Сырой .text() ответ от сервера.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async with aiofiles.open(path, 'rb') as f:
|
async with aiofiles.open(path, 'rb') as f:
|
||||||
@@ -160,14 +175,18 @@ class BaseConnection:
|
|||||||
buffer: bytes,
|
buffer: bytes,
|
||||||
type: UploadType
|
type: UploadType
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Загружает файл из буфера.
|
Загружает файл из буфера.
|
||||||
|
|
||||||
:param url: Конечная точка загрузки файла
|
Args:
|
||||||
:param buffer: Буфер (bytes)
|
filename (str): Имя файла.
|
||||||
:param type: Тип файла (video, image, audio, file)
|
url (str): URL загрузки.
|
||||||
|
buffer (bytes): Буфер данных.
|
||||||
|
type (UploadType): Тип файла.
|
||||||
|
|
||||||
:return: Сырой .text() ответ от сервера после загрузки файла
|
Returns:
|
||||||
|
str: Сырой .text() ответ от сервера.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -198,32 +217,3 @@ class BaseConnection:
|
|||||||
data=form
|
data=form
|
||||||
)
|
)
|
||||||
return await response.text()
|
return await response.text()
|
||||||
|
|
||||||
async def download_file(
|
|
||||||
self,
|
|
||||||
path: str,
|
|
||||||
url: str,
|
|
||||||
token: str,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
|
|
||||||
|
|
||||||
:param path: Путь сохранения медиа
|
|
||||||
:param url: Ссылка на медиа
|
|
||||||
:param token: Токен медиа
|
|
||||||
|
|
||||||
:return: Числовой статус
|
|
||||||
"""
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'Authorization': f'Bearer {token}'
|
|
||||||
}
|
|
||||||
|
|
||||||
async with ClientSession() as session:
|
|
||||||
async with session.get(url, headers=headers) as response:
|
|
||||||
|
|
||||||
if response.status == 200:
|
|
||||||
async with aiofiles.open(path, 'wb') as f:
|
|
||||||
await f.write(await response.read())
|
|
||||||
|
|
||||||
return response.status
|
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class State:
|
class State:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Представляет отдельное состояние в FSM-группе.
|
Представляет отдельное состояние в FSM-группе.
|
||||||
|
|
||||||
@@ -16,6 +20,7 @@ class State:
|
|||||||
|
|
||||||
|
|
||||||
class StatesGroup:
|
class StatesGroup:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Базовый класс для описания группы состояний FSM.
|
Базовый класс для описания группы состояний FSM.
|
||||||
|
|
||||||
@@ -23,11 +28,13 @@ class StatesGroup:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def states(cls) -> list[str]:
|
def states(cls) -> List[str]:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Получить список всех состояний в формате 'ИмяКласса:имя_состояния'.
|
Получить список всех состояний в формате 'ИмяКласса:имя_состояния'.
|
||||||
|
|
||||||
:return: Список строковых представлений состояний
|
Returns:
|
||||||
|
Список строковых представлений состояний
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return [str(getattr(cls, attr)) for attr in dir(cls)
|
return [str(getattr(cls, attr)) for attr in dir(cls)
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from typing import Any, Callable, Dict, List, TYPE_CHECKING, Optional
|
import functools
|
||||||
|
from typing import Any, Awaitable, Callable, Dict, List, TYPE_CHECKING, Literal, Optional
|
||||||
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
|
||||||
|
|
||||||
from aiohttp import ClientConnectorError
|
from aiohttp import ClientConnectorError
|
||||||
|
|
||||||
|
from maxapi.exceptions.dispatcher import HandlerException
|
||||||
|
|
||||||
|
from .filters.filter import BaseFilter
|
||||||
from .filters.middleware import BaseMiddleware
|
from .filters.middleware import BaseMiddleware
|
||||||
from .filters.handler import Handler
|
from .filters.handler import Handler
|
||||||
|
|
||||||
@@ -58,6 +62,9 @@ class Dispatcher:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Инициализация диспетчера.
|
Инициализация диспетчера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
router_id (str | None): Идентификатор роутера для логов.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.router_id = router_id
|
self.router_id = router_id
|
||||||
@@ -66,11 +73,13 @@ class Dispatcher:
|
|||||||
self.contexts: List[MemoryContext] = []
|
self.contexts: List[MemoryContext] = []
|
||||||
self.routers: List[Router | Dispatcher] = []
|
self.routers: List[Router | Dispatcher] = []
|
||||||
self.filters: List[MagicFilter] = []
|
self.filters: List[MagicFilter] = []
|
||||||
|
self.base_filters: List[BaseFilter] = []
|
||||||
self.middlewares: List[BaseMiddleware] = []
|
self.middlewares: List[BaseMiddleware] = []
|
||||||
|
|
||||||
self.bot: Optional[Bot] = None
|
self.bot: Optional[Bot] = None
|
||||||
self.webhook_app: Optional[FastAPI] = None
|
self.webhook_app: Optional[FastAPI] = None
|
||||||
self.on_started_func: Optional[Callable] = 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)
|
||||||
@@ -80,6 +89,7 @@ class Dispatcher:
|
|||||||
self.dialog_cleared = Event(update_type=UpdateType.DIALOG_CLEARED, 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_muted = Event(update_type=UpdateType.DIALOG_MUTED, router=self)
|
||||||
self.dialog_unmuted = Event(update_type=UpdateType.DIALOG_UNMUTED, 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)
|
||||||
@@ -118,26 +128,90 @@ class Dispatcher:
|
|||||||
|
|
||||||
logger_dp.info(f'Бот: @{me.username} first_name={me.first_name} id={me.user_id}')
|
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'):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Добавляет указанные роутеры в диспетчер.
|
Добавляет указанные роутеры в диспетчер.
|
||||||
|
|
||||||
:param routers: Роутеры для добавления.
|
Args:
|
||||||
|
*routers (Router): Роутеры для добавления.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.routers += [r for r in routers]
|
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):
|
async def __ready(self, bot: Bot):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Подготавливает диспетчер: сохраняет бота, регистрирует обработчики, вызывает on_started.
|
Подготавливает диспетчер: сохраняет бота, регистрирует обработчики, вызывает on_started.
|
||||||
|
|
||||||
:param bot: Экземпляр бота.
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.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()
|
await self.check_me()
|
||||||
|
|
||||||
self.routers += [self]
|
self.routers += [self]
|
||||||
@@ -152,11 +226,14 @@ class Dispatcher:
|
|||||||
def __get_memory_context(self, chat_id: int, user_id: int):
|
def __get_memory_context(self, chat_id: int, user_id: int):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Возвращает существующий или создает новый контекст по chat_id и user_id.
|
Возвращает существующий или создаёт новый MemoryContext по chat_id и user_id.
|
||||||
|
|
||||||
:param chat_id: Идентификатор чата.
|
Args:
|
||||||
:param user_id: Идентификатор пользователя.
|
chat_id (int): Идентификатор чата.
|
||||||
:return: Объект MemoryContext.
|
user_id (int): Идентификатор пользователя.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MemoryContext: Контекст.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for ctx in self.contexts:
|
for ctx in self.contexts:
|
||||||
@@ -167,44 +244,70 @@ class Dispatcher:
|
|||||||
self.contexts.append(new_ctx)
|
self.contexts.append(new_ctx)
|
||||||
return new_ctx
|
return new_ctx
|
||||||
|
|
||||||
async def process_middlewares(
|
async def call_handler(
|
||||||
self,
|
self,
|
||||||
middlewares: List[BaseMiddleware],
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
||||||
event_object: UpdateUnion,
|
event_object: UpdateType,
|
||||||
result_data_kwargs: Dict[str, Any]
|
data: Dict[str, Any]
|
||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Последовательно обрабатывает middleware цепочку.
|
Вызывает хендлер с нужными аргументами.
|
||||||
|
|
||||||
:param middlewares: Список middleware.
|
Args:
|
||||||
:param event_object: Объект события.
|
handler: Handler.
|
||||||
:param result_data_kwargs: Аргументы, передаваемые обработчику.
|
event_object: Объект события.
|
||||||
:return: Изменённые аргументы или None.
|
data: Данные для хендлера.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for middleware in middlewares:
|
func_args = handler.func_event.__annotations__.keys()
|
||||||
result = await middleware.process_middleware(
|
kwargs_filtered = {k: v for k, v in data.items() if k in func_args}
|
||||||
event_object=event_object,
|
|
||||||
result_data_kwargs=result_data_kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
if result is None or result is False:
|
if kwargs_filtered:
|
||||||
return
|
await handler.func_event(event_object, **kwargs_filtered)
|
||||||
|
else:
|
||||||
|
await handler.func_event(event_object)
|
||||||
|
|
||||||
elif result is True:
|
async def process_base_filters(
|
||||||
continue
|
self,
|
||||||
|
event: UpdateUnion,
|
||||||
|
filters: List[BaseFilter]
|
||||||
|
) -> Optional[Dict[str, Any]] | Literal[False]:
|
||||||
|
|
||||||
result_data_kwargs.update(result)
|
"""
|
||||||
|
Асинхронно применяет фильтры к событию.
|
||||||
|
|
||||||
return result_data_kwargs
|
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.
|
Основной обработчик события. Применяет фильтры, middleware и вызывает нужный handler.
|
||||||
|
|
||||||
:param event_object: Событие, пришедшее в бот.
|
Args:
|
||||||
|
event_object (UpdateUnion): Событие.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -229,12 +332,17 @@ class Dispatcher:
|
|||||||
if not filter_attrs(event_object, *router.filters):
|
if not filter_attrs(event_object, *router.filters):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
kwargs = await self.process_middlewares(
|
result_router_filter = await self.process_base_filters(
|
||||||
middlewares=router.middlewares,
|
event=event_object,
|
||||||
event_object=event_object,
|
filters=router.base_filters
|
||||||
result_data_kwargs=kwargs
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(result_router_filter, dict):
|
||||||
|
kwargs.update(result_router_filter)
|
||||||
|
|
||||||
|
elif not result_router_filter:
|
||||||
|
continue
|
||||||
|
|
||||||
for handler in router.event_handlers:
|
for handler in router.event_handlers:
|
||||||
|
|
||||||
if not handler.update_type == event_object.update_type:
|
if not handler.update_type == event_object.update_type:
|
||||||
@@ -244,52 +352,76 @@ class Dispatcher:
|
|||||||
if not filter_attrs(event_object, *handler.filters):
|
if not filter_attrs(event_object, *handler.filters):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not handler.state == current_state and handler.state:
|
if handler.states:
|
||||||
|
if current_state not in handler.states:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
func_args = handler.func_event.__annotations__.keys()
|
func_args = handler.func_event.__annotations__.keys()
|
||||||
|
|
||||||
kwargs = await self.process_middlewares(
|
if handler.base_filters:
|
||||||
middlewares=handler.middlewares,
|
result_filter = await self.process_base_filters(
|
||||||
event_object=event_object,
|
event=event_object,
|
||||||
result_data_kwargs=kwargs
|
filters=handler.base_filters
|
||||||
)
|
)
|
||||||
|
|
||||||
if not kwargs:
|
if isinstance(result_filter, dict):
|
||||||
|
kwargs.update(result_filter)
|
||||||
|
|
||||||
|
elif not result_filter:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for key in kwargs.copy().keys():
|
if isinstance(router, Router):
|
||||||
if key not in func_args:
|
full_middlewares = self.middlewares + router.middlewares + handler.middlewares
|
||||||
del kwargs[key]
|
elif isinstance(router, Dispatcher):
|
||||||
|
full_middlewares = self.middlewares + handler.middlewares
|
||||||
|
|
||||||
await handler.func_event(event_object, **kwargs)
|
handler_chain = self.build_middleware_chain(
|
||||||
|
full_middlewares,
|
||||||
|
functools.partial(self.call_handler, handler)
|
||||||
|
)
|
||||||
|
|
||||||
logger_dp.info(f'Обработано: {router_id} | {process_info}')
|
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'Проигнорировано: {router_id} | {process_info}')
|
logger_dp.info(f'Проигнорировано: router_id: {router_id} | {process_info}')
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger_dp.error(f"Ошибка при обработке события: {router_id} | {process_info} | {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):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Запускает цикл получения обновлений с сервера (long polling).
|
Запускает цикл получения обновлений (long polling).
|
||||||
|
|
||||||
:param bot: Экземпляр бота.
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
self.polling = True
|
||||||
|
|
||||||
await self.__ready(bot)
|
await self.__ready(bot)
|
||||||
|
|
||||||
while True:
|
|
||||||
|
|
||||||
if self.bot is None:
|
if self.bot is None:
|
||||||
raise RuntimeError('Bot не инициализирован')
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
while self.polling:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
events: Dict = await self.bot.get_updates()
|
events: Dict = await self.bot.get_updates()
|
||||||
except AsyncioTimeoutError:
|
except AsyncioTimeoutError:
|
||||||
@@ -323,9 +455,10 @@ class Dispatcher:
|
|||||||
"""
|
"""
|
||||||
Запускает FastAPI-приложение для приёма обновлений через вебхук.
|
Запускает FastAPI-приложение для приёма обновлений через вебхук.
|
||||||
|
|
||||||
:param bot: Экземпляр бота.
|
Args:
|
||||||
:param host: Хост, на котором запускается сервер.
|
bot (Bot): Экземпляр бота.
|
||||||
:param port: Порт сервера.
|
host (str): Хост сервера.
|
||||||
|
port (int): Порт сервера.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not FASTAPI_INSTALLED:
|
if not FASTAPI_INSTALLED:
|
||||||
@@ -346,19 +479,6 @@ class Dispatcher:
|
|||||||
'\n\t pip install maxapi[webhook]'
|
'\n\t pip install maxapi[webhook]'
|
||||||
)
|
)
|
||||||
|
|
||||||
# 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]'
|
|
||||||
# )
|
|
||||||
|
|
||||||
|
|
||||||
@self.webhook_post('/')
|
@self.webhook_post('/')
|
||||||
async def _(request: Request):
|
async def _(request: Request):
|
||||||
event_json = await request.json()
|
event_json = await request.json()
|
||||||
@@ -381,24 +501,14 @@ class Dispatcher:
|
|||||||
async def init_serve(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
|
async def init_serve(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Запускает сервер для обработки входящих вебхуков.
|
Запускает сервер для обработки вебхуков.
|
||||||
|
|
||||||
:param bot: Экземпляр бота.
|
Args:
|
||||||
:param host: Хост, на котором запускается сервер.
|
bot (Bot): Экземпляр бота.
|
||||||
:param port: Порт сервера.
|
host (str): Хост.
|
||||||
|
port (int): Порт.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# try:
|
|
||||||
# from uvicorn import Config, Server
|
|
||||||
# except ImportError:
|
|
||||||
# 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 not UVICORN_INSTALLED:
|
if not UVICORN_INSTALLED:
|
||||||
raise ImportError(
|
raise ImportError(
|
||||||
'\n\t Не установлен uvicorn!'
|
'\n\t Не установлен uvicorn!'
|
||||||
@@ -426,6 +536,14 @@ class Router(Dispatcher):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, router_id: str | None = None):
|
def __init__(self, router_id: str | None = None):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Инициализация роутера.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
router_id (str | None): Идентификатор роутера для логов.
|
||||||
|
"""
|
||||||
|
|
||||||
super().__init__(router_id)
|
super().__init__(router_id)
|
||||||
|
|
||||||
|
|
||||||
@@ -440,8 +558,9 @@ class Event:
|
|||||||
"""
|
"""
|
||||||
Инициализирует событие-декоратор.
|
Инициализирует событие-декоратор.
|
||||||
|
|
||||||
:param update_type: Тип события (UpdateType).
|
Args:
|
||||||
:param router: Роутер или диспетчер, в который регистрируется обработчик.
|
update_type (UpdateType): Тип события.
|
||||||
|
router (Dispatcher | Router): Экземпляр роутера или диспетчера.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.update_type = update_type
|
self.update_type = update_type
|
||||||
@@ -452,7 +571,8 @@ class Event:
|
|||||||
"""
|
"""
|
||||||
Регистрирует функцию как обработчик события.
|
Регистрирует функцию как обработчик события.
|
||||||
|
|
||||||
:return: Исходная функция.
|
Returns:
|
||||||
|
Callable: Исходная функция.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func_event: Callable):
|
def decorator(func_event: Callable):
|
||||||
|
|||||||
@@ -20,3 +20,4 @@ class ApiPath(str, Enum):
|
|||||||
MEMBERS = '/members'
|
MEMBERS = '/members'
|
||||||
ADMINS = '/admins'
|
ADMINS = '/admins'
|
||||||
UPLOADS = '/uploads'
|
UPLOADS = '/uploads'
|
||||||
|
SUBSCRIPTIONS = '/subscriptions'
|
||||||
@@ -24,6 +24,7 @@ class UpdateType(str, Enum):
|
|||||||
DIALOG_CLEARED = 'dialog_cleared'
|
DIALOG_CLEARED = 'dialog_cleared'
|
||||||
DIALOG_MUTED = 'dialog_muted'
|
DIALOG_MUTED = 'dialog_muted'
|
||||||
DIALOG_UNMUTED = 'dialog_unmuted'
|
DIALOG_UNMUTED = 'dialog_unmuted'
|
||||||
|
DIALOG_REMOVED = 'dialog_removed'
|
||||||
|
|
||||||
# Для начинки диспатчера
|
# Для начинки диспатчера
|
||||||
ON_STARTED = 'on_started'
|
ON_STARTED = 'on_started'
|
||||||
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)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
class NotAvailableForDownload(BaseException):
|
class NotAvailableForDownload(Exception):
|
||||||
...
|
...
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
class InvalidToken(BaseException):
|
class InvalidToken(Exception):
|
||||||
...
|
...
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
|
|
||||||
class MaxConnection(BaseException):
|
class MaxConnection(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class MaxUploadFileFailed(BaseException):
|
class MaxUploadFileFailed(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class MaxIconParamsException(BaseException):
|
class MaxIconParamsException(Exception):
|
||||||
...
|
...
|
||||||
@@ -1,16 +1,26 @@
|
|||||||
from magic_filter import MagicFilter
|
from magic_filter import MagicFilter
|
||||||
|
from .filter import BaseFilter
|
||||||
|
|
||||||
F = MagicFilter()
|
F = MagicFilter()
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BaseFilter'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
|
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Применяет один или несколько фильтров MagicFilter к объекту.
|
Применяет один или несколько фильтров MagicFilter к объекту.
|
||||||
|
|
||||||
:param obj: Любой объект с атрибутами (например, event/message)
|
Args:
|
||||||
:param filters: Один или несколько MagicFilter выражений
|
obj (object): Объект, к которому применяются фильтры (например, event или message).
|
||||||
:return: True, если все фильтры возвращают True, иначе False
|
*filters (MagicFilter): Один или несколько выражений MagicFilter.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True, если все фильтры возвращают True, иначе False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return all(f.resolve(obj) for f in filters)
|
return all(f.resolve(obj) for f in filters)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
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,11 +1,10 @@
|
|||||||
from typing import Callable, List, Optional
|
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 ..filters.middleware import BaseMiddleware
|
||||||
|
|
||||||
from ..types.command import Command, CommandStart
|
|
||||||
|
|
||||||
from ..context.state_machine import State
|
from ..context.state_machine import State
|
||||||
|
|
||||||
from ..enums.update import UpdateType
|
from ..enums.update import UpdateType
|
||||||
@@ -18,7 +17,7 @@ class Handler:
|
|||||||
"""
|
"""
|
||||||
Обработчик события.
|
Обработчик события.
|
||||||
|
|
||||||
Позволяет связать функцию-обработчик с типом обновления, состоянием и набором фильтров.
|
Связывает функцию-обработчик с типом события, состояниями и фильтрами.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -30,32 +29,31 @@ class Handler:
|
|||||||
):
|
):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Инициализация обработчика.
|
Создаёт обработчик события.
|
||||||
|
|
||||||
:param args: Список фильтров и состояний, в том числе:
|
Args:
|
||||||
- MagicFilter — фильтр события,
|
*args: Список фильтров (MagicFilter, State, Command, BaseFilter, BaseMiddleware).
|
||||||
- State — состояние FSM,
|
func_event (Callable): Функция-обработчик.
|
||||||
- Command — команда для фильтрации по началу текста сообщения.
|
update_type (UpdateType): Тип обновления.
|
||||||
:param func_event: Функция-обработчик события
|
|
||||||
:param update_type: Тип обновления (события), на которое подписан обработчик
|
|
||||||
:param kwargs: Дополнительные параметры (не используются)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.func_event: Callable = func_event
|
self.func_event: Callable = func_event
|
||||||
self.update_type: UpdateType = update_type
|
self.update_type: UpdateType = update_type
|
||||||
self.filters = []
|
self.filters: Optional[List[MagicFilter]] = []
|
||||||
self.state: Optional[State] = None
|
self.base_filters: Optional[List[BaseFilter]] = []
|
||||||
|
self.states: Optional[List[State]] = []
|
||||||
self.middlewares: List[BaseMiddleware] = []
|
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, CommandStart)):
|
|
||||||
self.filters.insert(0, F.message.body.text.split()[0] == arg.command)
|
|
||||||
elif isinstance(arg, BaseMiddleware):
|
elif isinstance(arg, BaseMiddleware):
|
||||||
self.middlewares.append(arg)
|
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__}`'
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
from typing import Any, Dict
|
from typing import Any, Callable, Awaitable
|
||||||
from ..types.updates import UpdateUnion
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMiddleware:
|
class BaseMiddleware:
|
||||||
def __init__(self):
|
|
||||||
...
|
|
||||||
|
|
||||||
async def process_middleware(
|
"""
|
||||||
|
Базовый класс для мидлварей.
|
||||||
|
|
||||||
|
Используется для обработки события до и после вызова хендлера.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
result_data_kwargs: Dict[str, Any],
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
||||||
event_object: UpdateUnion
|
event_object: Any,
|
||||||
):
|
data: dict[str, Any]
|
||||||
|
) -> Any:
|
||||||
|
|
||||||
# пока что заглушка
|
"""
|
||||||
if result_data_kwargs is None:
|
Вызывает хендлер с переданным событием и данными.
|
||||||
return {}
|
|
||||||
|
|
||||||
kwargs_temp = {'data': result_data_kwargs.copy()}
|
Args:
|
||||||
|
handler (Callable): Хендлер события.
|
||||||
|
event_object (Any): Событие.
|
||||||
|
data (dict): Дополнительные данные.
|
||||||
|
|
||||||
for key in kwargs_temp.copy().keys():
|
Returns:
|
||||||
if key not in self.__call__.__annotations__.keys(): # type: ignore
|
Any: Результат работы хендлера.
|
||||||
del kwargs_temp[key]
|
"""
|
||||||
|
|
||||||
result: Dict[str, Any] = await self(event_object, **kwargs_temp) # type: ignore
|
return await handler(event_object, data)
|
||||||
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
from typing import Any, Dict, List, TYPE_CHECKING, Optional
|
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
|
||||||
|
|
||||||
@@ -23,7 +25,7 @@ class ChangeInfo(BaseConnection):
|
|||||||
name (str, optional): Новое имя бота
|
name (str, optional): Новое имя бота
|
||||||
description (str, optional): Новое описание
|
description (str, optional): Новое описание
|
||||||
commands (List[BotCommand], optional): Список команд
|
commands (List[BotCommand], optional): Список команд
|
||||||
photo (Dict[str, Any], optional): Данные фото
|
photo (PhotoAttachmentRequestPayload, optional): Данные фото
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -32,7 +34,7 @@ class ChangeInfo(BaseConnection):
|
|||||||
name: Optional[str] = None,
|
name: Optional[str] = None,
|
||||||
description: Optional[str] = None,
|
description: Optional[str] = None,
|
||||||
commands: Optional[List[BotCommand]] = None,
|
commands: Optional[List[BotCommand]] = None,
|
||||||
photo: Optional[Dict[str, Any]] = None
|
photo: Optional[PhotoAttachmentRequestPayload] = None
|
||||||
):
|
):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.name = name
|
self.name = name
|
||||||
@@ -60,7 +62,7 @@ class ChangeInfo(BaseConnection):
|
|||||||
if self.commands:
|
if self.commands:
|
||||||
json['commands'] = [command.model_dump() for command in self.commands]
|
json['commands'] = [command.model_dump() for command in self.commands]
|
||||||
if self.photo:
|
if self.photo:
|
||||||
json['photo'] = self.photo
|
json['photo'] = self.photo.model_dump()
|
||||||
|
|
||||||
return await super().request(
|
return await super().request(
|
||||||
method=HTTPMethod.PATCH,
|
method=HTTPMethod.PATCH,
|
||||||
|
|||||||
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
|
||||||
|
)
|
||||||
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
|
||||||
|
)
|
||||||
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
|
||||||
|
)
|
||||||
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]
|
||||||
@@ -18,6 +18,7 @@ from ...types.updates.user_removed import UserRemoved
|
|||||||
from ...types.updates.dialog_cleared import DialogCleared
|
from ...types.updates.dialog_cleared import DialogCleared
|
||||||
from ...types.updates.dialog_muted import DialogMuted
|
from ...types.updates.dialog_muted import DialogMuted
|
||||||
from ...types.updates.dialog_unmuted import DialogUnmuted
|
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
|
||||||
@@ -38,7 +39,8 @@ UPDATE_MODEL_MAPPING = {
|
|||||||
UpdateType.BOT_STOPPED: BotStopped,
|
UpdateType.BOT_STOPPED: BotStopped,
|
||||||
UpdateType.DIALOG_CLEARED: DialogCleared,
|
UpdateType.DIALOG_CLEARED: DialogCleared,
|
||||||
UpdateType.DIALOG_MUTED: DialogMuted,
|
UpdateType.DIALOG_MUTED: DialogMuted,
|
||||||
UpdateType.DIALOG_UNMUTED: DialogUnmuted
|
UpdateType.DIALOG_UNMUTED: DialogUnmuted,
|
||||||
|
UpdateType.DIALOG_REMOVED: DialogRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
maxapi/methods/types/subscribed.py
Normal file
16
maxapi/methods/types/subscribed.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Subscribed(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Результат подписки на обновления на Webhook
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
message: Optional[str] = None
|
||||||
16
maxapi/methods/types/unsubscribed.py
Normal file
16
maxapi/methods/types/unsubscribed.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Unsubscribed(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Результат отписки от обновлений на Webhook
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
success (bool): Статус успешности операции.
|
||||||
|
message (Optional[str]): Дополнительное сообщение или ошибка.
|
||||||
|
"""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
message: Optional[str] = None
|
||||||
54
maxapi/methods/unsubscribe_webhook.py
Normal file
54
maxapi/methods/unsubscribe_webhook.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from ..methods.types.unsubscribed import Unsubscribed
|
||||||
|
|
||||||
|
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 UnsubscribeWebhook(BaseConnection):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отписывает бота от получения обновлений через WebHook. После вызова этого метода бот перестает получать уведомления о новых событиях, и доступна доставка уведомлений через API с длительным опросом.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||||
|
url (str): URL, который нужно удалить из подписок на WebHook
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot: 'Bot',
|
||||||
|
url: str,
|
||||||
|
):
|
||||||
|
self.bot = bot
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
async def fetch(self) -> Unsubscribed:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Отправляет запрос на подписку бота на получение обновлений через WebHook
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Unsubscribed: Объект с информацией об операции
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.bot is None:
|
||||||
|
raise RuntimeError('Bot не инициализирован')
|
||||||
|
|
||||||
|
params = self.bot.params.copy()
|
||||||
|
|
||||||
|
params['url'] = self.url
|
||||||
|
|
||||||
|
return await super().request(
|
||||||
|
method=HTTPMethod.DELETE,
|
||||||
|
path=ApiPath.SUBSCRIPTIONS,
|
||||||
|
model=Unsubscribed,
|
||||||
|
params=params,
|
||||||
|
)
|
||||||
@@ -28,14 +28,18 @@ from ..types.attachments.buttons.request_contact import RequestContactButton
|
|||||||
from ..types.attachments.buttons.open_app_button import OpenAppButton
|
from ..types.attachments.buttons.open_app_button import OpenAppButton
|
||||||
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
|
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
|
||||||
from ..types.attachments.buttons.message_button import MessageButton
|
from ..types.attachments.buttons.message_button import MessageButton
|
||||||
from ..types.message import Message
|
from ..types.attachments.image import PhotoAttachmentRequestPayload
|
||||||
|
from ..types.message import Message, NewMessageLink
|
||||||
|
|
||||||
from ..types.command import Command, BotCommand, CommandStart
|
from ..filters.command import Command, CommandStart
|
||||||
|
from ..types.command import BotCommand
|
||||||
|
|
||||||
from .input_media import InputMedia
|
from .input_media import InputMedia
|
||||||
from .input_media import InputMediaBuffer
|
from .input_media import InputMediaBuffer
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
'NewMessageLink',
|
||||||
|
'PhotoAttachmentRequestPayload',
|
||||||
'DialogUnmuted',
|
'DialogUnmuted',
|
||||||
'DialogMuted',
|
'DialogMuted',
|
||||||
'DialogCleared',
|
'DialogCleared',
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from typing import Annotated, Union
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
from ..attachments.share import Share
|
||||||
|
from ..attachments.buttons.attachment_button import AttachmentButton
|
||||||
|
from ..attachments.sticker import Sticker
|
||||||
|
from ..attachments.file import File
|
||||||
|
from ..attachments.image import Image
|
||||||
|
from ..attachments.video import Video
|
||||||
|
from ..attachments.audio import Audio
|
||||||
|
from ..attachments.location import Location
|
||||||
|
from ..attachments.contact import Contact
|
||||||
|
|
||||||
|
|
||||||
|
Attachments = Annotated[Union[
|
||||||
|
Audio,
|
||||||
|
Video,
|
||||||
|
File,
|
||||||
|
Image,
|
||||||
|
Sticker,
|
||||||
|
Share,
|
||||||
|
Location,
|
||||||
|
AttachmentButton,
|
||||||
|
Contact
|
||||||
|
], Field(discriminator='type')]
|
||||||
@@ -114,29 +114,3 @@ class Attachment(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
use_enum_values = True
|
use_enum_values = True
|
||||||
|
|
||||||
# async def download(
|
|
||||||
# self,
|
|
||||||
# path: str
|
|
||||||
# ):
|
|
||||||
|
|
||||||
# """
|
|
||||||
# Скачивает медиа, сохраняя по определенному пути
|
|
||||||
|
|
||||||
# :param path: Путь сохранения медиа
|
|
||||||
|
|
||||||
# :return: Числовой статус
|
|
||||||
# """
|
|
||||||
|
|
||||||
# if not hasattr(self.payload, 'token') or \
|
|
||||||
# not hasattr(self.payload, 'url'):
|
|
||||||
# raise NotAvailableForDownload()
|
|
||||||
|
|
||||||
# elif not self.payload.token or not self.payload.url:
|
|
||||||
# raise NotAvailableForDownload(f'Медиа типа `{self.type}` недоступно для скачивания')
|
|
||||||
|
|
||||||
# return await self.bot.download_file(
|
|
||||||
# path=path,
|
|
||||||
# url=self.payload.url,
|
|
||||||
# token=self.payload.token,
|
|
||||||
# )
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
@@ -11,9 +11,8 @@ class Audio(Attachment):
|
|||||||
Вложение с типом аудио.
|
Вложение с типом аудио.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Literal['audio']): Тип вложения, всегда 'audio'.
|
|
||||||
transcription (Optional[str]): Транскрипция аудио (если есть).
|
transcription (Optional[str]): Транскрипция аудио (если есть).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.AUDIO
|
type: Literal[AttachmentType.AUDIO]
|
||||||
transcription: Optional[str] = None
|
transcription: Optional[str] = None
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
from ..attachment import ButtonsPayload
|
from ....enums.attachment import AttachmentType
|
||||||
|
|
||||||
|
from ..attachment import Attachment
|
||||||
|
|
||||||
|
|
||||||
class AttachmentButton(BaseModel):
|
class AttachmentButton(Attachment):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Модель кнопки вложения для сообщения.
|
Модель кнопки вложения для сообщения.
|
||||||
@@ -14,5 +15,4 @@ class AttachmentButton(BaseModel):
|
|||||||
payload: Полезная нагрузка кнопки (массив рядов кнопок)
|
payload: Полезная нагрузка кнопки (массив рядов кнопок)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: Literal['inline_keyboard'] = 'inline_keyboard'
|
type: Literal[AttachmentType.INLINE_KEYBOARD]
|
||||||
payload: ButtonsPayload
|
|
||||||
@@ -5,7 +5,8 @@ from .button import Button
|
|||||||
|
|
||||||
class RequestGeoLocationButton(Button):
|
class RequestGeoLocationButton(Button):
|
||||||
|
|
||||||
"""Кнопка запроса геолокации пользователя.
|
"""
|
||||||
|
Кнопка запроса геолокации пользователя.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
quick: Если True, запрашивает геолокацию без дополнительного
|
quick: Если True, запрашивает геолокацию без дополнительного
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from typing import Literal
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
from .attachment import Attachment
|
from .attachment import Attachment
|
||||||
@@ -7,9 +8,6 @@ class Contact(Attachment):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
Вложение с типом контакта.
|
Вложение с типом контакта.
|
||||||
|
|
||||||
Attributes:
|
|
||||||
type (Literal['contact']): Тип вложения, всегда 'contact'.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.CONTACT
|
type: Literal[AttachmentType.CONTACT]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
@@ -11,11 +11,10 @@ class File(Attachment):
|
|||||||
Вложение с типом файла.
|
Вложение с типом файла.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Literal['file']): Тип вложения, всегда 'file'.
|
|
||||||
filename (Optional[str]): Имя файла.
|
filename (Optional[str]): Имя файла.
|
||||||
size (Optional[int]): Размер файла в байтах.
|
size (Optional[int]): Размер файла в байтах.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.FILE
|
type: Literal[AttachmentType.FILE]
|
||||||
filename: Optional[str] = None
|
filename: Optional[str] = None
|
||||||
size: Optional[int] = None
|
size: Optional[int] = None
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -31,4 +31,4 @@ class Image(Attachment):
|
|||||||
type (Literal['image']): Тип вложения, всегда 'image'.
|
type (Literal['image']): Тип вложения, всегда 'image'.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.IMAGE
|
type: Literal[AttachmentType.IMAGE]
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
@@ -11,11 +11,10 @@ class Location(Attachment):
|
|||||||
Вложение с типом геолокации.
|
Вложение с типом геолокации.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Literal['location']): Тип вложения, всегда 'location'.
|
|
||||||
latitude (Optional[float]): Широта.
|
latitude (Optional[float]): Широта.
|
||||||
longitude (Optional[float]): Долгота.
|
longitude (Optional[float]): Долгота.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.LOCATION
|
type: Literal[AttachmentType.LOCATION]
|
||||||
latitude: Optional[float] = None
|
latitude: Optional[float] = None
|
||||||
longitude: Optional[float] = None
|
longitude: Optional[float] = None
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
@@ -11,13 +11,12 @@ class Share(Attachment):
|
|||||||
Вложение с типом "share" (поделиться).
|
Вложение с типом "share" (поделиться).
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Literal['share']): Тип вложения, всегда 'share'.
|
|
||||||
title (Optional[str]): Заголовок для шаринга.
|
title (Optional[str]): Заголовок для шаринга.
|
||||||
description (Optional[str]): Описание.
|
description (Optional[str]): Описание.
|
||||||
image_url (Optional[str]): URL изображения для предпросмотра.
|
image_url (Optional[str]): URL изображения для предпросмотра.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.SHARE
|
type: Literal[AttachmentType.SHARE]
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
image_url: Optional[str] = None
|
image_url: Optional[str] = None
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Literal, Optional
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
|
|
||||||
@@ -11,11 +11,10 @@ class Sticker(Attachment):
|
|||||||
Вложение с типом стикера.
|
Вложение с типом стикера.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Literal['sticker']): Тип вложения, всегда 'sticker'.
|
|
||||||
width (Optional[int]): Ширина стикера в пикселях.
|
width (Optional[int]): Ширина стикера в пикселях.
|
||||||
height (Optional[int]): Высота стикера в пикселях.
|
height (Optional[int]): Высота стикера в пикселях.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.STICKER
|
type: Literal[AttachmentType.STICKER]
|
||||||
width: Optional[int] = None
|
width: Optional[int] = None
|
||||||
height: Optional[int] = None
|
height: Optional[int] = None
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import TYPE_CHECKING, Any, Optional
|
from typing import TYPE_CHECKING, Any, Literal, Optional
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...enums.attachment import AttachmentType
|
from ...enums.attachment import AttachmentType
|
||||||
@@ -51,7 +51,6 @@ class Video(Attachment):
|
|||||||
Вложение с типом видео.
|
Вложение с типом видео.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
type (Optional[Literal['video']]): Тип вложения, всегда 'video'.
|
|
||||||
token (Optional[str]): Токен видео.
|
token (Optional[str]): Токен видео.
|
||||||
urls (Optional[VideoUrl]): URLs видео разных разрешений.
|
urls (Optional[VideoUrl]): URLs видео разных разрешений.
|
||||||
thumbnail (VideoThumbnail): Миниатюра видео.
|
thumbnail (VideoThumbnail): Миниатюра видео.
|
||||||
@@ -61,7 +60,7 @@ class Video(Attachment):
|
|||||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
type: AttachmentType = AttachmentType.VIDEO
|
type: Literal[AttachmentType.VIDEO]
|
||||||
token: Optional[str] = None
|
token: Optional[str] = None
|
||||||
urls: Optional[VideoUrl] = None
|
urls: Optional[VideoUrl] = None
|
||||||
thumbnail: VideoThumbnail
|
thumbnail: VideoThumbnail
|
||||||
|
|||||||
@@ -2,33 +2,6 @@ from typing import Optional
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
|
||||||
|
|
||||||
"""
|
|
||||||
Класс для представления команды бота.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
text (str): Текст команды без префикса.
|
|
||||||
prefix (str): Префикс команды. По умолчанию '/'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, text: str, prefix: str = '/'):
|
|
||||||
self.text = text
|
|
||||||
self.prefix = prefix
|
|
||||||
|
|
||||||
@property
|
|
||||||
def command(self):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Возвращает полную команду с префиксом.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Команда, состоящая из префикса и текста.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self.prefix + self.text
|
|
||||||
|
|
||||||
|
|
||||||
class BotCommand(BaseModel):
|
class BotCommand(BaseModel):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@@ -41,18 +14,3 @@ class BotCommand(BaseModel):
|
|||||||
|
|
||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class CommandStart(Command):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Класс для представления команды /start бота.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
prefix (str): Префикс команды. По умолчанию '/'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
text = 'start'
|
|
||||||
|
|
||||||
def __init__(self, prefix: str = '/'):
|
|
||||||
self.prefix = prefix
|
|
||||||
@@ -6,6 +6,7 @@ from ..enums.upload_type import UploadType
|
|||||||
|
|
||||||
|
|
||||||
class InputMedia:
|
class InputMedia:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Класс для представления медиафайла.
|
Класс для представления медиафайла.
|
||||||
|
|
||||||
@@ -15,16 +16,19 @@ class InputMedia:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path: str):
|
def __init__(self, path: str):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Инициализирует объект медиафайла.
|
Инициализирует объект медиафайла.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
path (str): Путь к файлу.
|
path (str): Путь к файлу.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.path = path
|
self.path = path
|
||||||
self.type = self.__detect_file_type(path)
|
self.type = self.__detect_file_type(path)
|
||||||
|
|
||||||
def __detect_file_type(self, path: str) -> UploadType:
|
def __detect_file_type(self, path: str) -> UploadType:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Определяет тип файла на основе его содержимого (MIME-типа).
|
Определяет тип файла на основе его содержимого (MIME-типа).
|
||||||
|
|
||||||
@@ -34,6 +38,7 @@ class InputMedia:
|
|||||||
Returns:
|
Returns:
|
||||||
UploadType: Тип файла (VIDEO, IMAGE, AUDIO или FILE).
|
UploadType: Тип файла (VIDEO, IMAGE, AUDIO или FILE).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
sample = f.read(4096)
|
sample = f.read(4096)
|
||||||
|
|
||||||
@@ -60,6 +65,7 @@ class InputMedia:
|
|||||||
|
|
||||||
|
|
||||||
class InputMediaBuffer:
|
class InputMediaBuffer:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Класс для представления медиафайла из буфера.
|
Класс для представления медиафайла из буфера.
|
||||||
|
|
||||||
@@ -69,6 +75,7 @@ class InputMediaBuffer:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, buffer: bytes, filename: str | None = None):
|
def __init__(self, buffer: bytes, filename: str | None = None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Инициализирует объект медиафайла из буфера.
|
Инициализирует объект медиафайла из буфера.
|
||||||
|
|
||||||
@@ -76,6 +83,7 @@ class InputMediaBuffer:
|
|||||||
buffer (IO): Буфер с содержимым файла.
|
buffer (IO): Буфер с содержимым файла.
|
||||||
filename (str): Название файла (по умолчанию присваивается uuid4).
|
filename (str): Название файла (по умолчанию присваивается uuid4).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.buffer = buffer
|
self.buffer = buffer
|
||||||
self.type = self.__detect_file_type(buffer)
|
self.type = self.__detect_file_type(buffer)
|
||||||
|
|||||||
@@ -3,20 +3,14 @@ from __future__ import annotations
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from typing import Any, Optional, List, Union, TYPE_CHECKING
|
from typing import Any, Optional, List, Union, TYPE_CHECKING
|
||||||
|
|
||||||
|
from ..types.attachments import Attachments
|
||||||
|
|
||||||
from ..enums.text_style import TextStyle
|
from ..enums.text_style import TextStyle
|
||||||
from ..enums.parse_mode import ParseMode
|
from ..enums.parse_mode import ParseMode
|
||||||
from ..enums.chat_type import ChatType
|
from ..enums.chat_type import ChatType
|
||||||
from ..enums.message_link_type import MessageLinkType
|
from ..enums.message_link_type import MessageLinkType
|
||||||
|
|
||||||
from .attachments.attachment import Attachment
|
from .attachments.attachment import Attachment
|
||||||
from .attachments.share import Share
|
|
||||||
from .attachments.buttons.attachment_button import AttachmentButton
|
|
||||||
from .attachments.sticker import Sticker
|
|
||||||
from .attachments.file import File
|
|
||||||
from .attachments.image import Image
|
|
||||||
from .attachments.video import Video
|
|
||||||
from .attachments.audio import Audio
|
|
||||||
from .attachments.location import Location
|
|
||||||
|
|
||||||
from .users import User
|
from .users import User
|
||||||
|
|
||||||
@@ -91,18 +85,7 @@ class MessageBody(BaseModel):
|
|||||||
seq: int
|
seq: int
|
||||||
text: Optional[str] = None
|
text: Optional[str] = None
|
||||||
attachments: Optional[
|
attachments: Optional[
|
||||||
List[
|
List[Attachments]
|
||||||
Union[
|
|
||||||
AttachmentButton,
|
|
||||||
Audio,
|
|
||||||
Video,
|
|
||||||
File,
|
|
||||||
Image,
|
|
||||||
Sticker,
|
|
||||||
Share,
|
|
||||||
Location
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = Field(default_factory=list) # type: ignore
|
] = Field(default_factory=list) # type: ignore
|
||||||
|
|
||||||
markup: Optional[
|
markup: Optional[
|
||||||
|
|||||||
18
maxapi/types/subscription.py
Normal file
18
maxapi/types/subscription.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Subscription(BaseModel):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Подписка для вебхука
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
url (str): URL вебхука
|
||||||
|
time (int): Unix-время, когда была создана подписка
|
||||||
|
update_types (List[str]): Типы обновлений, на которые подписан бот
|
||||||
|
"""
|
||||||
|
|
||||||
|
url: str
|
||||||
|
time: int
|
||||||
|
update_types: Optional[List[str]] = None
|
||||||
30
maxapi/types/updates/dialog_removed.py
Normal file
30
maxapi/types/updates/dialog_removed.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from .update import Update
|
||||||
|
|
||||||
|
from ...types.users import User
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ...bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
class DialogRemoved(Update):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Обновление, сигнализирующее об удалении диалога с ботом.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
chat_id (int): Идентификатор чата.
|
||||||
|
user (User): Пользователь (бот).
|
||||||
|
user_locale (Optional[str]): Локаль пользователя.
|
||||||
|
"""
|
||||||
|
|
||||||
|
chat_id: int
|
||||||
|
user: User
|
||||||
|
user_locale: Optional[str] = None
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
bot: Optional[Bot]
|
||||||
|
|
||||||
|
def get_ids(self):
|
||||||
|
return (self.chat_id, self.user.user_id)
|
||||||
@@ -1,25 +1,18 @@
|
|||||||
from typing import List, Optional, Union
|
from typing import List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ...types.attachments.location import Location
|
from ...types.attachments import Attachments
|
||||||
|
|
||||||
|
|
||||||
from .update import Update
|
from .update import Update
|
||||||
|
|
||||||
from ...enums.parse_mode import ParseMode
|
from ...enums.parse_mode import ParseMode
|
||||||
|
|
||||||
from ...types.message import NewMessageLink
|
from ...types.message import NewMessageLink
|
||||||
from ...types.attachments.share import Share
|
|
||||||
from ...types.callback import Callback
|
from ...types.callback import Callback
|
||||||
from ...types.message import Message
|
from ...types.message import Message
|
||||||
|
|
||||||
from ..attachments.buttons.attachment_button import AttachmentButton
|
|
||||||
from ..attachments.sticker import Sticker
|
|
||||||
from ..attachments.file import File
|
|
||||||
from ..attachments.image import Image
|
|
||||||
from ..attachments.video import Video
|
|
||||||
from ..attachments.audio import Audio
|
|
||||||
|
|
||||||
|
|
||||||
class MessageForCallback(BaseModel):
|
class MessageForCallback(BaseModel):
|
||||||
|
|
||||||
@@ -37,18 +30,7 @@ class MessageForCallback(BaseModel):
|
|||||||
|
|
||||||
text: Optional[str] = None
|
text: Optional[str] = None
|
||||||
attachments: Optional[
|
attachments: Optional[
|
||||||
List[
|
List[Attachments]
|
||||||
Union[
|
|
||||||
AttachmentButton,
|
|
||||||
Audio,
|
|
||||||
Video,
|
|
||||||
File,
|
|
||||||
Image,
|
|
||||||
Sticker,
|
|
||||||
Share,
|
|
||||||
Location
|
|
||||||
]
|
|
||||||
]
|
|
||||||
] = Field(default_factory=list) # type: ignore
|
] = Field(default_factory=list) # type: ignore
|
||||||
link: Optional[NewMessageLink] = None
|
link: Optional[NewMessageLink] = None
|
||||||
notify: Optional[bool] = True
|
notify: Optional[bool] = True
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ from .update import Update
|
|||||||
|
|
||||||
|
|
||||||
class MessageChatCreated(Update):
|
class MessageChatCreated(Update):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Событие создания чата.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
chat (Chat): Объект чата.
|
||||||
|
title (Optional[str]): Название чата.
|
||||||
|
message_id (Optional[str]): ID сообщения.
|
||||||
|
start_payload (Optional[str]): Payload для старта.
|
||||||
|
"""
|
||||||
|
|
||||||
chat: Chat
|
chat: Chat
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
message_id: Optional[str] = None
|
message_id: Optional[str] = None
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ from ...types.users import User
|
|||||||
class UserRemoved(Update):
|
class UserRemoved(Update):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Класс для обработки события удаления пользователя из чата.
|
Класс для обработки события выходе/удаления пользователя из чата.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None.
|
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. None при выходе из чата самим пользователем.
|
||||||
chat_id (int): Идентификатор чата. Может быть None.
|
chat_id (int): Идентификатор чата. Может быть None.
|
||||||
user (User): Объект пользователя, удаленного из чата.
|
user (User): Объект пользователя, удаленного из чата.
|
||||||
is_channel (bool): Указывает, был ли пользователь удален из канала или нет
|
is_channel (bool): Указывает, был ли пользователь удален из канала или нет
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ from ..types.attachments.attachment import Attachment, ButtonsPayload
|
|||||||
|
|
||||||
class InlineKeyboardBuilder:
|
class InlineKeyboardBuilder:
|
||||||
|
|
||||||
"""Конструктор инлайн-клавиатур.
|
"""
|
||||||
|
Конструктор инлайн-клавиатур.
|
||||||
|
|
||||||
Позволяет удобно собирать кнопки в ряды и формировать из них клавиатуру
|
Позволяет удобно собирать кнопки в ряды и формировать из них клавиатуру
|
||||||
для отправки в сообщениях.
|
для отправки в сообщениях.
|
||||||
@@ -16,7 +17,8 @@ class InlineKeyboardBuilder:
|
|||||||
|
|
||||||
def row(self, *buttons: InlineButtonUnion):
|
def row(self, *buttons: InlineButtonUnion):
|
||||||
|
|
||||||
"""Добавить новый ряд кнопок в клавиатуру.
|
"""
|
||||||
|
Добавить новый ряд кнопок в клавиатуру.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
*buttons: Произвольное количество кнопок для добавления в ряд.
|
*buttons: Произвольное количество кнопок для добавления в ряд.
|
||||||
@@ -26,7 +28,8 @@ class InlineKeyboardBuilder:
|
|||||||
|
|
||||||
def add(self, button: InlineButtonUnion):
|
def add(self, button: InlineButtonUnion):
|
||||||
|
|
||||||
"""Добавить кнопку в последний ряд клавиатуры.
|
"""
|
||||||
|
Добавить кнопку в последний ряд клавиатуры.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
button: Кнопка для добавления.
|
button: Кнопка для добавления.
|
||||||
@@ -36,7 +39,8 @@ class InlineKeyboardBuilder:
|
|||||||
|
|
||||||
def as_markup(self):
|
def as_markup(self):
|
||||||
|
|
||||||
"""Собрать клавиатуру в объект для отправки.
|
"""
|
||||||
|
Собрать клавиатуру в объект для отправки.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Объект вложения с типом INLINE_KEYBOARD.
|
Объект вложения с типом INLINE_KEYBOARD.
|
||||||
|
|||||||
@@ -22,8 +22,19 @@ if TYPE_CHECKING:
|
|||||||
from ..bot import Bot
|
from ..bot import Bot
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def enrich_event(event_object: Any, bot: Bot) -> Any:
|
async def enrich_event(event_object: Any, bot: Bot) -> Any:
|
||||||
|
|
||||||
|
"""
|
||||||
|
Дополняет объект события данными чата, пользователя и ссылкой на бота.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
event_object (Any): Событие, которое нужно дополнить.
|
||||||
|
bot (Bot): Экземпляр бота.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Any: Обновлённый объект события.
|
||||||
|
"""
|
||||||
|
|
||||||
if not bot.auto_requests:
|
if not bot.auto_requests:
|
||||||
return event_object
|
return event_object
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "maxapi"
|
name = "maxapi"
|
||||||
version = "0.9.1"
|
version = "0.9.5"
|
||||||
description = "Библиотека для разработки чат-ботов с помощью API мессенджера MAX"
|
description = "Библиотека для разработки чат-ботов с помощью API мессенджера MAX"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
@@ -13,15 +13,16 @@ classifiers = [
|
|||||||
"Programming Language :: Python :: 3.11",
|
"Programming Language :: Python :: 3.11",
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aiohttp>=3.8.0",
|
"aiohttp>=3.12.14",
|
||||||
"fastapi>=0.68.0",
|
|
||||||
"magic_filter>=1.0.0",
|
"magic_filter>=1.0.0",
|
||||||
"pydantic>=1.8.0",
|
"pydantic>=1.8.0",
|
||||||
"uvicorn>=0.15.0",
|
|
||||||
"aiofiles==24.1.0",
|
"aiofiles==24.1.0",
|
||||||
"puremagic==1.30"
|
"puremagic==1.30"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
webhook = ["fastapi>=0.68.0", "uvicorn>=0.15.0"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://github.com/love-apples/maxapi"
|
Homepage = "https://github.com/love-apples/maxapi"
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,14 @@
|
|||||||
| `dialog_cleared` | Пользователь очистил историю диалога с ботом |
|
| `dialog_cleared` | Пользователь очистил историю диалога с ботом |
|
||||||
| `dialog_muted` | Пользователь отключил оповещения от чата бота |
|
| `dialog_muted` | Пользователь отключил оповещения от чата бота |
|
||||||
| `dialog_unmuted` | Пользователь включил оповещения от чата бота |
|
| `dialog_unmuted` | Пользователь включил оповещения от чата бота |
|
||||||
|
| `dialog_removed` | Пользователь удалил диалог с ботом |
|
||||||
| `chat_title_changed` | Изменено название чата |
|
| `chat_title_changed` | Изменено название чата |
|
||||||
| `message_callback` | Пользователь нажал на callback-кнопку (inline button) |
|
| `message_callback` | Пользователь нажал на callback-кнопку (inline button) |
|
||||||
| `message_chat_created`| Срабатывает когда пользователь нажал на кнопку с действием "Создать чат" (работает некорректно со стороны API MAX, ждем исправлений) |
|
| `message_chat_created`| Срабатывает когда пользователь нажал на кнопку с действием "Создать чат" (работает некорректно со стороны API MAX, ждем исправлений) |
|
||||||
| `message_edited` | Сообщение было отредактировано |
|
| `message_edited` | Сообщение было отредактировано |
|
||||||
| `message_removed` | Сообщение было удалено |
|
| `message_removed` | Сообщение было удалено |
|
||||||
| `user_added` | Пользователь добавлен в чат |
|
| `user_added` | Пользователь добавлен в чат |
|
||||||
| `user_removed` | Пользователь удалён из чата |
|
| `user_removed` | Пользователь удалён/вышел из чата |
|
||||||
| `on_started` | Бот запущен (**внутреннее** событие библиотеки) |
|
| `on_started` | Бот запущен (**внутреннее** событие библиотеки) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user