Добавлены примеры и обновлена модель Update

This commit is contained in:
Денис Семёнов 2025-06-21 02:05:33 +03:00
parent ab52abc474
commit ee58238261
32 changed files with 546 additions and 304 deletions

View File

@ -1,84 +0,0 @@
# Асинхронный MAX API
[![PyPI version](https://img.shields.io/pypi/v/maxapi.svg)](https://pypi.org/project/maxapi/)
[![Python Version](https://img.shields.io/pypi/pyversions/maxapi.svg)](https://pypi.org/project/maxapi/)
[![License](https://img.shields.io/github/license/love-apples/maxapi.svg)](https://love-apples/maxapi/blob/main/LICENSE)
---
## 📦 Установка
```bash
pip install maxapi
```
---
## 🚀 Быстрый старт
```python
import asyncio
import logging
from maxapi import Bot, Dispatcher
from maxapi.types import BotStarted, Command, MessageCreated
logging.basicConfig(level=logging.INFO)
bot = Bot('тут_ваш_токен')
dp = Dispatcher()
@dp.bot_started()
async def bot_started(event: BotStarted):
await event.bot.send_message(
chat_id=event.chat_id,
text='Привет! Отправь мне /start'
)
@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
await event.message.answer(f"Пример чат-бота для MAX 💙")
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())
```
---
## 📚 Документация
В разработке...
---
## 🧩 Возможности
- ✅ Роутеры
- ✅ Билдер инлайн клавиатур
- ✅ Простая загрузка медиафайлов
- ✅ MagicFilter
- ✅ Внутренние функции моделей
- ✅ Контекстный менеджер
- ✅ Поллинг
- ✅ Вебхук
- ✅ Логгирование
---
## 💬 Обратная связь и поддержка
- MAX: [Чат](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8)
- Telegram: [@loveappless](https://t.me/loveappless)
---
## 📄 Лицензия
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для подробностей.

View File

@ -1,31 +0,0 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher
from maxapi.types import BotStarted, Command, MessageCreated
logging.basicConfig(level=logging.INFO)
bot = Bot('тут_ваш_токен')
dp = Dispatcher()
@dp.bot_started()
async def bot_started(event: BotStarted):
await event.bot.send_message(
chat_id=event.chat_id,
text='Привет! Отправь мне /start'
)
@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
await event.message.answer(f"Пример чат-бота для MAX 💙")
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())

24
examples/echo/main.py Normal file
View File

@ -0,0 +1,24 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher
from maxapi.filters import F
from maxapi.types import MessageCreated
logging.basicConfig(level=logging.INFO)
bot = Bot('тут_ваш_токен')
dp = Dispatcher()
@dp.message_created(F.message.body.text)
async def echo(event: MessageCreated):
await event.message.answer(f"Повторяю за вами: {event.message.body.text}")
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())

122
examples/events/main.py Normal file
View File

@ -0,0 +1,122 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher
from maxapi.types import (
BotStarted,
Command,
MessageCreated,
CallbackButton,
MessageCallback,
BotAdded,
ChatTitleChanged,
MessageEdited,
MessageRemoved,
UserAdded,
UserRemoved
)
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
logging.basicConfig(level=logging.INFO)
bot = Bot('тут_ваш_токен')
dp = Dispatcher()
@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
builder = InlineKeyboardBuilder()
builder.row(
CallbackButton(
text='Кнопка 1',
payload='btn_1'
),
CallbackButton(
text='Кнопка 2',
payload='btn_2',
)
)
builder.add(
CallbackButton(
text='Кнопка 3',
payload='btn_3',
)
)
await event.message.answer(
text='Привет!',
attachments=[
builder.as_markup(),
] # Для MAX клавиатура это вложение,
) # поэтому она в списке вложений
@dp.bot_added()
async def bot_added(event: BotAdded):
await event.bot.send_message(
chat_id=event.chat.id,
text=f'Привет чат {event.chat.title}!'
)
@dp.message_removed()
async def message_removed(event: MessageRemoved):
await event.bot.send_message(
chat_id=event.chat_id,
text='Я всё видел!'
)
@dp.bot_started()
async def bot_started(event: BotStarted):
await event.bot.send_message(
chat_id=event.chat_id,
text='Привет! Отправь мне /start'
)
@dp.chat_title_changed()
async def chat_title_changed(event: ChatTitleChanged):
await event.bot.send_message(
chat_id=event.chat_id,
text=f'Крутое новое название "{event.chat.title}!"'
)
@dp.message_callback()
async def message_callback(event: MessageCallback):
await event.answer(
new_text=f'Вы нажали на кнопку {event.callback.payload}!'
)
@dp.message_edited()
async def message_edited(event: MessageEdited):
await event.message.answer(
text='Вы отредактировали сообщение!'
)
@dp.user_removed()
async def user_removed(event: UserRemoved):
await event.bot.send_message(
chat_id=event.chat_id,
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
)
@dp.user_added()
async def user_added(event: UserAdded):
await event.bot.send_message(
chat_id=event.chat_id,
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
)
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())

View File

@ -0,0 +1,48 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher, F
from maxapi.types import MessageCreated
logging.basicConfig(level=logging.INFO)
bot = Bot('тут_ваш_токен')
dp = Dispatcher()
@dp.message_created(F.message.body.text == 'привет')
async def on_hello(event: MessageCreated):
await event.message.answer('Привет!')
@dp.message_created(F.message.body.text.lower().contains('помощь'))
async def on_help(event: MessageCreated):
await event.message.answer('Чем могу помочь?')
@dp.message_created(F.message.body.text.regexp(r'^\d{4}$'))
async def on_code(event: MessageCreated):
await event.message.answer('Принят 4-значный код')
@dp.message_created(F.message.body.attachments)
async def on_attachment(event: MessageCreated):
await event.message.answer('Получено вложение')
@dp.message_created(F.message.body.text.len() > 20)
async def on_long_text(event: MessageCreated):
await event.message.answer('Слишком длинное сообщение')
@dp.message_created(F.message.body.text.len() > 0)
async def on_non_empty(event: MessageCreated):
await event.message.answer('Вы что-то написали.')
async def main():
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())

View File

@ -6,7 +6,7 @@ from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from example.router_for_example import router
from router import router
logging.basicConfig(level=logging.INFO)
@ -158,4 +158,5 @@ async def main():
# )
asyncio.run(main())
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,6 +1,8 @@
from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING
from maxapi.methods.download_media import DownloadMedia
from .methods.get_upload_url import GetUploadURL
from .methods.get_updates import GetUpdates
from .methods.remove_member_chat import RemoveMemberChat
@ -570,7 +572,7 @@ class Bot(BaseConnection):
"""Получает участников чата.
:param chat_id: ID чата
:param user_ids: Фильтр по ID пользователей
:param user_ids: Список ID участников
:param marker: Маркер для пагинации
:param count: Количество участников
@ -584,6 +586,28 @@ class Bot(BaseConnection):
marker=marker,
count=count,
).request()
async def get_chat_member(
self,
chat_id: int,
user_id: int,
) -> GettedMembersChat:
"""Получает участника чата.
:param chat_id: ID чата
:param user_id: ID участника
:return: Участник
"""
members = await self.get_chat_members(
chat_id=chat_id,
user_ids=[user_id]
)
if members.members:
return members.members[0]
async def add_chat_members(
self,
@ -673,4 +697,28 @@ class Bot(BaseConnection):
return await ChangeInfo(
bot=self,
commands=list(commands)
).request()
async def download_file(
self,
path: str,
url: str,
token: str
):
"""
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
:param path: Путь сохранения медиа
:param url: Ссылка на медиа
:param token: Токен медиа
:return: Числовой статус
"""
return await DownloadMedia(
bot=self,
path=path,
media_url=url,
media_token=token
).request()

View File

@ -1,13 +1,19 @@
import os
from typing import TYPE_CHECKING
import aiofiles
import aiohttp
from pydantic import BaseModel
from ..exceptions.invalid_token import InvalidToken
from ..types.errors import Error
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..enums.upload_type import UploadType
from ..loggers import logger_bot, logger_connection
if TYPE_CHECKING:
@ -65,6 +71,9 @@ class BaseConnection:
)
except aiohttp.ClientConnectorDNSError as e:
return logger_connection.error(f'Ошибка при отправке запроса: {e}')
if r.status == 401:
raise InvalidToken('Неверный токен!')
if not r.ok:
raw = await r.json()
@ -124,4 +133,33 @@ class BaseConnection:
data=form
)
return await response.text()
return await response.text()
async def download_file(
self,
path: str,
url: str,
token: str,
):
"""
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
:param path: Путь сохранения медиа
:param url: Ссылка на медиа
:param token: Токен медиа
:return: Числовой статус
"""
headers = {
'Authorization': f'Bearer {token}'
}
async with aiohttp.ClientSession() as session:
async with session.get(url, headers=headers) as response:
if response.status == 200:
async with aiofiles.open(path, 'wb') as f:
await f.write(await response.read())
return response.status

View File

@ -2,6 +2,7 @@ from typing import Callable, List
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from magic_filter import MagicFilter
from uvicorn import Config, Server
from aiohttp import ClientConnectorError
@ -33,6 +34,8 @@ class Dispatcher:
def __init__(self):
self.event_handlers: List[Handler] = []
self.contexts: List[MemoryContext] = []
self.routers: List[Router] = []
self.filters: List[MagicFilter] = []
self.bot = None
self.on_started_func = None
@ -65,9 +68,24 @@ class Dispatcher:
"""
for router in routers:
for event in router.event_handlers:
self.event_handlers.append(event)
self.routers.append(router)
async def __ready(self, bot: Bot):
self.bot = bot
await self.check_me()
self.routers += [self]
handlers_count = 0
for router in self.routers:
for handler in router.event_handlers:
handlers_count += 1
logger_dp.info(f'{handlers_count} событий на обработку')
if self.on_started_func:
await self.on_started_func()
def __get_memory_context(self, chat_id: int, user_id: int):
"""Возвращает или создает контекст для чата и пользователя.
@ -95,40 +113,51 @@ class Dispatcher:
Args:
event_object: Объект события для обработки
"""
ids = event_object.get_ids()
is_handled = False
for router in self.routers:
if is_handled:
break
if router.filters:
if not filter_attrs(event_object, *router.filters):
continue
for handler in router.event_handlers:
for handler in self.event_handlers:
if not handler.update_type == event_object.update_type:
continue
if handler.filters:
if not filter_attrs(event_object, *handler.filters):
if not handler.update_type == event_object.update_type:
continue
ids = event_object.get_ids()
if handler.filters:
if not filter_attrs(event_object, *handler.filters):
continue
memory_context = self.__get_memory_context(*ids)
if not handler.state == await memory_context.get_state() \
and handler.state:
continue
func_args = handler.func_event.__annotations__.keys()
memory_context = self.__get_memory_context(*ids)
if not handler.state == await memory_context.get_state() \
and handler.state:
continue
func_args = handler.func_event.__annotations__.keys()
kwargs = {'context': memory_context}
kwargs = {'context': memory_context}
for key in kwargs.copy().keys():
if not key in func_args:
del kwargs[key]
for key in kwargs.copy().keys():
if not key in func_args:
del kwargs[key]
if handler.middleware:
await handler.middleware()
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'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
is_handled = True
break
is_handled = True
break
if not is_handled:
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
@ -140,14 +169,7 @@ class Dispatcher:
Args:
bot: Экземпляр бота
"""
self.bot = bot
await self.check_me()
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
if self.on_started_func:
await self.on_started_func()
await self.__ready(bot)
while True:
try:
@ -184,11 +206,7 @@ class Dispatcher:
port: Порт для сервера
"""
self.bot = bot
await self.check_me()
if self.on_started_func:
await self.on_started_func()
await self.__ready(bot)
@app.post('/')
async def _(request: Request):
@ -206,7 +224,6 @@ class Dispatcher:
except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}")
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
config = Config(app=app, host=host, port=port, log_level="critical")
server = Server(config)
@ -231,8 +248,10 @@ class Event:
def __call__(self, *args, **kwargs):
def decorator(func_event: Callable):
if self.update_type == UpdateType.ON_STARTED:
self.router.on_started_func = func_event
else:
self.router.event_handlers.append(
Handler(

View File

@ -0,0 +1,4 @@
class NotAvailableForDownload(BaseException):
...

View File

@ -0,0 +1,4 @@
class InvalidToken(BaseException):
...

View File

@ -6,48 +6,15 @@ from magic_filter.operations.comparator import ComparatorOperation as mf_compara
F = MagicFilter()
def filter_attrs(obj, *magic_args):
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
"""
Применяет один или несколько фильтров MagicFilter к объекту.
:param obj: Любой объект с атрибутами (например, event/message)
:param filters: Один или несколько MagicFilter выражений
:return: True, если все фильтры возвращают True, иначе False
"""
try:
for arg in magic_args:
attr_last = None
method_found = False
operations = arg._operations
if isinstance(operations[-1], mf_call):
operations = operations[:len(operations)-2]
method_found = True
elif isinstance(operations[-1], mf_func):
operations = operations[:len(operations)-1]
method_found = True
elif isinstance(operations[-1], mf_comparator):
operations = operations[:len(operations)-1]
for element in operations:
if attr_last is None:
attr_last = getattr(obj, element.name)
else:
attr_last = getattr(attr_last, element.name)
if attr_last is None:
break
if isinstance(arg._operations[-1], mf_comparator):
return attr_last == arg._operations[-1].right
if not method_found:
return bool(attr_last)
if attr_last is None:
return False
if isinstance(arg._operations[-1], mf_func):
func_operation: mf_func = arg._operations[-1]
return func_operation.resolve(attr_last, attr_last)
else:
method = getattr(attr_last, arg._operations[-2].name)
args = arg._operations[-1].args
return method(*args)
except Exception as e:
...
return all(f.resolve(obj) for f in filters)
except Exception:
return False

View File

@ -2,9 +2,14 @@ from typing import Callable
from magic_filter import F, MagicFilter
from ..filters.middleware import BaseMiddleware
from ..types.command import Command
from ..context.state_machine import State
from ..enums.update import UpdateType
from ..loggers import logger_dp
@ -36,10 +41,11 @@ class Handler:
:param kwargs: Дополнительные параметры (не используются)
"""
self.func_event = func_event
self.update_type = update_type
self.func_event: Callable = func_event
self.update_type: UpdateType = update_type
self.filters = []
self.state = None
self.state: State = None
self.middleware: BaseMiddleware = None
for arg in args:
if isinstance(arg, MagicFilter):
@ -48,6 +54,8 @@ class Handler:
self.state = arg
elif isinstance(arg, Command):
self.filters.insert(0, F.message.body.text.startswith(arg.command))
elif isinstance(arg, BaseMiddleware):
self.middleware = arg
else:
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
f'регистрации функции `{func_event.__name__}`')

View File

@ -0,0 +1,6 @@
from ..types.updates import UpdateUnion
class BaseMiddleware:
def __init__(self):
...

View File

@ -0,0 +1,52 @@
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
if TYPE_CHECKING:
from ..bot import Bot
class DownloadMedia(BaseConnection):
"""
Класс для скачивания медиафайлов.
Args:
bot (Bot): Экземпляр бота для выполнения запроса.
media_url (str): Ссылка на медиа.
media_token (str): Токен медиа.
"""
def __init__(
self,
bot: 'Bot',
path: str,
media_url: str,
media_token: str
):
self.bot = bot
self.path = path
self.media_url = media_url
self.media_token = media_token
async def request(self) -> int:
"""
Выполняет GET-запрос для скачивания медиафайла
Returns:
int: Код операции.
"""
return await super().download_file(
path=self.path,
url=self.media_url,
token=self.media_token
)

View File

@ -60,7 +60,9 @@ class GetMembersChat(BaseConnection):
params = self.bot.params.copy()
if self.user_ids: params['user_ids'] = ','.join(self.user_ids)
if self.user_ids:
self.user_ids = [str(user_id) for user_id in self.user_ids]
params['user_ids'] = ','.join(self.user_ids)
if self.marker: params['marker'] = self.marker
if self.count: params['marker'] = self.count

View File

@ -19,34 +19,77 @@ if TYPE_CHECKING:
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)
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)
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)
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)
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)
event_object.from_user = await bot.get_chat_member(
chat_id=event_object.chat_id,
user_id=event_object.user_id
)
case UpdateType.USER_ADDED:
event_object = UserAdded(**event)
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
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)
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 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)
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

View File

@ -1,14 +1,19 @@
from typing import List, Optional, Union
from pydantic import BaseModel
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
from ...enums.attachment import AttachmentType
if TYPE_CHECKING:
from ...bot import Bot
class StickerAttachmentPayload(BaseModel):
"""
@ -98,6 +103,36 @@ class Attachment(BaseModel):
ButtonsPayload,
StickerAttachmentPayload
]] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
class Config:
use_enum_values = True
use_enum_values = True
async def download(
self,
path: str
):
"""
Скачивает медиа, сохраняя по определенному пути
:param path: Путь сохранения медиа
:return: Числовой статус
"""
if not hasattr(self.payload, 'token') or \
not hasattr(self.payload, 'url'):
raise NotAvailableForDownload()
elif not self.payload.token or not self.payload.url:
raise NotAvailableForDownload(f'Медиа типа `{self.type}` недоступно для скачивания')
return await self.bot.download_file(
path=path,
url=self.payload.url,
token=self.payload.token,
)

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -18,12 +16,10 @@ class BotAdded(Update):
Attributes:
chat_id (Optional[int]): Идентификатор чата, куда добавлен бот.
user (User): Объект пользователя-бота.
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
"""
chat_id: Optional[int] = None
user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -18,12 +16,10 @@ class BotRemoved(Update):
Attributes:
chat_id (Optional[int]): Идентификатор чата, из которого удалён бот.
user (User): Объект пользователя-бота.
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
"""
chat_id: Optional[int] = None
user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]

View File

@ -1,8 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
@ -19,14 +18,12 @@ class BotStarted(Update):
user (User): Пользователь (бот).
user_locale (Optional[str]): Локаль пользователя.
payload (Optional[str]): Дополнительные данные.
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
"""
chat_id: Optional[int] = None
user: User
user_locale: Optional[str] = None
payload: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -19,13 +17,11 @@ class ChatTitleChanged(Update):
chat_id (Optional[int]): Идентификатор чата.
user (User): Пользователь, совершивший изменение.
title (Optional[str]): Новое название чата.
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
"""
chat_id: Optional[int] = None
user: User
title: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]

View File

@ -1,6 +1,6 @@
from typing import Any, List, Optional, TYPE_CHECKING, Union
from typing import List, Optional, TYPE_CHECKING, Union
from pydantic import BaseModel, Field
from pydantic import BaseModel
from .update import Update
@ -21,6 +21,8 @@ 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):
@ -65,16 +67,11 @@ class MessageCallback(Update):
message (Message): Сообщение, на которое пришёл callback.
user_locale (Optional[str]): Локаль пользователя.
callback (Callback): Объект callback.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
"""
message: Message
user_locale: Optional[str] = None
callback: Callback
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
@ -89,7 +86,7 @@ class MessageCallback(Update):
async def answer(
self,
notification: str,
notification: str = None,
new_text: str = None,
link: NewMessageLink = None,
notify: bool = True,

View File

@ -1,24 +1,15 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from typing import Optional
from ...types.chats import Chat
from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageChatCreated(Update):
chat: Chat
title: Optional[str] = None
message_id: Optional[str] = None
start_payload: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, 0)
return (self.chat.chat_id, self.chat.owner_id)

View File

@ -1,15 +1,10 @@
from __future__ import annotations
from typing import Any, Optional, TYPE_CHECKING
from pydantic import Field
from typing import Optional, TYPE_CHECKING
from .update import Update
from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageCreated(Update):
@ -19,16 +14,11 @@ class MessageCreated(Update):
Attributes:
message (Message): Объект сообщения.
user_locale (Optional[str]): Локаль пользователя.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
"""
message: Message
user_locale: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
"""

View File

@ -1,14 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageEdited(Update):
@ -17,15 +10,10 @@ class MessageEdited(Update):
Attributes:
message (Message): Объект измененного сообщения.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
"""
message: Message
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
"""

View File

@ -1,12 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from typing import Optional
from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageRemoved(Update):
@ -17,16 +12,11 @@ class MessageRemoved(Update):
message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None.
user_id (Optional[int]): Идентификатор пользователя. Может быть None.
bot (Optional[Bot]): Объект бота, исключается из сериализации.
"""
message_id: Optional[str] = None
chat_id: Optional[int] = None
user_id: Optional[int] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):

View File

@ -1,7 +1,14 @@
from pydantic import BaseModel
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional
from pydantic import BaseModel, Field
from ...enums.update import UpdateType
if TYPE_CHECKING:
from ...bot import Bot
from ...types.chats import Chat
from ...types.users import User
class Update(BaseModel):
@ -15,6 +22,15 @@ class Update(BaseModel):
update_type: UpdateType
timestamp: int
bot: Optional[Any] = Field(default=None, exclude=True)
from_user: Optional[Any] = Field(default=None, exclude=True)
chat: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
from_user: Optional[User]
chat: Optional[Chat]
class Config:
arbitrary_types_allowed=True

View File

@ -1,16 +1,10 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from typing import Optional
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserAdded(Update):
"""
@ -20,17 +14,12 @@ class UserAdded(Update):
inviter_id (Optional[int]): Идентификатор пользователя, добавившего нового участника. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None.
user (User): Объект пользователя, добавленного в чат.
bot (Optional[Bot]): Объект бота, исключается из сериализации.
"""
inviter_id: Optional[int] = None
chat_id: Optional[int] = None
user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
"""

View File

@ -1,14 +1,9 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from typing import Optional
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserRemoved(Update):
@ -19,17 +14,12 @@ class UserRemoved(Update):
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None.
user (User): Объект пользователя, удаленного из чата.
bot (Optional[Bot]): Объект бота, исключается из сериализации.
"""
admin_id: Optional[int] = None
chat_id: Optional[int] = None
user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
"""