Compare commits
25 Commits
2420e4232e
...
6f86d15de4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f86d15de4 | |||
| 7ea24fe2af | |||
| 39fb0c5823 | |||
| af84301e4f | |||
| ec432fe8ce | |||
| 1bfd93f2ea | |||
| 7925087ac7 | |||
| 7ed540683c | |||
| 30350c8521 | |||
| 54683256ce | |||
| be7f98976e | |||
| 54c073ab76 | |||
| 29b319768b | |||
| 5e98e540ea | |||
| 1df293f44d | |||
| c667b82a6c | |||
| 62523c1eb2 | |||
| b0b7040206 | |||
| 29d3d7c042 | |||
| fd048e8544 | |||
| 32c0ca7647 | |||
| 02b4e2d39a | |||
| 354c296fed | |||
| 8f93cf36e4 | |||
| e1064761e4 |
@@ -13,7 +13,13 @@ from maxapi.types import (
|
||||
MessageEdited,
|
||||
MessageRemoved,
|
||||
UserAdded,
|
||||
UserRemoved
|
||||
UserRemoved,
|
||||
BotStopped,
|
||||
DialogCleared,
|
||||
DialogMuted,
|
||||
DialogUnmuted,
|
||||
ChatButton,
|
||||
MessageChatCreated
|
||||
)
|
||||
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||
|
||||
@@ -38,9 +44,9 @@ async def hello(event: MessageCreated):
|
||||
)
|
||||
)
|
||||
builder.add(
|
||||
CallbackButton(
|
||||
text='Кнопка 3',
|
||||
payload='btn_3',
|
||||
ChatButton(
|
||||
text='Создать чат',
|
||||
chat_title='Тест чат'
|
||||
)
|
||||
)
|
||||
|
||||
@@ -80,7 +86,7 @@ async def bot_started(event: BotStarted):
|
||||
async def chat_title_changed(event: ChatTitleChanged):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Крутое новое название "{event.chat.title}!"'
|
||||
text=f'Крутое новое название "{event.chat.title}"!'
|
||||
)
|
||||
|
||||
|
||||
@@ -112,6 +118,34 @@ async def user_added(event: UserAdded):
|
||||
chat_id=event.chat_id,
|
||||
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
||||
)
|
||||
|
||||
|
||||
@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():
|
||||
|
||||
@@ -44,7 +44,7 @@ async def echo(event: MessageCreated):
|
||||
|
||||
|
||||
@dp.message_created(Command('builder'))
|
||||
async def echo(event: MessageCreated):
|
||||
async def builder(event: MessageCreated):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(
|
||||
@@ -88,7 +88,7 @@ async def echo(event: MessageCreated):
|
||||
|
||||
|
||||
@dp.message_created(Command('payload'))
|
||||
async def echo(event: MessageCreated):
|
||||
async def payload(event: MessageCreated):
|
||||
buttons = [
|
||||
[
|
||||
# кнопку типа "chat" убрали из документации,
|
||||
@@ -133,7 +133,7 @@ async def echo(event: MessageCreated):
|
||||
|
||||
|
||||
@dp.message_chat_created()
|
||||
async def callback(obj: MessageChatCreated):
|
||||
async def message_chat_created(obj: MessageChatCreated):
|
||||
await obj.bot.send_message(
|
||||
chat_id=obj.chat.chat_id,
|
||||
text=f'Чат создан! Ссылка: {obj.chat.link}'
|
||||
@@ -141,7 +141,7 @@ async def callback(obj: MessageChatCreated):
|
||||
|
||||
|
||||
@dp.message_callback()
|
||||
async def callback(callback: MessageCallback):
|
||||
async def message_callback(callback: MessageCallback):
|
||||
await callback.message.answer('Вы нажали на Callback!')
|
||||
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ async def handle_message(event: MessageCreated):
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.handle_webhook(bot)
|
||||
await dp.handle_webhook(bot, log_level='critical')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
try:
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'\n\t Не установлен fastapi!'
|
||||
'\n\t Выполните команду для установки fastapi: '
|
||||
'\n\t pip install fastapi>=0.68.0'
|
||||
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||
'\n\t pip install maxapi[webhook]'
|
||||
)
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.methods.types.getted_updates import process_update_webhook
|
||||
from maxapi.types import MessageCreated
|
||||
from maxapi.dispatcher import webhook_app
|
||||
|
||||
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):
|
||||
|
||||
# Сериализация полученного запроса
|
||||
|
||||
@@ -3,8 +3,8 @@ from .dispatcher import Dispatcher, Router
|
||||
from .filters import F
|
||||
|
||||
__all__ = [
|
||||
Bot,
|
||||
Dispatcher,
|
||||
F,
|
||||
Router
|
||||
'Bot',
|
||||
'Dispatcher',
|
||||
'F',
|
||||
'Router'
|
||||
]
|
||||
@@ -49,7 +49,6 @@ if TYPE_CHECKING:
|
||||
from .types.chats import Chat, ChatMember, Chats
|
||||
from .types.command import BotCommand
|
||||
from .types.message import Message, Messages, NewMessageLink
|
||||
from .types.updates import UpdateUnion
|
||||
from .types.users import ChatAdmin, User
|
||||
|
||||
from .methods.types.added_admin_chat import AddedListAdminChat
|
||||
|
||||
@@ -4,7 +4,6 @@ import os
|
||||
import mimetypes
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
import aiofiles
|
||||
import puremagic
|
||||
@@ -70,7 +69,8 @@ class BaseConnection:
|
||||
- dict (если is_return_raw=True)
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
if not self.bot.session:
|
||||
self.bot.session = ClientSession(
|
||||
@@ -100,7 +100,8 @@ class BaseConnection:
|
||||
|
||||
raw = await r.json()
|
||||
|
||||
if is_return_raw: return raw
|
||||
if is_return_raw:
|
||||
return raw
|
||||
|
||||
model = model(**raw) # type: ignore
|
||||
|
||||
@@ -154,6 +155,7 @@ class BaseConnection:
|
||||
|
||||
async def upload_file_buffer(
|
||||
self,
|
||||
filename: str,
|
||||
url: str,
|
||||
buffer: bytes,
|
||||
type: UploadType
|
||||
@@ -180,7 +182,7 @@ class BaseConnection:
|
||||
mime_type = f"{type.value}/*"
|
||||
ext = ''
|
||||
|
||||
basename = f'{uuid4()}{ext}'
|
||||
basename = f'{filename}{ext}'
|
||||
|
||||
form = FormData()
|
||||
form.add_field(
|
||||
|
||||
@@ -1,93 +1,9 @@
|
||||
import asyncio
|
||||
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from ..context.state_machine import State, StatesGroup
|
||||
from .context import MemoryContext
|
||||
|
||||
|
||||
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 = {}
|
||||
__all__ = [
|
||||
'State',
|
||||
'StatesGroup',
|
||||
'MemoryContext'
|
||||
]
|
||||
93
maxapi/context/context.py
Normal file
93
maxapi/context/context.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import asyncio
|
||||
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from ..context.state_machine import State
|
||||
|
||||
|
||||
class MemoryContext:
|
||||
|
||||
"""
|
||||
Контекст хранения данных пользователя с блокировками.
|
||||
|
||||
Args:
|
||||
chat_id (int): Идентификатор чата
|
||||
user_id (int): Идентификатор пользователя
|
||||
"""
|
||||
|
||||
def __init__(self, chat_id: int, user_id: int):
|
||||
self.chat_id = chat_id
|
||||
self.user_id = user_id
|
||||
self._context: Dict[str, Any] = {}
|
||||
self._state: State | str | None = None
|
||||
self._lock = asyncio.Lock()
|
||||
|
||||
async def get_data(self) -> dict[str, Any]:
|
||||
|
||||
"""
|
||||
Возвращает текущий контекст данных.
|
||||
|
||||
Returns:
|
||||
Словарь с данными контекста
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
return self._context
|
||||
|
||||
async def set_data(self, data: dict[str, Any]):
|
||||
|
||||
"""
|
||||
Полностью заменяет контекст данных.
|
||||
|
||||
Args:
|
||||
data: Новый словарь контекста
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
self._context = data
|
||||
|
||||
async def update_data(self, **kwargs):
|
||||
|
||||
"""
|
||||
Обновляет контекст данных новыми значениями.
|
||||
|
||||
Args:
|
||||
**kwargs: Пары ключ-значение для обновления
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
self._context.update(kwargs)
|
||||
|
||||
async def set_state(self, state: Optional[Union[State, str]] = None):
|
||||
|
||||
"""
|
||||
Устанавливает новое состояние.
|
||||
|
||||
Args:
|
||||
state: Новое состояние или None для сброса
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
self._state = state
|
||||
|
||||
async def get_state(self):
|
||||
|
||||
"""
|
||||
Возвращает текущее состояние.
|
||||
|
||||
Returns:
|
||||
Текущее состояние или None
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
return self._state
|
||||
|
||||
async def clear(self):
|
||||
|
||||
"""
|
||||
Очищает контекст и сбрасывает состояние.
|
||||
"""
|
||||
|
||||
async with self._lock:
|
||||
self._state = None
|
||||
self._context = {}
|
||||
@@ -5,9 +5,6 @@ import asyncio
|
||||
from typing import Any, Callable, Dict, List, TYPE_CHECKING, Optional
|
||||
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 .filters.middleware import BaseMiddleware
|
||||
@@ -17,7 +14,7 @@ from .context import MemoryContext
|
||||
from .types.updates import UpdateUnion
|
||||
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
|
||||
|
||||
@@ -25,12 +22,25 @@ from .bot import Bot
|
||||
from .enums.update import UpdateType
|
||||
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:
|
||||
from magic_filter import MagicFilter
|
||||
|
||||
|
||||
webhook_app = FastAPI()
|
||||
|
||||
CONNECTION_RETRY_DELAY = 30
|
||||
GET_UPDATES_RETRY_DELAY = 5
|
||||
|
||||
@@ -44,12 +54,14 @@ class Dispatcher:
|
||||
применение 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.contexts: List[MemoryContext] = []
|
||||
self.routers: List[Router | Dispatcher] = []
|
||||
@@ -57,12 +69,17 @@ class Dispatcher:
|
||||
self.middlewares: List[BaseMiddleware] = []
|
||||
|
||||
self.bot: Optional[Bot] = None
|
||||
self.webhook_app: Optional[FastAPI] = None
|
||||
self.on_started_func: Optional[Callable] = None
|
||||
|
||||
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, 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_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.message_callback = Event(update_type=UpdateType.MESSAGE_CALLBACK, 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.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):
|
||||
|
||||
"""
|
||||
@@ -178,14 +212,19 @@ class Dispatcher:
|
||||
memory_context = self.__get_memory_context(*ids)
|
||||
current_state = await memory_context.get_state()
|
||||
kwargs = {'context': memory_context}
|
||||
router_id = None
|
||||
|
||||
process_info = f'{event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}'
|
||||
|
||||
is_handled = False
|
||||
|
||||
for router in self.routers:
|
||||
for index, router in enumerate(self.routers):
|
||||
|
||||
if is_handled:
|
||||
break
|
||||
|
||||
router_id = router.router_id or index
|
||||
|
||||
if router.filters:
|
||||
if not filter_attrs(event_object, *router.filters):
|
||||
continue
|
||||
@@ -220,21 +259,21 @@ class Dispatcher:
|
||||
continue
|
||||
|
||||
for key in kwargs.copy().keys():
|
||||
if not key in func_args:
|
||||
if key not in func_args:
|
||||
del kwargs[key]
|
||||
|
||||
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
|
||||
break
|
||||
|
||||
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:
|
||||
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):
|
||||
|
||||
@@ -247,9 +286,12 @@ class Dispatcher:
|
||||
await self.__ready(bot)
|
||||
|
||||
while True:
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
try:
|
||||
events: Dict = await self.bot.get_updates() # type: ignore
|
||||
events: Dict = await self.bot.get_updates()
|
||||
except AsyncioTimeoutError:
|
||||
continue
|
||||
|
||||
@@ -260,11 +302,11 @@ class Dispatcher:
|
||||
await asyncio.sleep(GET_UPDATES_RETRY_DELAY)
|
||||
continue
|
||||
|
||||
self.bot.marker_updates = events.get('marker') # type: ignore
|
||||
self.bot.marker_updates = events.get('marker')
|
||||
|
||||
processed_events = await process_update_request(
|
||||
events=events,
|
||||
bot=self.bot # type: ignore
|
||||
bot=self.bot
|
||||
)
|
||||
|
||||
for event in processed_events:
|
||||
@@ -276,7 +318,7 @@ class Dispatcher:
|
||||
except Exception as 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-приложение для приёма обновлений через вебхук.
|
||||
@@ -285,30 +327,58 @@ class Dispatcher:
|
||||
:param host: Хост, на котором запускается сервер.
|
||||
: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):
|
||||
try:
|
||||
event_json = await request.json()
|
||||
|
||||
event_object = await process_update_webhook(
|
||||
event_json=event_json,
|
||||
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}")
|
||||
|
||||
event_json = await request.json()
|
||||
event_object = await process_update_webhook(
|
||||
event_json=event_json,
|
||||
bot=bot
|
||||
)
|
||||
|
||||
await self.handle(event_object)
|
||||
return JSONResponse(content={'ok': True}, status_code=200)
|
||||
|
||||
|
||||
await self.init_serve(
|
||||
bot=bot,
|
||||
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: Порт сервера.
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
await self.__ready(bot)
|
||||
@@ -332,8 +425,8 @@ class Router(Dispatcher):
|
||||
Роутер для группировки обработчиков событий.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, router_id: str | None = None):
|
||||
super().__init__(router_id)
|
||||
|
||||
|
||||
class Event:
|
||||
|
||||
@@ -20,6 +20,10 @@ class UpdateType(str, Enum):
|
||||
MESSAGE_REMOVED = 'message_removed'
|
||||
USER_ADDED = 'user_added'
|
||||
USER_REMOVED = 'user_removed'
|
||||
BOT_STOPPED = 'bot_stopped'
|
||||
DIALOG_CLEARED = 'dialog_cleared'
|
||||
DIALOG_MUTED = 'dialog_muted'
|
||||
DIALOG_UNMUTED = 'dialog_unmuted'
|
||||
|
||||
# Для начинки диспатчера
|
||||
ON_STARTED = 'on_started'
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Callable, List
|
||||
from typing import Callable, List, Optional
|
||||
|
||||
from magic_filter import F, MagicFilter
|
||||
|
||||
@@ -44,7 +44,7 @@ class Handler:
|
||||
self.func_event: Callable = func_event
|
||||
self.update_type: UpdateType = update_type
|
||||
self.filters = []
|
||||
self.state: State = None
|
||||
self.state: Optional[State] = None
|
||||
self.middlewares: List[BaseMiddleware] = []
|
||||
|
||||
for arg in args:
|
||||
|
||||
@@ -19,9 +19,9 @@ class BaseMiddleware:
|
||||
kwargs_temp = {'data': result_data_kwargs.copy()}
|
||||
|
||||
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]
|
||||
|
||||
result: Dict[str, Any] = await self(event_object, **kwargs_temp)
|
||||
result: Dict[str, Any] = await self(event_object, **kwargs_temp) # type: ignore
|
||||
|
||||
return result
|
||||
@@ -48,7 +48,8 @@ class AddAdminChat(BaseConnection):
|
||||
AddedListAdminChat: Результат операции с информацией об успешности.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
|
||||
@@ -45,7 +45,8 @@ class AddMembersChat(BaseConnection):
|
||||
AddedMembersChat: Результат операции с информацией об успешности добавления.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
|
||||
@@ -48,14 +48,19 @@ class ChangeInfo(BaseConnection):
|
||||
User: Объект с обновленными данными бота
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
if self.name: json['name'] = self.name
|
||||
if self.description: json['description'] = self.description
|
||||
if self.commands: json['commands'] = [command.model_dump() for command in self.commands]
|
||||
if self.photo: json['photo'] = self.photo
|
||||
if self.name:
|
||||
json['name'] = self.name
|
||||
if self.description:
|
||||
json['description'] = self.description
|
||||
if self.commands:
|
||||
json['commands'] = [command.model_dump() for command in self.commands]
|
||||
if self.photo:
|
||||
json['photo'] = self.photo
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.PATCH,
|
||||
|
||||
@@ -39,7 +39,8 @@ class DeleteMeFromMessage(BaseConnection):
|
||||
DeletedBotFromChat: Результат операции удаления.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
return await super().request(
|
||||
method=HTTPMethod.DELETE,
|
||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
||||
|
||||
@@ -38,7 +38,9 @@ class DeleteChat(BaseConnection):
|
||||
DeletedChat: Результат операции удаления чата.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.DELETE,
|
||||
path=ApiPath.CHATS.value + '/' + str(self.chat_id),
|
||||
|
||||
@@ -40,7 +40,9 @@ class DeleteMessage(BaseConnection):
|
||||
DeletedMessage: Результат операции удаления сообщения.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['message_id'] = self.message_id
|
||||
|
||||
@@ -38,7 +38,10 @@ class DeletePinMessage(BaseConnection):
|
||||
Returns:
|
||||
DeletedPinMessage: Результат операции удаления закреплённого сообщения.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.DELETE,
|
||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -64,14 +64,16 @@ class EditChat(BaseConnection):
|
||||
Chat: Обновлённый объект чата.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
if self.icon:
|
||||
dump = self.icon.model_dump()
|
||||
counter = Counter(dump.values())
|
||||
|
||||
if not None in counter or \
|
||||
if None not in counter or \
|
||||
not counter[None] == 2:
|
||||
|
||||
raise MaxIconParamsException(
|
||||
@@ -81,9 +83,12 @@ class EditChat(BaseConnection):
|
||||
|
||||
json['icon'] = dump
|
||||
|
||||
if self.title: json['title'] = self.title
|
||||
if self.pin: json['pin'] = self.pin
|
||||
if self.notify: json['notify'] = self.notify
|
||||
if self.title:
|
||||
json['title'] = self.title
|
||||
if self.pin:
|
||||
json['pin'] = self.pin
|
||||
if self.notify:
|
||||
json['notify'] = self.notify
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.PATCH,
|
||||
|
||||
@@ -66,14 +66,17 @@ class EditMessage(BaseConnection):
|
||||
EditedMessage: Обновлённое сообщение.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
json: Dict[str, Any] = {'attachments': []}
|
||||
|
||||
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:
|
||||
|
||||
@@ -91,9 +94,12 @@ class EditMessage(BaseConnection):
|
||||
else:
|
||||
json['attachments'].append(att.model_dump())
|
||||
|
||||
if not self.link is None: json['link'] = self.link.model_dump()
|
||||
if not self.notify is None: json['notify'] = self.notify
|
||||
if not self.parse_mode is None: json['format'] = self.parse_mode.value
|
||||
if self.link is not None:
|
||||
json['link'] = self.link.model_dump()
|
||||
if self.notify is not None:
|
||||
json['notify'] = self.notify
|
||||
if self.parse_mode is not None:
|
||||
json['format'] = self.parse_mode.value
|
||||
|
||||
await asyncio.sleep(self.bot.after_input_media_delay)
|
||||
|
||||
|
||||
@@ -39,7 +39,9 @@ class GetChatById(BaseConnection):
|
||||
Chat: Объект чата с полной информацией.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.CHATS.value + '/' + str(self.id),
|
||||
|
||||
@@ -49,7 +49,9 @@ class GetChatByLink(BaseConnection):
|
||||
Chat: Объект с информацией о чате.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.CHATS.value + '/' + self.link[-1],
|
||||
|
||||
@@ -46,7 +46,10 @@ class GetChats(BaseConnection):
|
||||
Returns:
|
||||
Chats: Объект с данными по списку чатов.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['count'] = self.count
|
||||
|
||||
@@ -42,7 +42,10 @@ class GetListAdminChat(BaseConnection):
|
||||
Returns:
|
||||
GettedListAdminChat: Объект с информацией о администраторах чата.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,
|
||||
|
||||
@@ -32,7 +32,10 @@ class GetMe(BaseConnection):
|
||||
Returns:
|
||||
User: Объект пользователя с полной информацией.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.ME,
|
||||
|
||||
@@ -42,7 +42,10 @@ class GetMeFromChat(BaseConnection):
|
||||
Returns:
|
||||
ChatMember: Информация о боте как участнике чата.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
|
||||
|
||||
@@ -57,14 +57,19 @@ class GetMembersChat(BaseConnection):
|
||||
Returns:
|
||||
GettedMembersChat: Объект с данными по участникам чата.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
if 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.count: params['marker'] = self.count
|
||||
if self.marker:
|
||||
params['marker'] = self.marker
|
||||
if self.count:
|
||||
params['marker'] = self.count
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
|
||||
@@ -59,10 +59,14 @@ class GetMessages(BaseConnection):
|
||||
Returns:
|
||||
Messages: Объект с полученными сообщениями.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
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:
|
||||
params['message_ids'] = ','.join(self.message_ids)
|
||||
|
||||
@@ -37,7 +37,10 @@ class GetPinnedMessage(BaseConnection):
|
||||
Returns:
|
||||
GettedPin: Объект с информацией о закреплённом сообщении.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Dict
|
||||
|
||||
from ..types.updates import UpdateUnion
|
||||
|
||||
from ..enums.http_method import HTTPMethod
|
||||
from ..enums.api_path import ApiPath
|
||||
|
||||
@@ -45,7 +43,10 @@ class GetUpdates(BaseConnection):
|
||||
Returns:
|
||||
UpdateUnion: Объединённый тип данных обновлений.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['limit'] = self.limit
|
||||
|
||||
@@ -43,7 +43,10 @@ class GetUploadURL(BaseConnection):
|
||||
Returns:
|
||||
GettedUploadUrl: Результат с URL для загрузки.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['type'] = self.type.value
|
||||
|
||||
@@ -38,7 +38,10 @@ class GetVideo(BaseConnection):
|
||||
Returns:
|
||||
Video: Объект с информацией о видео.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.GET,
|
||||
path=ApiPath.VIDEOS.value + '/' + self.video_token,
|
||||
|
||||
@@ -52,7 +52,10 @@ class PinMessage(BaseConnection):
|
||||
Returns:
|
||||
PinnedMessage: Объект с информацией о закреплённом сообщении.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
json['message_id'] = self.message_id
|
||||
|
||||
@@ -46,7 +46,10 @@ class RemoveAdmin(BaseConnection):
|
||||
Returns:
|
||||
RemovedAdmin: Объект с результатом отмены прав администратора.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.DELETE,
|
||||
path=ApiPath.CHATS + '/' + str(self.chat_id) + \
|
||||
|
||||
@@ -54,7 +54,9 @@ class RemoveMemberChat(BaseConnection):
|
||||
RemovedMemberChat: Результат удаления участника.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['chat_id'] = self.chat_id
|
||||
|
||||
@@ -49,7 +49,9 @@ class SendAction(BaseConnection):
|
||||
Returns:
|
||||
SendedAction: Результат выполнения запроса.
|
||||
"""
|
||||
assert self.bot is not None
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
|
||||
@@ -55,15 +55,19 @@ class SendCallback(BaseConnection):
|
||||
SendedCallback: Объект с результатом отправки callback.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
params['callback_id'] = self.callback_id
|
||||
|
||||
json: Dict[str, Any] = {}
|
||||
|
||||
if self.message: json['message'] = self.message.model_dump()
|
||||
if self.notification: json['notification'] = self.notification
|
||||
if self.message:
|
||||
json['message'] = self.message.model_dump()
|
||||
if self.notification:
|
||||
json['notification'] = self.notification
|
||||
|
||||
return await super().request(
|
||||
method=HTTPMethod.POST,
|
||||
|
||||
@@ -70,21 +70,29 @@ class SendMessage(BaseConnection):
|
||||
SendedMessage или Error
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
json: Dict[str, Any] = {'attachments': []}
|
||||
|
||||
if self.chat_id: params['chat_id'] = self.chat_id
|
||||
elif self.user_id: params['user_id'] = self.user_id
|
||||
if self.chat_id:
|
||||
params['chat_id'] = self.chat_id
|
||||
elif self.user_id:
|
||||
params['user_id'] = self.user_id
|
||||
|
||||
json['text'] = self.text
|
||||
|
||||
HAS_INPUT_MEDIA = False
|
||||
|
||||
if 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(
|
||||
base_connection=self,
|
||||
bot=self.bot,
|
||||
@@ -96,11 +104,16 @@ class SendMessage(BaseConnection):
|
||||
else:
|
||||
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['link'] = self.link.model_dump()
|
||||
|
||||
json['notify'] = self.notify
|
||||
if not self.parse_mode is None: json['format'] = self.parse_mode.value
|
||||
|
||||
await asyncio.sleep(self.bot.after_input_media_delay)
|
||||
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
|
||||
for attempt in range(self.ATTEMPTS_COUNT):
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ...utils.updates import enrich_event
|
||||
|
||||
from ...enums.update import UpdateType
|
||||
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_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.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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
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'):
|
||||
event_object = None
|
||||
|
||||
match event['update_type']:
|
||||
|
||||
case UpdateType.BOT_ADDED:
|
||||
event_object = BotAdded(**event)
|
||||
|
||||
case UpdateType.BOT_REMOVED:
|
||||
event_object = BotRemoved(**event)
|
||||
|
||||
case UpdateType.BOT_STARTED:
|
||||
event_object = BotStarted(**event)
|
||||
update_type = event['update_type']
|
||||
model_cls = UPDATE_MODEL_MAPPING.get(update_type)
|
||||
|
||||
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
|
||||
if not model_cls:
|
||||
raise ValueError(f'Unknown update type: {update_type}')
|
||||
|
||||
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
|
||||
event_object = await enrich_event(
|
||||
event_object=model_cls(**event),
|
||||
bot=bot
|
||||
)
|
||||
|
||||
return event_object
|
||||
|
||||
|
||||
|
||||
async def process_update_request(events: dict, bot: 'Bot'):
|
||||
events = [event for event in events['updates']]
|
||||
|
||||
objects = []
|
||||
|
||||
for event in events:
|
||||
|
||||
objects.append(
|
||||
await get_update_model(
|
||||
bot=bot,
|
||||
event=event
|
||||
)
|
||||
)
|
||||
|
||||
return objects
|
||||
return [
|
||||
await get_update_model(event, bot)
|
||||
for event in events['updates']
|
||||
]
|
||||
|
||||
|
||||
async def process_update_webhook(event_json: dict, bot: 'Bot'):
|
||||
|
||||
@@ -21,4 +21,4 @@ class SendedCallback(BaseModel):
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
bot: Optional[Bot] # type: ignore
|
||||
|
||||
@@ -9,6 +9,10 @@ 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.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.attachments.attachment import Attachment
|
||||
@@ -32,6 +36,10 @@ from .input_media import InputMedia
|
||||
from .input_media import InputMediaBuffer
|
||||
|
||||
__all__ = [
|
||||
'DialogUnmuted',
|
||||
'DialogMuted',
|
||||
'DialogCleared',
|
||||
'BotStopped',
|
||||
'CommandStart',
|
||||
'OpenAppButton',
|
||||
'Message',
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...exceptions.download_file import NotAvailableForDownload
|
||||
|
||||
from ...types.attachments.upload import AttachmentUpload
|
||||
from ...types.attachments.buttons import InlineButtonUnion
|
||||
from ...types.users import User
|
||||
|
||||
@@ -17,8 +17,7 @@ class ChatButton(Button):
|
||||
"""
|
||||
|
||||
type: ButtonType = ButtonType.CHAT
|
||||
chat_title: Optional[str] = None
|
||||
chat_title: str
|
||||
chat_description: Optional[str] = None
|
||||
start_payload: Optional[str] = None
|
||||
chat_title: Optional[str] = None
|
||||
uuid: Optional[int] = None
|
||||
uuid: Optional[int] = None
|
||||
@@ -1,5 +1,3 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ....enums.button_type import ButtonType
|
||||
|
||||
from .button import Button
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from ....enums.button_type import ButtonType
|
||||
|
||||
from .button import Button
|
||||
|
||||
@@ -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 ...enums.attachment import AttachmentType
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import puremagic
|
||||
|
||||
from ..enums.upload_type import UploadType
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class InputMedia:
|
||||
"""
|
||||
Класс для представления медиафайла.
|
||||
@@ -74,13 +68,15 @@ class InputMediaBuffer:
|
||||
type (UploadType): Тип файла, определенный по содержимому.
|
||||
"""
|
||||
|
||||
def __init__(self, buffer: bytes):
|
||||
def __init__(self, buffer: bytes, filename: str | None = None):
|
||||
"""
|
||||
Инициализирует объект медиафайла из буфера.
|
||||
|
||||
Args:
|
||||
buffer (IO): Буфер с содержимым файла.
|
||||
filename (str): Название файла (по умолчанию присваивается uuid4).
|
||||
"""
|
||||
self.filename = filename
|
||||
self.buffer = buffer
|
||||
self.type = self.__detect_file_type(buffer)
|
||||
|
||||
|
||||
@@ -195,7 +195,9 @@ class Message(BaseModel):
|
||||
Any: Результат выполнения метода send_message бота.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await self.bot.send_message(
|
||||
chat_id=self.recipient.chat_id,
|
||||
user_id=self.recipient.user_id,
|
||||
@@ -227,7 +229,9 @@ class Message(BaseModel):
|
||||
Any: Результат выполнения метода send_message бота.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await self.bot.send_message(
|
||||
chat_id=self.recipient.chat_id,
|
||||
user_id=self.recipient.user_id,
|
||||
@@ -264,7 +268,9 @@ class Message(BaseModel):
|
||||
Any: Результат выполнения метода send_message бота.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await self.bot.send_message(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
@@ -300,7 +306,9 @@ class Message(BaseModel):
|
||||
Any: Результат выполнения метода edit_message бота.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await self.bot.edit_message(
|
||||
message_id=self.body.mid,
|
||||
text=text,
|
||||
@@ -335,7 +343,9 @@ class Message(BaseModel):
|
||||
Any: Результат выполнения метода pin_message бота.
|
||||
"""
|
||||
|
||||
assert self.bot is not None
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
return await self.bot.pin_message(
|
||||
chat_id=self.recipient.chat_id,
|
||||
message_id=self.body.mid,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
@@ -14,12 +14,14 @@ class BotAdded(Update):
|
||||
Обновление, сигнализирующее о добавлении бота в чат.
|
||||
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата, куда добавлен бот.
|
||||
chat_id (int): Идентификатор чата, куда добавлен бот.
|
||||
user (User): Объект пользователя-бота.
|
||||
is_channel (bool): Указывает, был ли бот добавлен в канал или нет
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
is_channel: bool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
@@ -14,12 +14,14 @@ class BotRemoved(Update):
|
||||
Обновление, сигнализирующее об удалении бота из чата.
|
||||
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата, из которого удалён бот.
|
||||
chat_id (int): Идентификатор чата, из которого удалён бот.
|
||||
user (User): Объект пользователя-бота.
|
||||
is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
is_channel: bool
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
@@ -14,13 +14,13 @@ class BotStarted(Update):
|
||||
Обновление, сигнализирующее о первом старте бота.
|
||||
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата.
|
||||
chat_id (int): Идентификатор чата.
|
||||
user (User): Пользователь (бот).
|
||||
user_locale (Optional[str]): Локаль пользователя.
|
||||
payload (Optional[str]): Дополнительные данные.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
user_locale: Optional[str] = None
|
||||
payload: Optional[str] = None
|
||||
|
||||
32
maxapi/types/updates/bot_stopped.py
Normal file
32
maxapi/types/updates/bot_stopped.py
Normal 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)
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
@@ -16,12 +16,12 @@ class ChatTitleChanged(Update):
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата.
|
||||
user (User): Пользователь, совершивший изменение.
|
||||
title (Optional[str]): Новое название чата.
|
||||
title (str): Новое название чата.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
title: Optional[str] = None
|
||||
title: str
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
30
maxapi/types/updates/dialog_cleared.py
Normal file
30
maxapi/types/updates/dialog_cleared.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 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)
|
||||
40
maxapi/types/updates/dialog_muted.py
Normal file
40
maxapi/types/updates/dialog_muted.py
Normal 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)
|
||||
30
maxapi/types/updates/dialog_unmuted.py
Normal file
30
maxapi/types/updates/dialog_unmuted.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 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)
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Optional, TYPE_CHECKING, Union
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -21,12 +21,6 @@ from ..attachments.video import Video
|
||||
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):
|
||||
|
||||
"""
|
||||
@@ -110,6 +104,9 @@ class MessageCallback(Update):
|
||||
Результат вызова send_callback бота.
|
||||
"""
|
||||
|
||||
if self.bot is None:
|
||||
raise RuntimeError('Bot не инициализирован')
|
||||
|
||||
message = MessageForCallback()
|
||||
|
||||
message.text = new_text
|
||||
@@ -117,8 +114,7 @@ class MessageCallback(Update):
|
||||
message.link = link
|
||||
message.notify = notify
|
||||
message.format = format
|
||||
|
||||
assert self.bot is not None
|
||||
|
||||
return await self.bot.send_callback(
|
||||
callback_id=self.callback.callback_id,
|
||||
message=message,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from __future__ import annotations
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from typing import Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
from typing import Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
|
||||
@@ -9,14 +7,14 @@ class MessageRemoved(Update):
|
||||
Класс для обработки события удаления сообщения в чате.
|
||||
|
||||
Attributes:
|
||||
message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
user_id (Optional[int]): Идентификатор пользователя. Может быть None.
|
||||
message_id (str): Идентификатор удаленного сообщения. Может быть None.
|
||||
chat_id (int): Идентификатор чата. Может быть None.
|
||||
user_id (int): Идентификатор пользователя. Может быть None.
|
||||
"""
|
||||
|
||||
message_id: Optional[str] = None
|
||||
chat_id: Optional[int] = None
|
||||
user_id: Optional[int] = None
|
||||
message_id: str
|
||||
chat_id: int
|
||||
user_id: int
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ class Update(BaseModel):
|
||||
chat: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
from_user: Optional[User]
|
||||
chat: Optional[Chat]
|
||||
bot: Optional[Bot] # type: ignore
|
||||
from_user: Optional[User] # type: ignore
|
||||
chat: Optional[Chat] # type: ignore
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed=True
|
||||
@@ -11,14 +11,16 @@ class UserAdded(Update):
|
||||
Класс для обработки события добавления пользователя в чат.
|
||||
|
||||
Attributes:
|
||||
inviter_id (Optional[int]): Идентификатор пользователя, добавившего нового участника. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
inviter_id (int): Идентификатор пользователя, добавившего нового участника. Может быть None.
|
||||
chat_id (int): Идентификатор чата. Может быть None.
|
||||
user (User): Объект пользователя, добавленного в чат.
|
||||
is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет
|
||||
"""
|
||||
|
||||
inviter_id: Optional[int] = None
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
is_channel: bool
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
|
||||
@@ -12,13 +12,15 @@ class UserRemoved(Update):
|
||||
|
||||
Attributes:
|
||||
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
chat_id (int): Идентификатор чата. Может быть None.
|
||||
user (User): Объект пользователя, удаленного из чата.
|
||||
is_channel (bool): Указывает, был ли пользователь удален из канала или нет
|
||||
"""
|
||||
|
||||
admin_id: Optional[int] = None
|
||||
chat_id: Optional[int] = None
|
||||
chat_id: int
|
||||
user: User
|
||||
is_channel: bool
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@ class User(BaseModel):
|
||||
avatar_url: Optional[str] = None
|
||||
full_avatar_url: Optional[str] = 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:
|
||||
json_encoders = {
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from json import loads
|
||||
from uuid import uuid4
|
||||
|
||||
from ..types.input_media import InputMedia, InputMediaBuffer
|
||||
from ..enums.upload_type import UploadType
|
||||
@@ -46,6 +47,7 @@ async def process_input_media(
|
||||
)
|
||||
elif isinstance(att, InputMediaBuffer):
|
||||
upload_file_response = await base_connection.upload_file_buffer(
|
||||
filename=att.filename or str(uuid4()),
|
||||
url=upload.url,
|
||||
buffer=att.buffer,
|
||||
type=att.type,
|
||||
@@ -53,8 +55,10 @@ async def process_input_media(
|
||||
|
||||
if att.type in (UploadType.VIDEO, UploadType.AUDIO):
|
||||
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 не был получен')
|
||||
|
||||
token = upload.token
|
||||
|
||||
77
maxapi/utils/updates.py
Normal file
77
maxapi/utils/updates.py
Normal 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
|
||||
@@ -6,6 +6,10 @@
|
||||
| `bot_added` | Бот добавлен в чат |
|
||||
| `bot_removed` | Бот удалён из чата |
|
||||
| `bot_started` | Пользователь запустил бота |
|
||||
| `bot_stopped` | Пользователь остановил бота |
|
||||
| `dialog_cleared` | Пользователь очистил историю диалога с ботом |
|
||||
| `dialog_muted` | Пользователь отключил оповещения от чата бота |
|
||||
| `dialog_unmuted` | Пользователь включил оповещения от чата бота |
|
||||
| `chat_title_changed` | Изменено название чата |
|
||||
| `message_callback` | Пользователь нажал на callback-кнопку (inline button) |
|
||||
| `message_chat_created`| Срабатывает когда пользователь нажал на кнопку с действием "Создать чат" (работает некорректно со стороны API MAX, ждем исправлений) |
|
||||
|
||||
@@ -81,13 +81,21 @@ async def init_serve(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080, **
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
try:
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
'\n\t Не установлен fastapi!'
|
||||
'\n\t Выполните команду для установки fastapi: '
|
||||
'\n\t pip install fastapi>=0.68.0'
|
||||
'\n\t Или сразу все зависимости для работы вебхука:'
|
||||
'\n\t pip install maxapi[webhook]'
|
||||
)
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.methods.types.getted_updates import process_update_webhook
|
||||
from maxapi.types import MessageCreated
|
||||
from maxapi.dispatcher import webhook_app
|
||||
|
||||
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):
|
||||
|
||||
# Сериализация полученного запроса
|
||||
@@ -133,7 +141,6 @@ async def main():
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
|
||||
```
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user