Compare commits

...

25 Commits

Author SHA1 Message Date
6f86d15de4 Добавлен filename к InputMediaBuffer 2025-07-27 02:12:58 +03:00
7ea24fe2af Изменен пример запуска 2025-07-27 02:12:30 +03:00
39fb0c5823 Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted 2025-07-27 02:12:22 +03:00
af84301e4f Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted. Правки с auto_requests 2025-07-27 02:12:16 +03:00
ec432fe8ce Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted 2025-07-27 02:11:48 +03:00
1bfd93f2ea chat_title обязателен 2025-07-27 02:11:30 +03:00
7925087ac7 Добавлено в User @property full_name 2025-07-27 02:11:04 +03:00
7ed540683c Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted 2025-07-27 02:10:34 +03:00
30350c8521 Откат преобразования Recipient, одного из полей может не быть 2025-07-27 02:10:25 +03:00
54683256ce Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted 2025-07-27 02:09:50 +03:00
be7f98976e Поправлена логика задержки отправки сообщения с InputMedia, InputMediaBuffer 2025-07-27 02:09:34 +03:00
54c073ab76 Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted 2025-07-27 02:09:04 +03:00
29b319768b Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted. Изменена система запуска вебхуков 2025-07-27 02:08:45 +03:00
5e98e540ea Изменена система запуска вебхука, fastapi с uvicorn опциональны 2025-07-27 02:08:27 +03:00
1df293f44d Маленькие правки 2025-07-27 02:07:22 +03:00
c667b82a6c Добавлены типы событий: bot_stopped, dialog_cleared, dialog_muted, dialog_unmuted, message_chat_created 2025-07-27 02:07:06 +03:00
62523c1eb2 Правки по преобразованию 2025-07-25 00:54:57 +03:00
b0b7040206 Добавлен аттрибут is_channel 2025-07-25 00:54:35 +03:00
29d3d7c042 Рефактор для получения обновлений 2025-07-25 00:53:35 +03:00
fd048e8544 ignore 2025-07-25 00:53:08 +03:00
32c0ca7647 Правки по ruff + mypy 2025-07-25 00:52:58 +03:00
02b4e2d39a Создан отдельный файл для MemoryContext 2025-07-25 00:52:38 +03:00
354c296fed Правки по ruff + mypy 2025-07-25 00:52:16 +03:00
8f93cf36e4 Удален импорт UpdateUnion 2025-07-25 00:49:20 +03:00
e1064761e4 Поправлены импорты 2025-07-25 00:48:53 +03:00
70 changed files with 792 additions and 389 deletions

View File

@@ -13,7 +13,13 @@ from maxapi.types import (
MessageEdited, MessageEdited,
MessageRemoved, MessageRemoved,
UserAdded, UserAdded,
UserRemoved UserRemoved,
BotStopped,
DialogCleared,
DialogMuted,
DialogUnmuted,
ChatButton,
MessageChatCreated
) )
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
@@ -38,9 +44,9 @@ async def hello(event: MessageCreated):
) )
) )
builder.add( builder.add(
CallbackButton( ChatButton(
text='Кнопка 3', text='Создать чат',
payload='btn_3', chat_title='Тест чат'
) )
) )
@@ -80,7 +86,7 @@ async def bot_started(event: BotStarted):
async def chat_title_changed(event: ChatTitleChanged): async def chat_title_changed(event: ChatTitleChanged):
await event.bot.send_message( await event.bot.send_message(
chat_id=event.chat_id, chat_id=event.chat_id,
text=f'Крутое новое название "{event.chat.title}!"' text=f'Крутое новое название "{event.chat.title}"!'
) )
@@ -114,6 +120,34 @@ async def user_added(event: UserAdded):
) )
@dp.bot_stopped()
async def bot_stopped(event: BotStopped):
print(event.from_user.full_name, 'остановил бота') # type: ignore
@dp.dialog_cleared()
async def dialog_cleared(event: DialogCleared):
print(event.from_user.full_name, 'очистил историю чата с ботом') # type: ignore
@dp.dialog_muted()
async def dialog_muted(event: DialogMuted):
print(event.from_user.full_name, 'отключил оповещения от чата бота до ', event.muted_until_datetime) # type: ignore
@dp.dialog_unmuted()
async def dialog_unmuted(event: DialogUnmuted):
print(event.from_user.full_name, 'включил оповещения от чата бота') # type: ignore
@dp.message_chat_created()
async def message_chat_created(event: MessageChatCreated):
await event.bot.send_message(
chat_id=event.chat.chat_id,
text=f'Чат создан! Ссылка: {event.chat.link}'
)
async def main(): async def main():
await dp.start_polling(bot) await dp.start_polling(bot)

View File

@@ -44,7 +44,7 @@ async def echo(event: MessageCreated):
@dp.message_created(Command('builder')) @dp.message_created(Command('builder'))
async def echo(event: MessageCreated): async def builder(event: MessageCreated):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
builder.row( builder.row(
@@ -88,7 +88,7 @@ async def echo(event: MessageCreated):
@dp.message_created(Command('payload')) @dp.message_created(Command('payload'))
async def echo(event: MessageCreated): async def payload(event: MessageCreated):
buttons = [ buttons = [
[ [
# кнопку типа "chat" убрали из документации, # кнопку типа "chat" убрали из документации,
@@ -133,7 +133,7 @@ async def echo(event: MessageCreated):
@dp.message_chat_created() @dp.message_chat_created()
async def callback(obj: MessageChatCreated): async def message_chat_created(obj: MessageChatCreated):
await obj.bot.send_message( await obj.bot.send_message(
chat_id=obj.chat.chat_id, chat_id=obj.chat.chat_id,
text=f'Чат создан! Ссылка: {obj.chat.link}' text=f'Чат создан! Ссылка: {obj.chat.link}'
@@ -141,7 +141,7 @@ async def callback(obj: MessageChatCreated):
@dp.message_callback() @dp.message_callback()
async def callback(callback: MessageCallback): async def message_callback(callback: MessageCallback):
await callback.message.answer('Вы нажали на Callback!') await callback.message.answer('Вы нажали на Callback!')

View File

@@ -16,7 +16,7 @@ async def handle_message(event: MessageCreated):
async def main(): async def main():
await dp.handle_webhook(bot) await dp.handle_webhook(bot, log_level='critical')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -1,13 +1,21 @@
import asyncio import asyncio
import logging import logging
from fastapi import Request try:
from fastapi.responses import JSONResponse from fastapi import Request
from fastapi.responses import JSONResponse
except ImportError:
raise ImportError(
'\n\t Не установлен fastapi!'
'\n\t Выполните команду для установки fastapi: '
'\n\t pip install fastapi>=0.68.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
from maxapi import Bot, Dispatcher from maxapi import Bot, Dispatcher
from maxapi.methods.types.getted_updates import process_update_webhook from maxapi.methods.types.getted_updates import process_update_webhook
from maxapi.types import MessageCreated from maxapi.types import MessageCreated
from maxapi.dispatcher import webhook_app
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -21,7 +29,7 @@ async def handle_message(event: MessageCreated):
# Регистрация обработчика # Регистрация обработчика
# для вебхука # для вебхука
@webhook_app.post('/') @dp.webhook_post('/')
async def _(request: Request): async def _(request: Request):
# Сериализация полученного запроса # Сериализация полученного запроса

View File

@@ -3,8 +3,8 @@ from .dispatcher import Dispatcher, Router
from .filters import F from .filters import F
__all__ = [ __all__ = [
Bot, 'Bot',
Dispatcher, 'Dispatcher',
F, 'F',
Router 'Router'
] ]

View File

@@ -49,7 +49,6 @@ if TYPE_CHECKING:
from .types.chats import Chat, ChatMember, Chats from .types.chats import Chat, ChatMember, Chats
from .types.command import BotCommand from .types.command import BotCommand
from .types.message import Message, Messages, NewMessageLink from .types.message import Message, Messages, NewMessageLink
from .types.updates import UpdateUnion
from .types.users import ChatAdmin, User from .types.users import ChatAdmin, User
from .methods.types.added_admin_chat import AddedListAdminChat from .methods.types.added_admin_chat import AddedListAdminChat

View File

@@ -4,7 +4,6 @@ import os
import mimetypes import mimetypes
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Any, Optional
from uuid import uuid4
import aiofiles import aiofiles
import puremagic import puremagic
@@ -70,7 +69,8 @@ class BaseConnection:
- dict (если is_return_raw=True) - dict (если is_return_raw=True)
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
if not self.bot.session: if not self.bot.session:
self.bot.session = ClientSession( self.bot.session = ClientSession(
@@ -100,7 +100,8 @@ class BaseConnection:
raw = await r.json() raw = await r.json()
if is_return_raw: return raw if is_return_raw:
return raw
model = model(**raw) # type: ignore model = model(**raw) # type: ignore
@@ -154,6 +155,7 @@ class BaseConnection:
async def upload_file_buffer( async def upload_file_buffer(
self, self,
filename: str,
url: str, url: str,
buffer: bytes, buffer: bytes,
type: UploadType type: UploadType
@@ -180,7 +182,7 @@ class BaseConnection:
mime_type = f"{type.value}/*" mime_type = f"{type.value}/*"
ext = '' ext = ''
basename = f'{uuid4()}{ext}' basename = f'{filename}{ext}'
form = FormData() form = FormData()
form.add_field( form.add_field(

View File

@@ -1,93 +1,9 @@
import asyncio
from typing import Any, Dict, Optional, Union
from ..context.state_machine import State, StatesGroup from ..context.state_machine import State, StatesGroup
from .context import MemoryContext
class MemoryContext: __all__ = [
'State',
""" 'StatesGroup',
Контекст хранения данных пользователя с блокировками. 'MemoryContext'
]
Args:
chat_id (int): Идентификатор чата
user_id (int): Идентификатор пользователя
"""
def __init__(self, chat_id: int, user_id: int):
self.chat_id = chat_id
self.user_id = user_id
self._context: Dict[str, Any] = {}
self._state: State | str | None = None
self._lock = asyncio.Lock()
async def get_data(self) -> dict[str, Any]:
"""
Возвращает текущий контекст данных.
Returns:
Словарь с данными контекста
"""
async with self._lock:
return self._context
async def set_data(self, data: dict[str, Any]):
"""
Полностью заменяет контекст данных.
Args:
data: Новый словарь контекста
"""
async with self._lock:
self._context = data
async def update_data(self, **kwargs):
"""
Обновляет контекст данных новыми значениями.
Args:
**kwargs: Пары ключ-значение для обновления
"""
async with self._lock:
self._context.update(kwargs)
async def set_state(self, state: Optional[Union[State, str]] = None):
"""
Устанавливает новое состояние.
Args:
state: Новое состояние или None для сброса
"""
async with self._lock:
self._state = state
async def get_state(self):
"""
Возвращает текущее состояние.
Returns:
Текущее состояние или None
"""
async with self._lock:
return self._state
async def clear(self):
"""
Очищает контекст и сбрасывает состояние.
"""
async with self._lock:
self._state = None
self._context = {}

93
maxapi/context/context.py Normal file
View File

@@ -0,0 +1,93 @@
import asyncio
from typing import Any, Dict, Optional, Union
from ..context.state_machine import State
class MemoryContext:
"""
Контекст хранения данных пользователя с блокировками.
Args:
chat_id (int): Идентификатор чата
user_id (int): Идентификатор пользователя
"""
def __init__(self, chat_id: int, user_id: int):
self.chat_id = chat_id
self.user_id = user_id
self._context: Dict[str, Any] = {}
self._state: State | str | None = None
self._lock = asyncio.Lock()
async def get_data(self) -> dict[str, Any]:
"""
Возвращает текущий контекст данных.
Returns:
Словарь с данными контекста
"""
async with self._lock:
return self._context
async def set_data(self, data: dict[str, Any]):
"""
Полностью заменяет контекст данных.
Args:
data: Новый словарь контекста
"""
async with self._lock:
self._context = data
async def update_data(self, **kwargs):
"""
Обновляет контекст данных новыми значениями.
Args:
**kwargs: Пары ключ-значение для обновления
"""
async with self._lock:
self._context.update(kwargs)
async def set_state(self, state: Optional[Union[State, str]] = None):
"""
Устанавливает новое состояние.
Args:
state: Новое состояние или None для сброса
"""
async with self._lock:
self._state = state
async def get_state(self):
"""
Возвращает текущее состояние.
Returns:
Текущее состояние или None
"""
async with self._lock:
return self._state
async def clear(self):
"""
Очищает контекст и сбрасывает состояние.
"""
async with self._lock:
self._state = None
self._context = {}

View File

@@ -5,9 +5,6 @@ import asyncio
from typing import Any, Callable, Dict, List, TYPE_CHECKING, Optional from typing import Any, Callable, Dict, List, TYPE_CHECKING, Optional
from asyncio.exceptions import TimeoutError as AsyncioTimeoutError from asyncio.exceptions import TimeoutError as AsyncioTimeoutError
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from uvicorn import Config, Server
from aiohttp import ClientConnectorError from aiohttp import ClientConnectorError
from .filters.middleware import BaseMiddleware from .filters.middleware import BaseMiddleware
@@ -17,7 +14,7 @@ from .context import MemoryContext
from .types.updates import UpdateUnion from .types.updates import UpdateUnion
from .types.errors import Error from .types.errors import Error
from .methods.types.getted_updates import process_update_webhook, process_update_request from .methods.types.getted_updates import process_update_request, process_update_webhook
from .filters import filter_attrs from .filters import filter_attrs
@@ -25,12 +22,25 @@ from .bot import Bot
from .enums.update import UpdateType from .enums.update import UpdateType
from .loggers import logger_dp from .loggers import logger_dp
try:
from fastapi import FastAPI, Request # type: ignore
from fastapi.responses import JSONResponse # type: ignore
FASTAPI_INSTALLED = True
except ImportError:
FASTAPI_INSTALLED = False
try:
from uvicorn import Config, Server # type: ignore
UVICORN_INSTALLED = True
except ImportError:
UVICORN_INSTALLED = False
if TYPE_CHECKING: if TYPE_CHECKING:
from magic_filter import MagicFilter from magic_filter import MagicFilter
webhook_app = FastAPI()
CONNECTION_RETRY_DELAY = 30 CONNECTION_RETRY_DELAY = 30
GET_UPDATES_RETRY_DELAY = 5 GET_UPDATES_RETRY_DELAY = 5
@@ -44,12 +54,14 @@ class Dispatcher:
применение middleware, фильтров и вызов соответствующих обработчиков. применение middleware, фильтров и вызов соответствующих обработчиков.
""" """
def __init__(self) -> None: def __init__(self, router_id: str | None = None) -> None:
""" """
Инициализация диспетчера. Инициализация диспетчера.
""" """
self.router_id = router_id
self.event_handlers: List[Handler] = [] self.event_handlers: List[Handler] = []
self.contexts: List[MemoryContext] = [] self.contexts: List[MemoryContext] = []
self.routers: List[Router | Dispatcher] = [] self.routers: List[Router | Dispatcher] = []
@@ -57,12 +69,17 @@ class Dispatcher:
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.on_started_func: Optional[Callable] = None self.on_started_func: Optional[Callable] = None
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self) self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self)
self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self) self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self)
self.bot_removed = Event(update_type=UpdateType.BOT_REMOVED, router=self) self.bot_removed = Event(update_type=UpdateType.BOT_REMOVED, router=self)
self.bot_started = Event(update_type=UpdateType.BOT_STARTED, router=self) self.bot_started = Event(update_type=UpdateType.BOT_STARTED, router=self)
self.bot_stopped = Event(update_type=UpdateType.BOT_STOPPED, router=self)
self.dialog_cleared = Event(update_type=UpdateType.DIALOG_CLEARED, router=self)
self.dialog_muted = Event(update_type=UpdateType.DIALOG_MUTED, router=self)
self.dialog_unmuted = Event(update_type=UpdateType.DIALOG_UNMUTED, router=self)
self.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)
@@ -72,6 +89,23 @@ class Dispatcher:
self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self) self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self)
self.on_started = Event(update_type=UpdateType.ON_STARTED, router=self) self.on_started = Event(update_type=UpdateType.ON_STARTED, router=self)
def webhook_post(self, path: str):
def decorator(func):
if self.webhook_app is None:
try:
from fastapi import FastAPI # type: ignore
except ImportError:
raise ImportError(
'\n\t Не установлен fastapi!'
'\n\t Выполните команду для установки fastapi: '
'\n\t pip install fastapi>=0.68.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
self.webhook_app = FastAPI()
return self.webhook_app.post(path)(func)
return decorator
async def check_me(self): async def check_me(self):
""" """
@@ -178,14 +212,19 @@ class Dispatcher:
memory_context = self.__get_memory_context(*ids) memory_context = self.__get_memory_context(*ids)
current_state = await memory_context.get_state() current_state = await memory_context.get_state()
kwargs = {'context': memory_context} kwargs = {'context': memory_context}
router_id = None
process_info = f'{event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}'
is_handled = False is_handled = False
for router in self.routers: for index, router in enumerate(self.routers):
if is_handled: if is_handled:
break break
router_id = router.router_id or index
if router.filters: if router.filters:
if not filter_attrs(event_object, *router.filters): if not filter_attrs(event_object, *router.filters):
continue continue
@@ -220,21 +259,21 @@ class Dispatcher:
continue continue
for key in kwargs.copy().keys(): for key in kwargs.copy().keys():
if not key in func_args: if key not in func_args:
del kwargs[key] del kwargs[key]
await handler.func_event(event_object, **kwargs) await handler.func_event(event_object, **kwargs)
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}') logger_dp.info(f'Обработано: {router_id} | {process_info}')
is_handled = True is_handled = True
break break
if not is_handled: if not is_handled:
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}') logger_dp.info(f'Проигнорировано: {router_id} | {process_info}')
except Exception as e: except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]} | {e} ") logger_dp.error(f"Ошибка при обработке события: {router_id} | {process_info} | {e} ")
async def start_polling(self, bot: Bot): async def start_polling(self, bot: Bot):
@@ -248,8 +287,11 @@ class Dispatcher:
while True: while True:
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
try: try:
events: Dict = await self.bot.get_updates() # type: ignore events: Dict = await self.bot.get_updates()
except AsyncioTimeoutError: except AsyncioTimeoutError:
continue continue
@@ -260,11 +302,11 @@ class Dispatcher:
await asyncio.sleep(GET_UPDATES_RETRY_DELAY) await asyncio.sleep(GET_UPDATES_RETRY_DELAY)
continue continue
self.bot.marker_updates = events.get('marker') # type: ignore self.bot.marker_updates = events.get('marker')
processed_events = await process_update_request( processed_events = await process_update_request(
events=events, events=events,
bot=self.bot # type: ignore bot=self.bot
) )
for event in processed_events: for event in processed_events:
@@ -276,7 +318,7 @@ class Dispatcher:
except Exception as e: except Exception as e:
logger_dp.error(f'Общая ошибка при обработке событий: {e.__class__} - {e}') logger_dp.error(f'Общая ошибка при обработке событий: {e.__class__} - {e}')
async def handle_webhook(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080): async def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
""" """
Запускает FastAPI-приложение для приёма обновлений через вебхук. Запускает FastAPI-приложение для приёма обновлений через вебхук.
@@ -286,29 +328,57 @@ class Dispatcher:
:param port: Порт сервера. :param port: Порт сервера.
""" """
@webhook_app.post('/') if not FASTAPI_INSTALLED:
raise ImportError(
'\n\t Не установлен fastapi!'
'\n\t Выполните команду для установки fastapi: '
'\n\t pip install fastapi>=0.68.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
elif not UVICORN_INSTALLED:
raise ImportError(
'\n\t Не установлен uvicorn!'
'\n\t Выполните команду для установки uvicorn: '
'\n\t pip install uvicorn>=0.15.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
# 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('/')
async def _(request: Request): async def _(request: Request):
try: event_json = await request.json()
event_json = await request.json() event_object = await process_update_webhook(
event_json=event_json,
bot=bot
)
event_object = await process_update_webhook( await self.handle(event_object)
event_json=event_json, return JSONResponse(content={'ok': True}, status_code=200)
bot=self.bot # type: ignore
)
await self.handle(event_object)
return JSONResponse(content={'ok': True}, status_code=200)
except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}")
await self.init_serve( await self.init_serve(
bot=bot, bot=bot,
host=host, host=host,
port=port port=port,
**kwargs
) )
async def init_serve(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080, **kwargs): async def init_serve(self, bot: Bot, host: str = 'localhost', port: int = 8080, **kwargs):
""" """
Запускает сервер для обработки входящих вебхуков. Запускает сервер для обработки входящих вебхуков.
@@ -318,7 +388,30 @@ class Dispatcher:
:param port: Порт сервера. :param port: Порт сервера.
""" """
config = Config(app=webhook_app, host=host, port=port, **kwargs) # 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:
raise ImportError(
'\n\t Не установлен uvicorn!'
'\n\t Выполните команду для установки uvicorn: '
'\n\t pip install uvicorn>=0.15.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
if self.webhook_app is None:
raise RuntimeError('webhook_app не инициализирован')
config = Config(app=self.webhook_app, host=host, port=port, **kwargs)
server = Server(config) server = Server(config)
await self.__ready(bot) await self.__ready(bot)
@@ -332,8 +425,8 @@ class Router(Dispatcher):
Роутер для группировки обработчиков событий. Роутер для группировки обработчиков событий.
""" """
def __init__(self): def __init__(self, router_id: str | None = None):
super().__init__() super().__init__(router_id)
class Event: class Event:

View File

@@ -20,6 +20,10 @@ class UpdateType(str, Enum):
MESSAGE_REMOVED = 'message_removed' MESSAGE_REMOVED = 'message_removed'
USER_ADDED = 'user_added' USER_ADDED = 'user_added'
USER_REMOVED = 'user_removed' USER_REMOVED = 'user_removed'
BOT_STOPPED = 'bot_stopped'
DIALOG_CLEARED = 'dialog_cleared'
DIALOG_MUTED = 'dialog_muted'
DIALOG_UNMUTED = 'dialog_unmuted'
# Для начинки диспатчера # Для начинки диспатчера
ON_STARTED = 'on_started' ON_STARTED = 'on_started'

View File

@@ -1,4 +1,4 @@
from typing import Callable, List from typing import Callable, List, Optional
from magic_filter import F, MagicFilter from magic_filter import F, MagicFilter
@@ -44,7 +44,7 @@ class Handler:
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 = []
self.state: State = None self.state: Optional[State] = None
self.middlewares: List[BaseMiddleware] = [] self.middlewares: List[BaseMiddleware] = []
for arg in args: for arg in args:

View File

@@ -19,9 +19,9 @@ class BaseMiddleware:
kwargs_temp = {'data': result_data_kwargs.copy()} kwargs_temp = {'data': result_data_kwargs.copy()}
for key in kwargs_temp.copy().keys(): for key in kwargs_temp.copy().keys():
if not key in self.__call__.__annotations__.keys(): if key not in self.__call__.__annotations__.keys(): # type: ignore
del kwargs_temp[key] del kwargs_temp[key]
result: Dict[str, Any] = await self(event_object, **kwargs_temp) result: Dict[str, Any] = await self(event_object, **kwargs_temp) # type: ignore
return result return result

View File

@@ -48,7 +48,8 @@ class AddAdminChat(BaseConnection):
AddedListAdminChat: Результат операции с информацией об успешности. AddedListAdminChat: Результат операции с информацией об успешности.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}

View File

@@ -45,7 +45,8 @@ class AddMembersChat(BaseConnection):
AddedMembersChat: Результат операции с информацией об успешности добавления. AddedMembersChat: Результат операции с информацией об успешности добавления.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}

View File

@@ -48,14 +48,19 @@ class ChangeInfo(BaseConnection):
User: Объект с обновленными данными бота User: Объект с обновленными данными бота
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}
if self.name: json['name'] = self.name if self.name:
if self.description: json['description'] = self.description json['name'] = self.name
if self.commands: json['commands'] = [command.model_dump() for command in self.commands] if self.description:
if self.photo: json['photo'] = self.photo json['description'] = self.description
if self.commands:
json['commands'] = [command.model_dump() for command in self.commands]
if self.photo:
json['photo'] = self.photo
return await super().request( return await super().request(
method=HTTPMethod.PATCH, method=HTTPMethod.PATCH,

View File

@@ -39,7 +39,8 @@ class DeleteMeFromMessage(BaseConnection):
DeletedBotFromChat: Результат операции удаления. DeletedBotFromChat: Результат операции удаления.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.DELETE, method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME, path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,

View File

@@ -38,7 +38,9 @@ class DeleteChat(BaseConnection):
DeletedChat: Результат операции удаления чата. DeletedChat: Результат операции удаления чата.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.DELETE, method=HTTPMethod.DELETE,
path=ApiPath.CHATS.value + '/' + str(self.chat_id), path=ApiPath.CHATS.value + '/' + str(self.chat_id),

View File

@@ -40,7 +40,9 @@ class DeleteMessage(BaseConnection):
DeletedMessage: Результат операции удаления сообщения. DeletedMessage: Результат операции удаления сообщения.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['message_id'] = self.message_id params['message_id'] = self.message_id

View File

@@ -38,7 +38,10 @@ class DeletePinMessage(BaseConnection):
Returns: Returns:
DeletedPinMessage: Результат операции удаления закреплённого сообщения. DeletedPinMessage: Результат операции удаления закреплённого сообщения.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.DELETE, method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN, path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,

View File

@@ -1,11 +1,5 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ..methods.types.deleted_pin_message import DeletedPinMessage
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..enums.upload_type import UploadType
from ..connection.base import BaseConnection from ..connection.base import BaseConnection

View File

@@ -64,14 +64,16 @@ class EditChat(BaseConnection):
Chat: Обновлённый объект чата. Chat: Обновлённый объект чата.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}
if self.icon: if self.icon:
dump = self.icon.model_dump() dump = self.icon.model_dump()
counter = Counter(dump.values()) counter = Counter(dump.values())
if not None in counter or \ if None not in counter or \
not counter[None] == 2: not counter[None] == 2:
raise MaxIconParamsException( raise MaxIconParamsException(
@@ -81,9 +83,12 @@ class EditChat(BaseConnection):
json['icon'] = dump json['icon'] = dump
if self.title: json['title'] = self.title if self.title:
if self.pin: json['pin'] = self.pin json['title'] = self.title
if self.notify: json['notify'] = self.notify if self.pin:
json['pin'] = self.pin
if self.notify:
json['notify'] = self.notify
return await super().request( return await super().request(
method=HTTPMethod.PATCH, method=HTTPMethod.PATCH,

View File

@@ -66,14 +66,17 @@ class EditMessage(BaseConnection):
EditedMessage: Обновлённое сообщение. EditedMessage: Обновлённое сообщение.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
json: Dict[str, Any] = {'attachments': []} json: Dict[str, Any] = {'attachments': []}
params['message_id'] = self.message_id params['message_id'] = self.message_id
if not self.text is None: json['text'] = self.text if self.text is not None:
json['text'] = self.text
if self.attachments: if self.attachments:
@@ -91,9 +94,12 @@ class EditMessage(BaseConnection):
else: else:
json['attachments'].append(att.model_dump()) json['attachments'].append(att.model_dump())
if not self.link is None: json['link'] = self.link.model_dump() if self.link is not None:
if not self.notify is None: json['notify'] = self.notify json['link'] = self.link.model_dump()
if not self.parse_mode is None: json['format'] = self.parse_mode.value if self.notify is not None:
json['notify'] = self.notify
if self.parse_mode is not None:
json['format'] = self.parse_mode.value
await asyncio.sleep(self.bot.after_input_media_delay) await asyncio.sleep(self.bot.after_input_media_delay)

View File

@@ -39,7 +39,9 @@ class GetChatById(BaseConnection):
Chat: Объект чата с полной информацией. Chat: Объект чата с полной информацией.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.id), path=ApiPath.CHATS.value + '/' + str(self.id),

View File

@@ -49,7 +49,9 @@ class GetChatByLink(BaseConnection):
Chat: Объект с информацией о чате. Chat: Объект с информацией о чате.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + self.link[-1], path=ApiPath.CHATS.value + '/' + self.link[-1],

View File

@@ -46,7 +46,10 @@ class GetChats(BaseConnection):
Returns: Returns:
Chats: Объект с данными по списку чатов. Chats: Объект с данными по списку чатов.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['count'] = self.count params['count'] = self.count

View File

@@ -42,7 +42,10 @@ class GetListAdminChat(BaseConnection):
Returns: Returns:
GettedListAdminChat: Объект с информацией о администраторах чата. GettedListAdminChat: Объект с информацией о администраторах чата.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS, path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,

View File

@@ -32,7 +32,10 @@ class GetMe(BaseConnection):
Returns: Returns:
User: Объект пользователя с полной информацией. User: Объект пользователя с полной информацией.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.ME, path=ApiPath.ME,

View File

@@ -42,7 +42,10 @@ class GetMeFromChat(BaseConnection):
Returns: Returns:
ChatMember: Информация о боте как участнике чата. ChatMember: Информация о боте как участнике чата.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME, path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,

View File

@@ -57,14 +57,19 @@ class GetMembersChat(BaseConnection):
Returns: Returns:
GettedMembersChat: Объект с данными по участникам чата. GettedMembersChat: Объект с данными по участникам чата.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
if self.user_ids: if self.user_ids:
params['user_ids'] = ','.join([str(user_id) for user_id in self.user_ids]) params['user_ids'] = ','.join([str(user_id) for user_id in self.user_ids])
if self.marker: params['marker'] = self.marker if self.marker:
if self.count: params['marker'] = self.count params['marker'] = self.marker
if self.count:
params['marker'] = self.count
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,

View File

@@ -59,10 +59,14 @@ class GetMessages(BaseConnection):
Returns: Returns:
Messages: Объект с полученными сообщениями. Messages: Объект с полученными сообщениями.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
if self.chat_id: params['chat_id'] = self.chat_id if self.chat_id:
params['chat_id'] = self.chat_id
if self.message_ids: if self.message_ids:
params['message_ids'] = ','.join(self.message_ids) params['message_ids'] = ','.join(self.message_ids)

View File

@@ -37,7 +37,10 @@ class GetPinnedMessage(BaseConnection):
Returns: Returns:
GettedPin: Объект с информацией о закреплённом сообщении. GettedPin: Объект с информацией о закреплённом сообщении.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN, path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,

View File

@@ -1,8 +1,6 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Dict from typing import TYPE_CHECKING, Dict
from ..types.updates import UpdateUnion
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
@@ -45,7 +43,10 @@ class GetUpdates(BaseConnection):
Returns: Returns:
UpdateUnion: Объединённый тип данных обновлений. UpdateUnion: Объединённый тип данных обновлений.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['limit'] = self.limit params['limit'] = self.limit

View File

@@ -43,7 +43,10 @@ class GetUploadURL(BaseConnection):
Returns: Returns:
GettedUploadUrl: Результат с URL для загрузки. GettedUploadUrl: Результат с URL для загрузки.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['type'] = self.type.value params['type'] = self.type.value

View File

@@ -38,7 +38,10 @@ class GetVideo(BaseConnection):
Returns: Returns:
Video: Объект с информацией о видео. Video: Объект с информацией о видео.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.VIDEOS.value + '/' + self.video_token, path=ApiPath.VIDEOS.value + '/' + self.video_token,

View File

@@ -52,7 +52,10 @@ class PinMessage(BaseConnection):
Returns: Returns:
PinnedMessage: Объект с информацией о закреплённом сообщении. PinnedMessage: Объект с информацией о закреплённом сообщении.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}
json['message_id'] = self.message_id json['message_id'] = self.message_id

View File

@@ -46,7 +46,10 @@ class RemoveAdmin(BaseConnection):
Returns: Returns:
RemovedAdmin: Объект с результатом отмены прав администратора. RemovedAdmin: Объект с результатом отмены прав администратора.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await super().request( return await super().request(
method=HTTPMethod.DELETE, method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + \ path=ApiPath.CHATS + '/' + str(self.chat_id) + \

View File

@@ -54,7 +54,9 @@ class RemoveMemberChat(BaseConnection):
RemovedMemberChat: Результат удаления участника. RemovedMemberChat: Результат удаления участника.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['chat_id'] = self.chat_id params['chat_id'] = self.chat_id

View File

@@ -49,7 +49,9 @@ class SendAction(BaseConnection):
Returns: Returns:
SendedAction: Результат выполнения запроса. SendedAction: Результат выполнения запроса.
""" """
assert self.bot is not None
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
json: Dict[str, Any] = {} json: Dict[str, Any] = {}

View File

@@ -55,15 +55,19 @@ class SendCallback(BaseConnection):
SendedCallback: Объект с результатом отправки callback. SendedCallback: Объект с результатом отправки callback.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
params['callback_id'] = self.callback_id params['callback_id'] = self.callback_id
json: Dict[str, Any] = {} json: Dict[str, Any] = {}
if self.message: json['message'] = self.message.model_dump() if self.message:
if self.notification: json['notification'] = self.notification json['message'] = self.message.model_dump()
if self.notification:
json['notification'] = self.notification
return await super().request( return await super().request(
method=HTTPMethod.POST, method=HTTPMethod.POST,

View File

@@ -70,21 +70,29 @@ class SendMessage(BaseConnection):
SendedMessage или Error SendedMessage или Error
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
params = self.bot.params.copy() params = self.bot.params.copy()
json: Dict[str, Any] = {'attachments': []} json: Dict[str, Any] = {'attachments': []}
if self.chat_id: params['chat_id'] = self.chat_id if self.chat_id:
elif self.user_id: params['user_id'] = self.user_id params['chat_id'] = self.chat_id
elif self.user_id:
params['user_id'] = self.user_id
json['text'] = self.text json['text'] = self.text
HAS_INPUT_MEDIA = False
if self.attachments: if self.attachments:
for att in self.attachments: for att in self.attachments:
if isinstance(att, InputMedia) or isinstance(att, InputMediaBuffer): if isinstance(att, (InputMedia, InputMediaBuffer)):
HAS_INPUT_MEDIA = True
input_media = await process_input_media( input_media = await process_input_media(
base_connection=self, base_connection=self,
bot=self.bot, bot=self.bot,
@@ -96,11 +104,16 @@ class SendMessage(BaseConnection):
else: else:
json['attachments'].append(att.model_dump()) json['attachments'].append(att.model_dump())
if not self.link is None: json['link'] = self.link.model_dump() if self.link is not None:
json['notify'] = self.notify json['link'] = self.link.model_dump()
if not self.parse_mode is None: json['format'] = self.parse_mode.value
await asyncio.sleep(self.bot.after_input_media_delay) json['notify'] = self.notify
if self.parse_mode is not None:
json['format'] = self.parse_mode.value
if HAS_INPUT_MEDIA:
await asyncio.sleep(self.bot.after_input_media_delay)
response = None response = None
for attempt in range(self.ATTEMPTS_COUNT): for attempt in range(self.ATTEMPTS_COUNT):

View File

@@ -1,9 +1,12 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from ...utils.updates import enrich_event
from ...enums.update import UpdateType from ...enums.update import UpdateType
from ...types.updates.bot_added import BotAdded from ...types.updates.bot_added import BotAdded
from ...types.updates.bot_removed import BotRemoved from ...types.updates.bot_removed import BotRemoved
from ...types.updates.bot_started import BotStarted from ...types.updates.bot_started import BotStarted
from ...types.updates.bot_stopped import BotStopped
from ...types.updates.chat_title_changed import ChatTitleChanged from ...types.updates.chat_title_changed import ChatTitleChanged
from ...types.updates.message_callback import MessageCallback from ...types.updates.message_callback import MessageCallback
from ...types.updates.message_chat_created import MessageChatCreated from ...types.updates.message_chat_created import MessageChatCreated
@@ -12,125 +15,54 @@ from ...types.updates.message_edited import MessageEdited
from ...types.updates.message_removed import MessageRemoved from ...types.updates.message_removed import MessageRemoved
from ...types.updates.user_added import UserAdded from ...types.updates.user_added import UserAdded
from ...types.updates.user_removed import UserRemoved from ...types.updates.user_removed import UserRemoved
from ...types.updates.dialog_cleared import DialogCleared
from ...types.updates.dialog_muted import DialogMuted
from ...types.updates.dialog_unmuted import DialogUnmuted
if TYPE_CHECKING: if TYPE_CHECKING:
from ...bot import Bot from ...bot import Bot
UPDATE_MODEL_MAPPING = {
UpdateType.BOT_ADDED: BotAdded,
UpdateType.BOT_REMOVED: BotRemoved,
UpdateType.BOT_STARTED: BotStarted,
UpdateType.CHAT_TITLE_CHANGED: ChatTitleChanged,
UpdateType.MESSAGE_CALLBACK: MessageCallback,
UpdateType.MESSAGE_CHAT_CREATED: MessageChatCreated,
UpdateType.MESSAGE_CREATED: MessageCreated,
UpdateType.MESSAGE_EDITED: MessageEdited,
UpdateType.MESSAGE_REMOVED: MessageRemoved,
UpdateType.USER_ADDED: UserAdded,
UpdateType.USER_REMOVED: UserRemoved,
UpdateType.BOT_STOPPED: BotStopped,
UpdateType.DIALOG_CLEARED: DialogCleared,
UpdateType.DIALOG_MUTED: DialogMuted,
UpdateType.DIALOG_UNMUTED: DialogUnmuted
}
async def get_update_model(event: dict, bot: 'Bot'): async def get_update_model(event: dict, bot: 'Bot'):
event_object = None update_type = event['update_type']
model_cls = UPDATE_MODEL_MAPPING.get(update_type)
match event['update_type']: if not model_cls:
raise ValueError(f'Unknown update type: {update_type}')
case UpdateType.BOT_ADDED: event_object = await enrich_event(
event_object = BotAdded(**event) event_object=model_cls(**event),
bot=bot
case UpdateType.BOT_REMOVED: )
event_object = BotRemoved(**event)
case UpdateType.BOT_STARTED:
event_object = BotStarted(**event)
case UpdateType.CHAT_TITLE_CHANGED:
event_object = ChatTitleChanged(**event)
case UpdateType.MESSAGE_CALLBACK:
event_object = MessageCallback(**event)
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id) \
if bot.auto_requests else None
event_object.from_user = event_object.callback.user
case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event)
event_object.chat = event_object.chat
case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event)
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id) \
if bot.auto_requests else None
event_object.from_user = event_object.message.sender
case UpdateType.MESSAGE_EDITED:
event_object = MessageEdited(**event)
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id) \
if bot.auto_requests else None
event_object.from_user = event_object.message.sender
case UpdateType.MESSAGE_REMOVED:
event_object = MessageRemoved(**event)
event_object.chat = await bot.get_chat_by_id(event_object.chat_id) \
if bot.auto_requests else None
event_object.from_user = await bot.get_chat_member(
chat_id=event_object.chat_id,
user_id=event_object.user_id
) if bot.auto_requests else None
case UpdateType.USER_ADDED:
event_object = UserAdded(**event)
event_object.chat = await bot.get_chat_by_id(event_object.chat_id) \
if bot.auto_requests else None
event_object.from_user = event_object.user
case UpdateType.USER_REMOVED:
event_object = UserRemoved(**event)
event_object.chat = await bot.get_chat_by_id(event_object.chat_id) \
if bot.auto_requests else None
event_object.from_user = await bot.get_chat_member(
chat_id=event_object.chat_id,
user_id=event_object.admin_id
) if event_object.admin_id and \
bot.auto_requests else None
if event['update_type'] in (UpdateType.BOT_ADDED,
UpdateType.BOT_REMOVED,
UpdateType.BOT_STARTED,
UpdateType.CHAT_TITLE_CHANGED):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id) \
if bot.auto_requests else None
event_object.from_user = event_object.user
if hasattr(event_object, 'bot'):
event_object.bot = bot
if hasattr(event_object, 'message'):
event_object.message.bot = bot
for attachment in event_object.message.body.attachments:
if hasattr(attachment, 'bot'):
attachment.bot = bot
return event_object return event_object
async def process_update_request(events: dict, bot: 'Bot'): async def process_update_request(events: dict, bot: 'Bot'):
events = [event for event in events['updates']] return [
await get_update_model(event, bot)
objects = [] for event in events['updates']
]
for event in events:
objects.append(
await get_update_model(
bot=bot,
event=event
)
)
return objects
async def process_update_webhook(event_json: dict, bot: 'Bot'): async def process_update_webhook(event_json: dict, bot: 'Bot'):

View File

@@ -21,4 +21,4 @@ class SendedCallback(BaseModel):
bot: Optional[Any] = Field(default=None, exclude=True) bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] bot: Optional[Bot] # type: ignore

View File

@@ -9,6 +9,10 @@ from ..types.updates.message_edited import MessageEdited
from ..types.updates.message_removed import MessageRemoved from ..types.updates.message_removed import MessageRemoved
from ..types.updates.user_added import UserAdded from ..types.updates.user_added import UserAdded
from ..types.updates.user_removed import UserRemoved from ..types.updates.user_removed import UserRemoved
from ..types.updates.bot_stopped import BotStopped
from ..types.updates.dialog_cleared import DialogCleared
from ..types.updates.dialog_muted import DialogMuted
from ..types.updates.dialog_unmuted import DialogUnmuted
from ..types.updates import UpdateUnion from ..types.updates import UpdateUnion
from ..types.attachments.attachment import Attachment from ..types.attachments.attachment import Attachment
@@ -32,6 +36,10 @@ from .input_media import InputMedia
from .input_media import InputMediaBuffer from .input_media import InputMediaBuffer
__all__ = [ __all__ = [
'DialogUnmuted',
'DialogMuted',
'DialogCleared',
'BotStopped',
'CommandStart', 'CommandStart',
'OpenAppButton', 'OpenAppButton',
'Message', 'Message',

View File

@@ -1,8 +1,6 @@
from typing import TYPE_CHECKING, Any, List, Optional, Union from typing import TYPE_CHECKING, Any, List, Optional, Union
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ...exceptions.download_file import NotAvailableForDownload
from ...types.attachments.upload import AttachmentUpload from ...types.attachments.upload import AttachmentUpload
from ...types.attachments.buttons import InlineButtonUnion from ...types.attachments.buttons import InlineButtonUnion
from ...types.users import User from ...types.users import User

View File

@@ -17,8 +17,7 @@ class ChatButton(Button):
""" """
type: ButtonType = ButtonType.CHAT type: ButtonType = ButtonType.CHAT
chat_title: Optional[str] = None chat_title: str
chat_description: Optional[str] = None chat_description: Optional[str] = None
start_payload: Optional[str] = None start_payload: Optional[str] = None
chat_title: Optional[str] = None
uuid: Optional[int] = None uuid: Optional[int] = None

View File

@@ -1,5 +1,3 @@
from pydantic import BaseModel
from ....enums.button_type import ButtonType from ....enums.button_type import ButtonType
from .button import Button from .button import Button

View File

@@ -1,5 +1,3 @@
from typing import Optional
from ....enums.button_type import ButtonType from ....enums.button_type import ButtonType
from .button import Button from .button import Button

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Literal, Optional from typing import TYPE_CHECKING, Any, Optional
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from ...enums.attachment import AttachmentType from ...enums.attachment import AttachmentType

View File

@@ -1,16 +1,10 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
import puremagic import puremagic
from ..enums.upload_type import UploadType from ..enums.upload_type import UploadType
if TYPE_CHECKING:
from io import BytesIO
class InputMedia: class InputMedia:
""" """
Класс для представления медиафайла. Класс для представления медиафайла.
@@ -74,13 +68,15 @@ class InputMediaBuffer:
type (UploadType): Тип файла, определенный по содержимому. type (UploadType): Тип файла, определенный по содержимому.
""" """
def __init__(self, buffer: bytes): def __init__(self, buffer: bytes, filename: str | None = None):
""" """
Инициализирует объект медиафайла из буфера. Инициализирует объект медиафайла из буфера.
Args: Args:
buffer (IO): Буфер с содержимым файла. buffer (IO): Буфер с содержимым файла.
filename (str): Название файла (по умолчанию присваивается uuid4).
""" """
self.filename = filename
self.buffer = buffer self.buffer = buffer
self.type = self.__detect_file_type(buffer) self.type = self.__detect_file_type(buffer)

View File

@@ -195,7 +195,9 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота. Any: Результат выполнения метода send_message бота.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await self.bot.send_message( return await self.bot.send_message(
chat_id=self.recipient.chat_id, chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id, user_id=self.recipient.user_id,
@@ -227,7 +229,9 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота. Any: Результат выполнения метода send_message бота.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await self.bot.send_message( return await self.bot.send_message(
chat_id=self.recipient.chat_id, chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id, user_id=self.recipient.user_id,
@@ -264,7 +268,9 @@ class Message(BaseModel):
Any: Результат выполнения метода send_message бота. Any: Результат выполнения метода send_message бота.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await self.bot.send_message( return await self.bot.send_message(
chat_id=chat_id, chat_id=chat_id,
user_id=user_id, user_id=user_id,
@@ -300,7 +306,9 @@ class Message(BaseModel):
Any: Результат выполнения метода edit_message бота. Any: Результат выполнения метода edit_message бота.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await self.bot.edit_message( return await self.bot.edit_message(
message_id=self.body.mid, message_id=self.body.mid,
text=text, text=text,
@@ -335,7 +343,9 @@ class Message(BaseModel):
Any: Результат выполнения метода pin_message бота. Any: Результат выполнения метода pin_message бота.
""" """
assert self.bot is not None if self.bot is None:
raise RuntimeError('Bot не инициализирован')
return await self.bot.pin_message( return await self.bot.pin_message(
chat_id=self.recipient.chat_id, chat_id=self.recipient.chat_id,
message_id=self.body.mid, message_id=self.body.mid,

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Optional
from .update import Update from .update import Update
@@ -14,12 +14,14 @@ class BotAdded(Update):
Обновление, сигнализирующее о добавлении бота в чат. Обновление, сигнализирующее о добавлении бота в чат.
Attributes: Attributes:
chat_id (Optional[int]): Идентификатор чата, куда добавлен бот. chat_id (int): Идентификатор чата, куда добавлен бот.
user (User): Объект пользователя-бота. user (User): Объект пользователя-бота.
is_channel (bool): Указывает, был ли бот добавлен в канал или нет
""" """
chat_id: Optional[int] = None chat_id: int
user: User user: User
is_channel: bool
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] bot: Optional[Bot]

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Optional
from .update import Update from .update import Update
@@ -14,12 +14,14 @@ class BotRemoved(Update):
Обновление, сигнализирующее об удалении бота из чата. Обновление, сигнализирующее об удалении бота из чата.
Attributes: Attributes:
chat_id (Optional[int]): Идентификатор чата, из которого удалён бот. chat_id (int): Идентификатор чата, из которого удалён бот.
user (User): Объект пользователя-бота. user (User): Объект пользователя-бота.
is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет
""" """
chat_id: Optional[int] = None chat_id: int
user: User user: User
is_channel: bool
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] bot: Optional[Bot]

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Optional
from .update import Update from .update import Update
@@ -14,13 +14,13 @@ class BotStarted(Update):
Обновление, сигнализирующее о первом старте бота. Обновление, сигнализирующее о первом старте бота.
Attributes: Attributes:
chat_id (Optional[int]): Идентификатор чата. chat_id (int): Идентификатор чата.
user (User): Пользователь (бот). user (User): Пользователь (бот).
user_locale (Optional[str]): Локаль пользователя. user_locale (Optional[str]): Локаль пользователя.
payload (Optional[str]): Дополнительные данные. payload (Optional[str]): Дополнительные данные.
""" """
chat_id: Optional[int] = None chat_id: int
user: User user: User
user_locale: Optional[str] = None user_locale: Optional[str] = None
payload: Optional[str] = None payload: Optional[str] = None

View File

@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING, Optional
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class BotStopped(Update):
"""
Обновление, сигнализирующее об остановке бота.
Attributes:
chat_id (int): Идентификатор чата.
user (User): Пользователь (бот).
user_locale (Optional[str]): Локаль пользователя.
payload (Optional[str]): Дополнительные данные.
"""
chat_id: int
user: User
user_locale: Optional[str] = None
payload: Optional[str] = None
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user.user_id)

View File

@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Optional
from .update import Update from .update import Update
@@ -16,12 +16,12 @@ class ChatTitleChanged(Update):
Attributes: Attributes:
chat_id (Optional[int]): Идентификатор чата. chat_id (Optional[int]): Идентификатор чата.
user (User): Пользователь, совершивший изменение. user (User): Пользователь, совершивший изменение.
title (Optional[str]): Новое название чата. title (str): Новое название чата.
""" """
chat_id: Optional[int] = None chat_id: int
user: User user: User
title: Optional[str] = None title: str
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] bot: Optional[Bot]

View 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 DialogCleared(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)

View File

@@ -0,0 +1,40 @@
from typing import TYPE_CHECKING, Optional
from datetime import datetime
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class DialogMuted(Update):
"""
Обновление, сигнализирующее об отключении оповещений от бота.
Attributes:
chat_id (int): Идентификатор чата.
muted_until (int): Время до включения оповещений от бота.
user (User): Пользователь (бот).
user_locale (Optional[str]): Локаль пользователя.
"""
chat_id: int
muted_until: int
user: User
user_locale: Optional[str] = None
if TYPE_CHECKING:
bot: Optional[Bot]
@property
def muted_until_datetime(self):
try:
return datetime.fromtimestamp(self.muted_until // 1000)
except (OverflowError, OSError):
return datetime.max
def get_ids(self):
return (self.chat_id, self.user.user_id)

View 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 DialogUnmuted(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)

View File

@@ -1,4 +1,4 @@
from typing import List, Optional, TYPE_CHECKING, Union from typing import List, Optional, Union
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
@@ -21,12 +21,6 @@ from ..attachments.video import Video
from ..attachments.audio import Audio from ..attachments.audio import Audio
if TYPE_CHECKING:
from ...bot import Bot
from ...types.chats import Chat
from ...types.users import User
class MessageForCallback(BaseModel): class MessageForCallback(BaseModel):
""" """
@@ -110,6 +104,9 @@ class MessageCallback(Update):
Результат вызова send_callback бота. Результат вызова send_callback бота.
""" """
if self.bot is None:
raise RuntimeError('Bot не инициализирован')
message = MessageForCallback() message = MessageForCallback()
message.text = new_text message.text = new_text
@@ -118,7 +115,6 @@ class MessageCallback(Update):
message.notify = notify message.notify = notify
message.format = format message.format = format
assert self.bot is not None
return await self.bot.send_callback( return await self.bot.send_callback(
callback_id=self.callback.callback_id, callback_id=self.callback.callback_id,
message=message, message=message,

View File

@@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
from typing import Optional, TYPE_CHECKING from typing import Optional
from .update import Update from .update import Update

View File

@@ -1,5 +1,3 @@
from typing import Optional
from .update import Update from .update import Update
@@ -9,14 +7,14 @@ class MessageRemoved(Update):
Класс для обработки события удаления сообщения в чате. Класс для обработки события удаления сообщения в чате.
Attributes: Attributes:
message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None. message_id (str): Идентификатор удаленного сообщения. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None. chat_id (int): Идентификатор чата. Может быть None.
user_id (Optional[int]): Идентификатор пользователя. Может быть None. user_id (int): Идентификатор пользователя. Может быть None.
""" """
message_id: Optional[str] = None message_id: str
chat_id: Optional[int] = None chat_id: int
user_id: Optional[int] = None user_id: int
def get_ids(self): def get_ids(self):

View File

@@ -28,9 +28,9 @@ class Update(BaseModel):
chat: Optional[Any] = Field(default=None, exclude=True) chat: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] bot: Optional[Bot] # type: ignore
from_user: Optional[User] from_user: Optional[User] # type: ignore
chat: Optional[Chat] chat: Optional[Chat] # type: ignore
class Config: class Config:
arbitrary_types_allowed=True arbitrary_types_allowed=True

View File

@@ -11,14 +11,16 @@ class UserAdded(Update):
Класс для обработки события добавления пользователя в чат. Класс для обработки события добавления пользователя в чат.
Attributes: Attributes:
inviter_id (Optional[int]): Идентификатор пользователя, добавившего нового участника. Может быть None. inviter_id (int): Идентификатор пользователя, добавившего нового участника. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None. chat_id (int): Идентификатор чата. Может быть None.
user (User): Объект пользователя, добавленного в чат. user (User): Объект пользователя, добавленного в чат.
is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет
""" """
inviter_id: Optional[int] = None inviter_id: Optional[int] = None
chat_id: Optional[int] = None chat_id: int
user: User user: User
is_channel: bool
def get_ids(self): def get_ids(self):

View File

@@ -12,13 +12,15 @@ class UserRemoved(Update):
Attributes: Attributes:
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None. admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None. chat_id (int): Идентификатор чата. Может быть None.
user (User): Объект пользователя, удаленного из чата. user (User): Объект пользователя, удаленного из чата.
is_channel (bool): Указывает, был ли пользователь удален из канала или нет
""" """
admin_id: Optional[int] = None admin_id: Optional[int] = None
chat_id: Optional[int] = None chat_id: int
user: User user: User
is_channel: bool
def get_ids(self): def get_ids(self):

View File

@@ -36,6 +36,13 @@ class User(BaseModel):
full_avatar_url: Optional[str] = None full_avatar_url: Optional[str] = None
commands: Optional[List[BotCommand]] = None commands: Optional[List[BotCommand]] = None
@property
def full_name(self):
if self.last_name is None:
return self.first_name
return f'{self.first_name} {self.last_name}'
class Config: class Config:
json_encoders = { json_encoders = {
datetime: lambda v: int(v.timestamp() * 1000) datetime: lambda v: int(v.timestamp() * 1000)

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from json import loads from json import loads
from uuid import uuid4
from ..types.input_media import InputMedia, InputMediaBuffer from ..types.input_media import InputMedia, InputMediaBuffer
from ..enums.upload_type import UploadType from ..enums.upload_type import UploadType
@@ -46,6 +47,7 @@ async def process_input_media(
) )
elif isinstance(att, InputMediaBuffer): elif isinstance(att, InputMediaBuffer):
upload_file_response = await base_connection.upload_file_buffer( upload_file_response = await base_connection.upload_file_buffer(
filename=att.filename or str(uuid4()),
url=upload.url, url=upload.url,
buffer=att.buffer, buffer=att.buffer,
type=att.type, type=att.type,
@@ -53,8 +55,10 @@ async def process_input_media(
if att.type in (UploadType.VIDEO, UploadType.AUDIO): if att.type in (UploadType.VIDEO, UploadType.AUDIO):
if upload.token is None: if upload.token is None:
assert bot.session is not None
await bot.session.close() if bot.session is not None:
await bot.session.close()
raise MaxUploadFileFailed('По неизвестной причине token не был получен') raise MaxUploadFileFailed('По неизвестной причине token не был получен')
token = upload.token token = upload.token

77
maxapi/utils/updates.py Normal file
View File

@@ -0,0 +1,77 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..types.updates.bot_added import BotAdded
from ..types.updates.bot_removed import BotRemoved
from ..types.updates.bot_started import BotStarted
from ..types.updates.bot_stopped import BotStopped
from ..types.updates.chat_title_changed import ChatTitleChanged
from ..types.updates.message_callback import MessageCallback
from ..types.updates.message_created import MessageCreated
from ..types.updates.message_edited import MessageEdited
from ..types.updates.message_removed import MessageRemoved
from ..types.updates.user_added import UserAdded
from ..types.updates.user_removed import UserRemoved
from ..types.updates.dialog_cleared import DialogCleared
from ..types.updates.dialog_muted import DialogMuted
from ..types.updates.dialog_unmuted import DialogUnmuted
from ..enums.chat_type import ChatType
if TYPE_CHECKING:
from ..bot import Bot
async def enrich_event(event_object: Any, bot: Bot) -> Any:
if not bot.auto_requests:
return event_object
if hasattr(event_object, 'chat_id'):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
if isinstance(event_object, (MessageCreated, MessageEdited, MessageCallback)):
if event_object.message.recipient.chat_id is not None:
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id)
event_object.from_user = getattr(event_object.message, 'sender', None)
elif isinstance(event_object, MessageRemoved):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
if event_object.chat.type == ChatType.CHAT:
event_object.from_user = await bot.get_chat_member(
chat_id=event_object.chat_id,
user_id=event_object.user_id
)
elif event_object.chat.type == ChatType.DIALOG:
event_object.from_user = event_object.chat
elif isinstance(event_object, UserRemoved):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
if event_object.admin_id:
event_object.from_user = await bot.get_chat_member(
chat_id=event_object.chat_id,
user_id=event_object.admin_id
)
elif isinstance(event_object, UserAdded):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
event_object.from_user = event_object.user
elif isinstance(event_object, (BotAdded, BotRemoved, BotStarted, ChatTitleChanged, BotStopped, DialogCleared, DialogMuted, DialogUnmuted)):
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
event_object.from_user = event_object.user
if hasattr(event_object, 'message'):
event_object.message.bot = bot
for att in event_object.message.body.attachments:
if hasattr(att, 'bot'):
att.bot = bot
if hasattr(event_object, 'bot'):
event_object.bot = bot
return event_object

View File

@@ -6,6 +6,10 @@
| `bot_added` | Бот добавлен в чат | | `bot_added` | Бот добавлен в чат |
| `bot_removed` | Бот удалён из чата | | `bot_removed` | Бот удалён из чата |
| `bot_started` | Пользователь запустил бота | | `bot_started` | Пользователь запустил бота |
| `bot_stopped` | Пользователь остановил бота |
| `dialog_cleared` | Пользователь очистил историю диалога с ботом |
| `dialog_muted` | Пользователь отключил оповещения от чата бота |
| `dialog_unmuted` | Пользователь включил оповещения от чата бота |
| `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, ждем исправлений) |

View File

@@ -81,13 +81,21 @@ async def init_serve(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080, **
import asyncio import asyncio
import logging import logging
from fastapi import Request try:
from fastapi.responses import JSONResponse from fastapi import Request
from fastapi.responses import JSONResponse
except ImportError:
raise ImportError(
'\n\t Не установлен fastapi!'
'\n\t Выполните команду для установки fastapi: '
'\n\t pip install fastapi>=0.68.0'
'\n\t Или сразу все зависимости для работы вебхука:'
'\n\t pip install maxapi[webhook]'
)
from maxapi import Bot, Dispatcher from maxapi import Bot, Dispatcher
from maxapi.methods.types.getted_updates import process_update_webhook from maxapi.methods.types.getted_updates import process_update_webhook
from maxapi.types import MessageCreated from maxapi.types import MessageCreated
from maxapi.dispatcher import webhook_app
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@@ -101,7 +109,7 @@ async def handle_message(event: MessageCreated):
# Регистрация обработчика # Регистрация обработчика
# для вебхука # для вебхука
@webhook_app.post('/') @dp.webhook_post('/')
async def _(request: Request): async def _(request: Request):
# Сериализация полученного запроса # Сериализация полученного запроса
@@ -133,7 +141,6 @@ async def main():
if __name__ == '__main__': if __name__ == '__main__':
asyncio.run(main()) asyncio.run(main())
``` ```
--- ---