Compare commits

..

No commits in common. "bca4c3fd6cc4bffa162673b75906bdc199f8e21d" and "ab52abc474d1b55fdad12a7ce4c42f3d25ba4862" have entirely different histories.

33 changed files with 223 additions and 561 deletions

View File

@ -16,8 +16,6 @@ pip install maxapi
## 🚀 Быстрый старт
Если вы тестируете бота в чате - не забудьте дать ему права администратора!
```python
import asyncio
import logging
@ -60,16 +58,6 @@ if __name__ == '__main__':
---
## ⭐️ Примеры
- [Эхо бот](https://github.com/love-apples/maxapi/blob/main/examples/echo/main.py)
- [Обработчик доступных событий](https://github.com/love-apples/maxapi/blob/main/examples/events/main.py)
- [Обработчики с MagicFilter](https://github.com/love-apples/maxapi/blob/main/examples/magic_filters/main.py)
- [Демонстрация роутинга, InputMedia и механика контекста](https://github.com/love-apples/maxapi/tree/main/examples/router_with_input_media) (audio.mp3 для команды /media)
---
## 🧩 Возможности
- ✅ Роутеры
@ -93,4 +81,4 @@ if __name__ == '__main__':
## 📄 Лицензия
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](https://github.com/love-apples/maxapi/blob/main/LICENSE) для подробностей.
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для подробностей.

31
example/echo_example.py Normal file
View File

@ -0,0 +1,31 @@
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())

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 router import router
from example.router_for_example import router
logging.basicConfig(level=logging.INFO)
@ -158,5 +158,4 @@ async def main():
# )
if __name__ == '__main__':
asyncio.run(main())

View File

@ -1,24 +0,0 @@
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())

View File

@ -1,122 +0,0 @@
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

@ -1,48 +0,0 @@
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

@ -1,8 +1,6 @@
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
@ -572,7 +570,7 @@ class Bot(BaseConnection):
"""Получает участников чата.
:param chat_id: ID чата
:param user_ids: Список ID участников
:param user_ids: Фильтр по ID пользователей
:param marker: Маркер для пагинации
:param count: Количество участников
@ -587,28 +585,6 @@ class Bot(BaseConnection):
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,
chat_id: int,
@ -698,27 +674,3 @@ class Bot(BaseConnection):
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,19 +1,13 @@
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:
@ -72,9 +66,6 @@ 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()
error = Error(code=r.status, raw=raw)
@ -134,32 +125,3 @@ class BaseConnection:
)
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,7 +2,6 @@ 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
@ -34,8 +33,6 @@ 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
@ -68,23 +65,8 @@ class Dispatcher:
"""
for router in routers:
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()
for event in router.event_handlers:
self.event_handlers.append(event)
def __get_memory_context(self, chat_id: int, user_id: int):
@ -113,20 +95,10 @@ 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
@ -135,6 +107,8 @@ class Dispatcher:
if not filter_attrs(event_object, *handler.filters):
continue
ids = event_object.get_ids()
memory_context = self.__get_memory_context(*ids)
if not handler.state == await memory_context.get_state() \
@ -149,9 +123,6 @@ class Dispatcher:
if not key in func_args:
del kwargs[key]
if handler.middleware:
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]}')
@ -169,7 +140,14 @@ class Dispatcher:
Args:
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:
try:
@ -206,7 +184,11 @@ class Dispatcher:
port: Порт для сервера
"""
await self.__ready(bot)
self.bot = bot
await self.check_me()
if self.on_started_func:
await self.on_started_func()
@app.post('/')
async def _(request: Request):
@ -224,6 +206,7 @@ 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)
@ -248,10 +231,8 @@ 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

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

View File

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

View File

@ -6,15 +6,48 @@ from magic_filter.operations.comparator import ComparatorOperation as mf_compara
F = MagicFilter()
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
"""
Применяет один или несколько фильтров MagicFilter к объекту.
:param obj: Любой объект с атрибутами (например, event/message)
:param filters: Один или несколько MagicFilter выражений
:return: True, если все фильтры возвращают True, иначе False
"""
def filter_attrs(obj, *magic_args):
try:
return all(f.resolve(obj) for f in filters)
except Exception:
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:
...

View File

@ -2,14 +2,9 @@ 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
@ -41,11 +36,10 @@ class Handler:
:param kwargs: Дополнительные параметры (не используются)
"""
self.func_event: Callable = func_event
self.update_type: UpdateType = update_type
self.func_event = func_event
self.update_type = update_type
self.filters = []
self.state: State = None
self.middleware: BaseMiddleware = None
self.state = None
for arg in args:
if isinstance(arg, MagicFilter):
@ -54,8 +48,6 @@ 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

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

View File

@ -1,52 +0,0 @@
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,9 +60,7 @@ class GetMembersChat(BaseConnection):
params = self.bot.params.copy()
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.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,78 +19,35 @@ 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,19 +1,14 @@
from typing import TYPE_CHECKING, Any, List, Optional, Union
from pydantic import BaseModel, Field
from ...exceptions.download_file import NotAvailableForDownload
from typing import List, Optional, Union
from pydantic import BaseModel
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):
"""
@ -103,36 +98,6 @@ 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
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,5 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -16,10 +18,12 @@ 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,5 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -16,10 +18,12 @@ 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,7 +1,8 @@
from typing import TYPE_CHECKING, Any, Optional
from .update import Update
from pydantic import Field
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
@ -18,12 +19,14 @@ 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,5 +1,7 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
@ -17,11 +19,13 @@ 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 List, Optional, TYPE_CHECKING, Union
from typing import Any, List, Optional, TYPE_CHECKING, Union
from pydantic import BaseModel
from pydantic import BaseModel, Field
from .update import Update
@ -21,8 +21,6 @@ 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):
@ -67,11 +65,16 @@ 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):
@ -86,7 +89,7 @@ class MessageCallback(Update):
async def answer(
self,
notification: str = None,
notification: str,
new_text: str = None,
link: NewMessageLink = None,
notify: bool = True,

View File

@ -1,15 +1,24 @@
from typing import Optional
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
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.chat_id, self.chat.owner_id)
return (self.chat_id, 0)

View File

@ -1,10 +1,15 @@
from __future__ import annotations
from typing import Optional, TYPE_CHECKING
from typing import Any, Optional, TYPE_CHECKING
from pydantic import Field
from .update import Update
from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageCreated(Update):
@ -14,10 +19,15 @@ 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,7 +1,14 @@
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):
@ -10,9 +17,14 @@ 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,7 +1,12 @@
from typing import Optional
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageRemoved(Update):
@ -12,11 +17,16 @@ 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,14 +1,7 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Optional
from pydantic import BaseModel, Field
from pydantic import BaseModel
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):
@ -23,14 +16,5 @@ 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,10 +1,16 @@
from typing import Optional
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserAdded(Update):
"""
@ -14,11 +20,16 @@ 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,9 +1,14 @@
from typing import Optional
from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserRemoved(Update):
@ -14,11 +19,16 @@ 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):

View File

@ -1,11 +1,11 @@
[project]
name = "maxapi"
version = "0.8.2"
version = "0.7"
description = "Библиотека для разработки чат-ботов с помощью API мессенджера MAX"
readme = "README.md"
requires-python = ">=3.10"
authors = [
{name = "Denis", email = "bestloveapples@gmail.com"},
{name = "Denis", email = "your-email@example.com"},
]
keywords = ["max", "api", "bot"]
classifiers = [