Добавлены примеры и обновлена модель 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.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from example.router_for_example import router from router import router
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -158,4 +158,5 @@ async def main():
# ) # )
asyncio.run(main()) if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,6 +1,8 @@
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING 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_upload_url import GetUploadURL
from .methods.get_updates import GetUpdates from .methods.get_updates import GetUpdates
from .methods.remove_member_chat import RemoveMemberChat from .methods.remove_member_chat import RemoveMemberChat
@ -570,7 +572,7 @@ class Bot(BaseConnection):
"""Получает участников чата. """Получает участников чата.
:param chat_id: ID чата :param chat_id: ID чата
:param user_ids: Фильтр по ID пользователей :param user_ids: Список ID участников
:param marker: Маркер для пагинации :param marker: Маркер для пагинации
:param count: Количество участников :param count: Количество участников
@ -585,6 +587,28 @@ class Bot(BaseConnection):
count=count, count=count,
).request() ).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( async def add_chat_members(
self, self,
chat_id: int, chat_id: int,
@ -674,3 +698,27 @@ class Bot(BaseConnection):
bot=self, bot=self,
commands=list(commands) commands=list(commands)
).request() ).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 import os
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import aiofiles
import aiohttp import aiohttp
from pydantic import BaseModel from pydantic import BaseModel
from ..exceptions.invalid_token import InvalidToken
from ..types.errors import Error from ..types.errors import Error
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from ..enums.upload_type import UploadType from ..enums.upload_type import UploadType
from ..loggers import logger_bot, logger_connection from ..loggers import logger_bot, logger_connection
if TYPE_CHECKING: if TYPE_CHECKING:
@ -66,6 +72,9 @@ class BaseConnection:
except aiohttp.ClientConnectorDNSError as e: except aiohttp.ClientConnectorDNSError as e:
return logger_connection.error(f'Ошибка при отправке запроса: {e}') return logger_connection.error(f'Ошибка при отправке запроса: {e}')
if r.status == 401:
raise InvalidToken('Неверный токен!')
if not r.ok: if not r.ok:
raw = await r.json() raw = await r.json()
error = Error(code=r.status, raw=raw) error = Error(code=r.status, raw=raw)
@ -125,3 +134,32 @@ class BaseConnection:
) )
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 import FastAPI, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from magic_filter import MagicFilter
from uvicorn import Config, Server from uvicorn import Config, Server
from aiohttp import ClientConnectorError from aiohttp import ClientConnectorError
@ -33,6 +34,8 @@ class Dispatcher:
def __init__(self): def __init__(self):
self.event_handlers: List[Handler] = [] self.event_handlers: List[Handler] = []
self.contexts: List[MemoryContext] = [] self.contexts: List[MemoryContext] = []
self.routers: List[Router] = []
self.filters: List[MagicFilter] = []
self.bot = None self.bot = None
self.on_started_func = None self.on_started_func = None
@ -65,8 +68,23 @@ class Dispatcher:
""" """
for router in routers: for router in routers:
for event in router.event_handlers: self.routers.append(router)
self.event_handlers.append(event)
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): def __get_memory_context(self, chat_id: int, user_id: int):
@ -95,40 +113,51 @@ class Dispatcher:
Args: Args:
event_object: Объект события для обработки event_object: Объект события для обработки
""" """
ids = event_object.get_ids()
is_handled = False is_handled = False
for handler in self.event_handlers: for router in self.routers:
if not handler.update_type == event_object.update_type: if is_handled:
continue break
if handler.filters: if router.filters:
if not filter_attrs(event_object, *handler.filters): if not filter_attrs(event_object, *router.filters):
continue continue
ids = event_object.get_ids() for handler in router.event_handlers:
memory_context = self.__get_memory_context(*ids) if not handler.update_type == event_object.update_type:
continue
if not handler.state == await memory_context.get_state() \ if handler.filters:
and handler.state: if not filter_attrs(event_object, *handler.filters):
continue continue
func_args = handler.func_event.__annotations__.keys() memory_context = self.__get_memory_context(*ids)
kwargs = {'context': memory_context} if not handler.state == await memory_context.get_state() \
and handler.state:
continue
for key in kwargs.copy().keys(): func_args = handler.func_event.__annotations__.keys()
if not key in func_args:
del kwargs[key]
await handler.func_event(event_object, **kwargs) kwargs = {'context': memory_context}
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}') for key in kwargs.copy().keys():
if not key in func_args:
del kwargs[key]
is_handled = True if handler.middleware:
break await handler.middleware()
await handler.func_event(event_object, **kwargs)
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
is_handled = True
break
if not is_handled: if not is_handled:
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}') logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
@ -140,14 +169,7 @@ class Dispatcher:
Args: Args:
bot: Экземпляр бота bot: Экземпляр бота
""" """
await self.__ready(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()
while True: while True:
try: try:
@ -184,11 +206,7 @@ class Dispatcher:
port: Порт для сервера port: Порт для сервера
""" """
self.bot = bot await self.__ready(bot)
await self.check_me()
if self.on_started_func:
await self.on_started_func()
@app.post('/') @app.post('/')
async def _(request: Request): async def _(request: Request):
@ -206,7 +224,6 @@ class Dispatcher:
except Exception as e: except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {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") config = Config(app=app, host=host, port=port, log_level="critical")
server = Server(config) server = Server(config)
@ -231,8 +248,10 @@ class Event:
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
def decorator(func_event: Callable): def decorator(func_event: Callable):
if self.update_type == UpdateType.ON_STARTED: if self.update_type == UpdateType.ON_STARTED:
self.router.on_started_func = func_event self.router.on_started_func = func_event
else: else:
self.router.event_handlers.append( self.router.event_handlers.append(
Handler( Handler(

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() 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: try:
for arg in magic_args: return all(f.resolve(obj) for f in filters)
except Exception:
attr_last = None return False
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:
...

View File

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

View File

@ -19,35 +19,78 @@ if TYPE_CHECKING:
async def get_update_model(event: dict, bot: 'Bot'): async def get_update_model(event: dict, bot: 'Bot'):
event_object = None event_object = None
match event['update_type']: match event['update_type']:
case UpdateType.BOT_ADDED: case UpdateType.BOT_ADDED:
event_object = BotAdded(**event) event_object = BotAdded(**event)
case UpdateType.BOT_REMOVED: case UpdateType.BOT_REMOVED:
event_object = BotRemoved(**event) event_object = BotRemoved(**event)
case UpdateType.BOT_STARTED: case UpdateType.BOT_STARTED:
event_object = BotStarted(**event) event_object = BotStarted(**event)
case UpdateType.CHAT_TITLE_CHANGED: case UpdateType.CHAT_TITLE_CHANGED:
event_object = ChatTitleChanged(**event) event_object = ChatTitleChanged(**event)
case UpdateType.MESSAGE_CALLBACK: case UpdateType.MESSAGE_CALLBACK:
event_object = MessageCallback(**event) 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: case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event) event_object = MessageChatCreated(**event)
event_object.chat = event_object.chat
case UpdateType.MESSAGE_CREATED: case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event) 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: case UpdateType.MESSAGE_EDITED:
event_object = MessageEdited(**event) 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: case UpdateType.MESSAGE_REMOVED:
event_object = MessageRemoved(**event) 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: case UpdateType.USER_ADDED:
event_object = UserAdded(**event) 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: case UpdateType.USER_REMOVED:
event_object = UserRemoved(**event) 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'): if hasattr(event_object, 'bot'):
event_object.bot = bot event_object.bot = bot
if hasattr(event_object, 'message'): if hasattr(event_object, 'message'):
event_object.message.bot = bot event_object.message.bot = bot
for attachment in event_object.message.body.attachments:
if hasattr(attachment, 'bot'):
attachment.bot = bot
return event_object return event_object

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,5 @@
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update from .update import Update
from ...types.users import User from ...types.users import User
@ -19,13 +17,11 @@ class ChatTitleChanged(Update):
chat_id (Optional[int]): Идентификатор чата. chat_id (Optional[int]): Идентификатор чата.
user (User): Пользователь, совершивший изменение. user (User): Пользователь, совершивший изменение.
title (Optional[str]): Новое название чата. title (Optional[str]): Новое название чата.
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
""" """
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
title: Optional[str] = None title: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING: if TYPE_CHECKING:
bot: Optional[Bot] 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 from .update import Update
@ -21,6 +21,8 @@ from ..attachments.audio import Audio
if TYPE_CHECKING: if TYPE_CHECKING:
from ...bot import Bot from ...bot import Bot
from ...types.chats import Chat
from ...types.users import User
class MessageForCallback(BaseModel): class MessageForCallback(BaseModel):
@ -65,16 +67,11 @@ class MessageCallback(Update):
message (Message): Сообщение, на которое пришёл callback. message (Message): Сообщение, на которое пришёл callback.
user_locale (Optional[str]): Локаль пользователя. user_locale (Optional[str]): Локаль пользователя.
callback (Callback): Объект callback. callback (Callback): Объект callback.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
""" """
message: Message message: Message
user_locale: Optional[str] = None user_locale: Optional[str] = None
callback: Callback callback: Callback
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self): def get_ids(self):
@ -89,7 +86,7 @@ class MessageCallback(Update):
async def answer( async def answer(
self, self,
notification: str, notification: str = None,
new_text: str = None, new_text: str = None,
link: NewMessageLink = None, link: NewMessageLink = None,
notify: bool = True, notify: bool = True,

View File

@ -1,24 +1,15 @@
from typing import TYPE_CHECKING, Any, Optional from typing import Optional
from pydantic import Field
from ...types.chats import Chat from ...types.chats import Chat
from .update import Update from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageChatCreated(Update): class MessageChatCreated(Update):
chat: Chat chat: Chat
title: Optional[str] = None title: Optional[str] = None
message_id: Optional[str] = None message_id: Optional[str] = None
start_payload: 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): 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 __future__ import annotations
from typing import Any, Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from pydantic import Field
from .update import Update from .update import Update
from ...types.message import Message from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageCreated(Update): class MessageCreated(Update):
@ -19,15 +14,10 @@ class MessageCreated(Update):
Attributes: Attributes:
message (Message): Объект сообщения. message (Message): Объект сообщения.
user_locale (Optional[str]): Локаль пользователя. user_locale (Optional[str]): Локаль пользователя.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
""" """
message: Message message: Message
user_locale: Optional[str] = None user_locale: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self): 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 .update import Update
from ...types.message import Message from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageEdited(Update): class MessageEdited(Update):
@ -17,14 +10,9 @@ class MessageEdited(Update):
Attributes: Attributes:
message (Message): Объект измененного сообщения. message (Message): Объект измененного сообщения.
bot (Optional[Any]): Экземпляр бота, не сериализуется.
""" """
message: Message message: Message
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self): def get_ids(self):

View File

@ -1,12 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional from typing import Optional
from pydantic import Field
from .update import Update from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageRemoved(Update): class MessageRemoved(Update):
@ -17,16 +12,11 @@ class MessageRemoved(Update):
message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None. message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None.
chat_id (Optional[int]): Идентификатор чата. Может быть None. chat_id (Optional[int]): Идентификатор чата. Может быть None.
user_id (Optional[int]): Идентификатор пользователя. Может быть None. user_id (Optional[int]): Идентификатор пользователя. Может быть None.
bot (Optional[Bot]): Объект бота, исключается из сериализации.
""" """
message_id: Optional[str] = None message_id: Optional[str] = None
chat_id: Optional[int] = None chat_id: Optional[int] = None
user_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): 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 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): class Update(BaseModel):
@ -16,5 +23,14 @@ class Update(BaseModel):
update_type: UpdateType update_type: UpdateType
timestamp: int 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: class Config:
arbitrary_types_allowed=True arbitrary_types_allowed=True

View File

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

View File

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