From a8f60bd767a7a688c69816622105ff97fc686437 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 15 Jun 2025 21:10:31 +0300 Subject: [PATCH] added --- .gitignore | 160 ++++++++++++++++++ example.py | 50 ++++++ maxapi/bot.py | 89 ++++++++++ maxapi/connection/base.py | 36 ++++ maxapi/dispatcher.py | 151 +++++++++++++++++ maxapi/enums/api_path.py | 7 + maxapi/enums/attachment.py | 11 ++ maxapi/enums/button_type.py | 9 + maxapi/enums/chat_type.py | 6 + maxapi/enums/http_method.py | 8 + maxapi/enums/intent.py | 6 + maxapi/enums/message_link_type.py | 6 + maxapi/enums/parse_mode.py | 5 + maxapi/enums/text_style.py | 13 ++ maxapi/enums/update.py | 14 ++ maxapi/filters/__init__.py | 53 ++++++ maxapi/loggers.py | 4 + maxapi/methods/change_info.py | 46 +++++ maxapi/methods/edit_message.py | 59 +++++++ maxapi/methods/get_chats.py | 29 ++++ maxapi/methods/get_me.py | 29 ++++ maxapi/methods/get_messages.py | 30 ++++ maxapi/methods/send_message.py | 68 ++++++++ maxapi/methods/types/edited_message.py | 7 + maxapi/methods/types/sended_message.py | 7 + maxapi/types/__init__.py | 0 maxapi/types/attachments/attachment.py | 59 +++++++ maxapi/types/attachments/audio.py | 8 + maxapi/types/attachments/buttons/__init__.py | 12 ++ .../attachments/buttons/attachment_button.py | 9 + .../attachments/buttons/callback_button.py | 9 + .../types/attachments/buttons/chat_button.py | 11 ++ .../types/attachments/buttons/link_button.py | 7 + .../attachments/buttons/request_contact.py | 5 + .../buttons/request_geo_location_button.py | 5 + maxapi/types/attachments/contact.py | 7 + maxapi/types/attachments/file.py | 9 + maxapi/types/attachments/image.py | 6 + maxapi/types/attachments/location.py | 9 + maxapi/types/attachments/share.py | 9 + maxapi/types/attachments/sticker.py | 9 + maxapi/types/attachments/video.py | 16 ++ maxapi/types/callback.py | 11 ++ maxapi/types/chats.py | 46 +++++ maxapi/types/errors.py | 6 + maxapi/types/message.py | 119 +++++++++++++ maxapi/types/updates/__init__.py | 11 ++ maxapi/types/updates/bot_added.py | 8 + maxapi/types/updates/bot_removed.py | 8 + maxapi/types/updates/bot_started.py | 10 ++ maxapi/types/updates/chat_title_changed.py | 9 + maxapi/types/updates/message_callback.py | 11 ++ maxapi/types/updates/message_chat_created.py | 11 ++ maxapi/types/updates/message_created.py | 9 + maxapi/types/updates/message_edited.py | 6 + maxapi/types/updates/message_removed.py | 9 + maxapi/types/updates/user_added.py | 10 ++ maxapi/types/updates/user_removed.py | 10 ++ maxapi/types/users.py | 26 +++ 59 files changed, 1413 insertions(+) create mode 100644 .gitignore create mode 100644 example.py create mode 100644 maxapi/bot.py create mode 100644 maxapi/connection/base.py create mode 100644 maxapi/dispatcher.py create mode 100644 maxapi/enums/api_path.py create mode 100644 maxapi/enums/attachment.py create mode 100644 maxapi/enums/button_type.py create mode 100644 maxapi/enums/chat_type.py create mode 100644 maxapi/enums/http_method.py create mode 100644 maxapi/enums/intent.py create mode 100644 maxapi/enums/message_link_type.py create mode 100644 maxapi/enums/parse_mode.py create mode 100644 maxapi/enums/text_style.py create mode 100644 maxapi/enums/update.py create mode 100644 maxapi/filters/__init__.py create mode 100644 maxapi/loggers.py create mode 100644 maxapi/methods/change_info.py create mode 100644 maxapi/methods/edit_message.py create mode 100644 maxapi/methods/get_chats.py create mode 100644 maxapi/methods/get_me.py create mode 100644 maxapi/methods/get_messages.py create mode 100644 maxapi/methods/send_message.py create mode 100644 maxapi/methods/types/edited_message.py create mode 100644 maxapi/methods/types/sended_message.py create mode 100644 maxapi/types/__init__.py create mode 100644 maxapi/types/attachments/attachment.py create mode 100644 maxapi/types/attachments/audio.py create mode 100644 maxapi/types/attachments/buttons/__init__.py create mode 100644 maxapi/types/attachments/buttons/attachment_button.py create mode 100644 maxapi/types/attachments/buttons/callback_button.py create mode 100644 maxapi/types/attachments/buttons/chat_button.py create mode 100644 maxapi/types/attachments/buttons/link_button.py create mode 100644 maxapi/types/attachments/buttons/request_contact.py create mode 100644 maxapi/types/attachments/buttons/request_geo_location_button.py create mode 100644 maxapi/types/attachments/contact.py create mode 100644 maxapi/types/attachments/file.py create mode 100644 maxapi/types/attachments/image.py create mode 100644 maxapi/types/attachments/location.py create mode 100644 maxapi/types/attachments/share.py create mode 100644 maxapi/types/attachments/sticker.py create mode 100644 maxapi/types/attachments/video.py create mode 100644 maxapi/types/callback.py create mode 100644 maxapi/types/chats.py create mode 100644 maxapi/types/errors.py create mode 100644 maxapi/types/message.py create mode 100644 maxapi/types/updates/__init__.py create mode 100644 maxapi/types/updates/bot_added.py create mode 100644 maxapi/types/updates/bot_removed.py create mode 100644 maxapi/types/updates/bot_started.py create mode 100644 maxapi/types/updates/chat_title_changed.py create mode 100644 maxapi/types/updates/message_callback.py create mode 100644 maxapi/types/updates/message_chat_created.py create mode 100644 maxapi/types/updates/message_created.py create mode 100644 maxapi/types/updates/message_edited.py create mode 100644 maxapi/types/updates/message_removed.py create mode 100644 maxapi/types/updates/user_added.py create mode 100644 maxapi/types/updates/user_removed.py create mode 100644 maxapi/types/users.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/example.py b/example.py new file mode 100644 index 0000000..b34f575 --- /dev/null +++ b/example.py @@ -0,0 +1,50 @@ +from maxapi.bot import Bot +from maxapi.dispatcher import Dispatcher +from maxapi.types.updates.message_created import MessageCreated +from maxapi.types.updates.message_callback import MessageCallback +from maxapi.types.attachments.attachment import ButtonsPayload +from maxapi.types.attachments.buttons.callback_button import CallbackButton +from maxapi.types.attachments.attachment import Attachment +from maxapi.enums.attachment import AttachmentType +from maxapi.enums.button_type import ButtonType +from maxapi.enums.intent import Intent +from maxapi.filters import F + + +bot = Bot('токен') +dp = Dispatcher() + +# Отвечает только на текст "Привет" +@dp.message_created(F.message.body.text == 'Привет') +async def hello(obj: MessageCreated): + await obj.message.answer('Привет 👋') + +# Отвечает только на текст "Клавиатура" +@dp.message_created(F.message.body.text == 'Клавиатура') +async def hello(obj: MessageCreated): + button_1 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 1', payload='1', intent=Intent.DEFAULT) + button_2 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 2', payload='2', intent=Intent.DEFAULT) + + keyboard = ButtonsPayload(buttons=[[button_1], [button_2]]) + + attachments = [Attachment(type=AttachmentType.INLINE_KEYBOARD, payload=keyboard)] + + await obj.message.answer('Привет 👋', attachments=attachments) + +# Ответчает на коллбек с начинкой "1" +@dp.message_callback(F.callback.payload == '1') +async def _(obj: MessageCallback): + await obj.message.answer('Вы нажали на кнопку 1 🤩') + +# Ответчает на коллбек с начинкой "2" +@dp.message_callback(F.callback.payload == '2') +async def _(obj: MessageCallback): + await obj.message.answer('Вы нажали на кнопку 2 🥳') + +# Отвечает на любое текстовое сообщение +@dp.message_created(F.message.body.text) +async def hello(obj: MessageCreated): + await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}') + + +dp.handle_webhook(bot) \ No newline at end of file diff --git a/maxapi/bot.py b/maxapi/bot.py new file mode 100644 index 0000000..b44c409 --- /dev/null +++ b/maxapi/bot.py @@ -0,0 +1,89 @@ +from typing import Any, Dict, List + +from .methods.edit_message import EditMessage +from .enums.parse_mode import ParseMode +from .types.attachments.attachment import Attachment +from .types.message import NewMessageLink +from .types.users import BotCommand +from .methods.change_info import ChangeInfo +from .methods.get_me import GetMe +from .methods.get_messages import GetMessages +from .methods.get_chats import GetChats +from .methods.send_message import SendMessage +from .connection.base import BaseConnection + + +class Bot(BaseConnection): + + def __init__(self, token: str): + self.__token = token + self.params = { + 'access_token': self.__token + } + + async def send_message( + self, + chat_id: int = None, + user_id: int = None, + disable_link_preview: bool = False, + text: str = None, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + return await SendMessage( + bot=self, + chat_id=chat_id, + user_id=user_id, + disable_link_preview=disable_link_preview, + text=text, + attachments=attachments, + link=link, + notify=notify, + parse_mode=parse_mode + ).request() + + async def edit_message( + self, + message_id: str, + text: str = None, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + return await EditMessage( + bot=self, + message_id=message_id, + text=text, + attachments=attachments, + link=link, + notify=notify, + parse_mode=parse_mode + ).request() + + async def get_messages(self, chat_id: int = None): + return await GetMessages(self, chat_id).request() + + async def get_me(self): + return await GetMe(self).request() + + async def change_info( + self, + name: str = None, + description: str = None, + commands: List[BotCommand] = None, + photo: Dict[str, Any] = None + ): + + return await ChangeInfo( + bot=self, + name=name, + description=description, + commands=commands, + photo=photo + ).request() + + async def get_chats(self): + return await GetChats(self).request() \ No newline at end of file diff --git a/maxapi/connection/base.py b/maxapi/connection/base.py new file mode 100644 index 0000000..e9091d9 --- /dev/null +++ b/maxapi/connection/base.py @@ -0,0 +1,36 @@ +import aiohttp +from pydantic import BaseModel + +from ..types.errors import Error +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath + + +class BaseConnection: + + API_URL = 'https://botapi.max.ru' + + async def request( + self, + method: HTTPMethod, + path: ApiPath, + model: BaseModel, + is_return_raw: bool = False, + **kwargs + ): + async with aiohttp.ClientSession(self.API_URL) as s: + r = await s.request( + method=method.value, + url=path.value, + **kwargs + ) + + if not r.ok: + raw = await r.text() + return Error(code=r.status, text=raw) + + raw = await r.json() + + if is_return_raw: return raw + + return model(**raw) \ No newline at end of file diff --git a/maxapi/dispatcher.py b/maxapi/dispatcher.py new file mode 100644 index 0000000..c2a0abe --- /dev/null +++ b/maxapi/dispatcher.py @@ -0,0 +1,151 @@ +from typing import Callable, List + +import uvicorn + +from fastapi import FastAPI, Request +from magic_filter import MagicFilter + +from .filters import filter_m +from .types.updates import Update + +from .bot import Bot +from .enums.update import UpdateType +from .types.updates.bot_added import BotAdded +from .types.updates.bot_removed import BotRemoved +from .types.updates.bot_started import BotStarted +from .types.updates.chat_title_changed import ChatTitleChanged +from .types.updates.message_callback import MessageCallback +from .types.updates.message_chat_created import MessageChatCreated +from .types.updates.message_created import MessageCreated +from .types.updates.message_edited import MessageEdited +from .types.updates.message_removed import MessageRemoved +from .types.updates.user_added import UserAdded +from .types.updates.user_removed import UserRemoved +from .loggers import logger + + +class Handler: + + def __init__( + self, + *args, + func_event: Callable, + update_type: UpdateType, + **kwargs + ): + + self.func_event = func_event + self.update_type = update_type + self.filters = [] + + for arg in args: + if isinstance(arg, MagicFilter): + arg: MagicFilter = arg + + self.filters.append(arg) + + +class Dispatcher: + def __init__(self): + self.event_handlers = [] + self.bot = None + + self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self) + self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self) + self.bot_removed = Event(update_type=UpdateType.BOT_REMOVED, router=self) + self.bot_started = Event(update_type=UpdateType.BOT_STARTED, router=self) + self.chat_title_changed = Event(update_type=UpdateType.CHAT_TITLE_CHANGED, router=self) + self.message_callback = Event(update_type=UpdateType.MESSAGE_CALLBACK, router=self) + self.message_chat_created = Event(update_type=UpdateType.MESSAGE_CHAT_CREATED, router=self) + self.message_edited = Event(update_type=UpdateType.MESSAGE_EDITED, router=self) + self.message_removed = Event(update_type=UpdateType.MESSAGE_REMOVED, router=self) + self.user_added = Event(update_type=UpdateType.USER_ADDED, router=self) + self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self) + + def include_routers(self, *routers: 'Router'): + for router in routers: + for event in router.event_handlers: + self.event_handlers.append(event) + + def handle_webhook(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080): + self.bot = bot + + app = FastAPI() + + @app.post("/") + async def _(request: Request): + try: + event_json = await request.json() + event = Update(**event_json) + + event_object = None + match event.update_type: + case UpdateType.BOT_ADDED: + event_object = BotAdded(**event_json) + case UpdateType.BOT_REMOVED: + event_object = BotRemoved(**event_json) + case UpdateType.BOT_STARTED: + event_object = BotStarted(**event_json) + case UpdateType.CHAT_TITLE_CHANGED: + event_object = ChatTitleChanged(**event_json) + case UpdateType.MESSAGE_CALLBACK: + event_object = MessageCallback(**event_json) + event_object.message.bot = self.bot + case UpdateType.MESSAGE_CHAT_CREATED: + event_object = MessageChatCreated(**event_json) + case UpdateType.MESSAGE_CREATED: + event_object = MessageCreated(**event_json) + event_object.message.bot = self.bot + case UpdateType.MESSAGE_EDITED: + event_object = MessageEdited(**event_json) + case UpdateType.MESSAGE_REMOVED: + event_object = MessageRemoved(**event_json) + case UpdateType.USER_ADDED: + event_object = UserAdded(**event_json) + case UpdateType.USER_REMOVED: + event_object = UserRemoved(**event_json) + + handlers: List[Handler] = self.event_handlers + for handler in handlers: + + if not handler.update_type == event.update_type: + continue + + if handler.filters: + if not filter_m(event_object, *handler.filters): + continue + + await handler.func_event(event_object) + break + + return True + except Exception as e: + print(e) + ... + + logger.info(f'{len(self.event_handlers)} event handlers started') + uvicorn.run(app, host=host, port=port, log_level='critical') + + +class Router(Dispatcher): + def __init__(self): + super().__init__() + + +class Event: + def __init__(self, update_type: UpdateType, router: Dispatcher | Router): + self.update_type = update_type + self.router = router + + def __call__(self, *args, **kwargs): + def decorator(func_event: Callable): + self.router.event_handlers.append( + Handler( + func_event=func_event, + update_type=self.update_type, + *args, **kwargs + ) + ) + return func_event + + return decorator \ No newline at end of file diff --git a/maxapi/enums/api_path.py b/maxapi/enums/api_path.py new file mode 100644 index 0000000..0b23947 --- /dev/null +++ b/maxapi/enums/api_path.py @@ -0,0 +1,7 @@ +from enum import Enum + +class ApiPath(str, Enum): + ME = '/me' + CHATS = '/chats' + MESSAGES = '/messages' + UPDATES = '/updates' \ No newline at end of file diff --git a/maxapi/enums/attachment.py b/maxapi/enums/attachment.py new file mode 100644 index 0000000..00614d2 --- /dev/null +++ b/maxapi/enums/attachment.py @@ -0,0 +1,11 @@ +from enum import Enum + +class AttachmentType(str, Enum): + IMAGE = 'image' + VIDEO = 'video' + AUDIO = 'audio' + FILE = 'file' + STICKER = 'sticker' + CONTACT = 'contact' + INLINE_KEYBOARD = 'inline_keyboard' + LOCATION = 'location' \ No newline at end of file diff --git a/maxapi/enums/button_type.py b/maxapi/enums/button_type.py new file mode 100644 index 0000000..08b5715 --- /dev/null +++ b/maxapi/enums/button_type.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class ButtonType(Enum): + REQUEST_CONTACT = 'request_contact' + CALLBACK = 'callback' + LINK = 'link' + REQUEST_GEO_LOCATION = 'request_geo_location' + CHAT = 'chat' diff --git a/maxapi/enums/chat_type.py b/maxapi/enums/chat_type.py new file mode 100644 index 0000000..9d1c19f --- /dev/null +++ b/maxapi/enums/chat_type.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class ChatType(str, Enum): + DIALOG = 'dialog' + CHAT = 'chat' \ No newline at end of file diff --git a/maxapi/enums/http_method.py b/maxapi/enums/http_method.py new file mode 100644 index 0000000..0f288f5 --- /dev/null +++ b/maxapi/enums/http_method.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class HTTPMethod(str, Enum): + POST = 'POST' + GET = 'GET' + PATCH = 'PATCH' + PUT = 'PUT' diff --git a/maxapi/enums/intent.py b/maxapi/enums/intent.py new file mode 100644 index 0000000..2ae81f4 --- /dev/null +++ b/maxapi/enums/intent.py @@ -0,0 +1,6 @@ +from enum import Enum + +class Intent(str, Enum): + DEFAULT = 'default' + POSITIVE = 'positive' + NEGATIVE = 'negative' \ No newline at end of file diff --git a/maxapi/enums/message_link_type.py b/maxapi/enums/message_link_type.py new file mode 100644 index 0000000..f5bfd50 --- /dev/null +++ b/maxapi/enums/message_link_type.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class MessageLinkType(str, Enum): + FORWARD = 'forward' + REPLY = 'reply' \ No newline at end of file diff --git a/maxapi/enums/parse_mode.py b/maxapi/enums/parse_mode.py new file mode 100644 index 0000000..e7ff9da --- /dev/null +++ b/maxapi/enums/parse_mode.py @@ -0,0 +1,5 @@ +from enum import Enum + +class ParseMode(str, Enum): + MARKDOWN = 'markdown' + HTML = 'html' \ No newline at end of file diff --git a/maxapi/enums/text_style.py b/maxapi/enums/text_style.py new file mode 100644 index 0000000..d349454 --- /dev/null +++ b/maxapi/enums/text_style.py @@ -0,0 +1,13 @@ +from enum import Enum + + +class TextStyle(Enum): + UNDERLINE = 'underline' + STRONG = 'strong' + EMPHASIZED = 'emphasized' + MONOSPACED = 'monospaced' + LINK = 'link' + STRIKETHROUGH = 'strikethrough' + USER_MENTION = 'user_mention' + HEADING = 'heading' + HIGHLIGHTED = 'highlighted' \ No newline at end of file diff --git a/maxapi/enums/update.py b/maxapi/enums/update.py new file mode 100644 index 0000000..8220410 --- /dev/null +++ b/maxapi/enums/update.py @@ -0,0 +1,14 @@ +from enum import Enum + +class UpdateType(str, Enum): + MESSAGE_CREATED = 'message_created' + BOT_ADDED = 'bot_added' + BOT_REMOVED = 'bot_removed' + BOT_STARTED = 'bot_started' + CHAT_TITLE_CHANGED = 'chat_title_changed' + MESSAGE_CALLBACK = 'message_callback' + MESSAGE_CHAT_CREATED = 'message_chat_created' + MESSAGE_EDITED = 'message_edited' + MESSAGE_REMOVED = 'message_removed' + USER_ADDED = 'user_added' + USER_REMOVED = 'user_removed' \ No newline at end of file diff --git a/maxapi/filters/__init__.py b/maxapi/filters/__init__.py new file mode 100644 index 0000000..9f86d7b --- /dev/null +++ b/maxapi/filters/__init__.py @@ -0,0 +1,53 @@ +from magic_filter import MagicFilter +from magic_filter.operations.call import CallOperation as mf_call +from magic_filter.operations.function import FunctionOperation as mf_func +from magic_filter.operations.comparator import ComparatorOperation as mf_comparator + +F = MagicFilter() + + +def filter_m(obj, *magic_args): + 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: + ... \ No newline at end of file diff --git a/maxapi/loggers.py b/maxapi/loggers.py new file mode 100644 index 0000000..2e976b2 --- /dev/null +++ b/maxapi/loggers.py @@ -0,0 +1,4 @@ +import logging + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger('bot') \ No newline at end of file diff --git a/maxapi/methods/change_info.py b/maxapi/methods/change_info.py new file mode 100644 index 0000000..027679f --- /dev/null +++ b/maxapi/methods/change_info.py @@ -0,0 +1,46 @@ + + +from typing import Any, Dict, List, TYPE_CHECKING + +from ..types.users import BotCommand, User + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath + +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class ChangeInfo(BaseConnection): + def __init__( + self, + bot: 'Bot', + name: str = None, + description: str = None, + commands: List[BotCommand] = None, + photo: Dict[str, Any] = None + ): + self.bot = bot + self.name = name + self.description = description + self.commands = commands + self.photo = photo + + async def request(self) -> User: + json = {} + + if self.name: json['name'] = self.name + if self.description: json['description'] = self.description + if self.commands: json['commands'] = [command.model_dump() for command in self.commands] + if self.photo: json['photo'] = self.photo + + return await super().request( + method=HTTPMethod.PATCH, + path=ApiPath.ME, + model=User, + params=self.bot.params, + json=json + ) \ No newline at end of file diff --git a/maxapi/methods/edit_message.py b/maxapi/methods/edit_message.py new file mode 100644 index 0000000..f0387ae --- /dev/null +++ b/maxapi/methods/edit_message.py @@ -0,0 +1,59 @@ + + +from typing import List, TYPE_CHECKING + +from aiomax.enums.parse_mode import ParseMode + +from ..types.attachments.attachment import Attachment + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from ..types.message import NewMessageLink +from .types.edited_message import EditedMessage +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class EditMessage(BaseConnection): + def __init__( + self, + bot: 'Bot', + message_id: str, + text: str = None, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + self.bot = bot + self.message_id = message_id + self.text = text + self.attachments = attachments + self.link = link + self.notify = notify + self.parse_mode = parse_mode + + async def request(self) -> 'EditedMessage': + params = self.bot.params.copy() + + json = {} + + params['message_id'] = self.message_id + + if not self.text is None: json['text'] = self.text + if self.attachments: json['attachments'] = \ + [att.model_dump() for att in self.attachments] + if not self.link is None: json['link'] = self.link.model_dump() + if not self.notify is None: json['notify'] = self.notify + if not self.parse_mode is None: json['format'] = self.parse_mode.value + + return await super().request( + method=HTTPMethod.PUT, + path=ApiPath.MESSAGES, + model=EditedMessage, + params=params, + json=json + ) \ No newline at end of file diff --git a/maxapi/methods/get_chats.py b/maxapi/methods/get_chats.py new file mode 100644 index 0000000..35616f4 --- /dev/null +++ b/maxapi/methods/get_chats.py @@ -0,0 +1,29 @@ + + +from typing import TYPE_CHECKING + +from ..types.chats import Chats + +from ..types.users import User + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath + +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class GetChats(BaseConnection): + def __init__(self, bot: 'Bot'): + self.bot = bot + + async def request(self) -> Chats: + return await super().request( + method=HTTPMethod.GET, + path=ApiPath.CHATS, + model=Chats, + params=self.bot.params + ) \ No newline at end of file diff --git a/maxapi/methods/get_me.py b/maxapi/methods/get_me.py new file mode 100644 index 0000000..833e4d5 --- /dev/null +++ b/maxapi/methods/get_me.py @@ -0,0 +1,29 @@ + + +from typing import TYPE_CHECKING + +from ..types.chats import Chats + +from ..types.users import User + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath + +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class GetMe(BaseConnection): + def __init__(self, bot: 'Bot'): + self.bot = bot + + async def request(self) -> Chats: + return await super().request( + method=HTTPMethod.GET, + path=ApiPath.ME, + model=User, + params=self.bot.params + ) \ No newline at end of file diff --git a/maxapi/methods/get_messages.py b/maxapi/methods/get_messages.py new file mode 100644 index 0000000..b667f4f --- /dev/null +++ b/maxapi/methods/get_messages.py @@ -0,0 +1,30 @@ + + +from typing import TYPE_CHECKING + +from ..types.message import Messages +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class GetMessages(BaseConnection): + def __init__(self, bot: 'Bot', chat_id: int = None): + self.bot = bot + self.chat_id = chat_id + + async def request(self) -> Messages: + params = self.bot.params.copy() + + if self.chat_id: params['chat_id'] = self.chat_id + + return await super().request( + method=HTTPMethod.GET, + path=ApiPath.MESSAGES, + model=Messages, + params=params + ) \ No newline at end of file diff --git a/maxapi/methods/send_message.py b/maxapi/methods/send_message.py new file mode 100644 index 0000000..04179f3 --- /dev/null +++ b/maxapi/methods/send_message.py @@ -0,0 +1,68 @@ + + +from typing import List, TYPE_CHECKING + +from ..enums.parse_mode import ParseMode + +from ..types.message import NewMessageLink + +from ..types.attachments.attachment import Attachment + +from ..enums.http_method import HTTPMethod +from ..enums.api_path import ApiPath +from .types.sended_message import SendedMessage +from ..connection.base import BaseConnection + + +if TYPE_CHECKING: + from ..bot import Bot + + +class SendMessage(BaseConnection): + def __init__( + self, + bot: 'Bot', + chat_id: int = None, + user_id: int = None, + disable_link_preview: bool = False, + text: str = None, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + self.bot = bot + self.chat_id = chat_id + self.user_id = user_id + self.disable_link_preview = disable_link_preview + self.text = text + self.attachments = attachments + self.link = link + self.notify = notify + self.parse_mode = parse_mode + + async def request(self) -> 'SendedMessage': + params = self.bot.params.copy() + + json = {} + + if self.chat_id: params['chat_id'] = self.chat_id + elif self.user_id: params['user_id'] = self.user_id + + json['text'] = self.text + json['disable_link_preview'] = str(self.disable_link_preview).lower() + + if self.attachments: json['attachments'] = \ + [att.model_dump() for att in self.attachments] + + if not self.link is None: json['link'] = self.link.model_dump() + if not self.notify is None: json['notify'] = self.notify + if not self.parse_mode is None: json['format'] = self.parse_mode.value + + return await super().request( + method=HTTPMethod.POST, + path=ApiPath.MESSAGES, + model=SendedMessage, + params=params, + json=json + ) \ No newline at end of file diff --git a/maxapi/methods/types/edited_message.py b/maxapi/methods/types/edited_message.py new file mode 100644 index 0000000..1c4729d --- /dev/null +++ b/maxapi/methods/types/edited_message.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + +from ...types.message import Message + + +class EditedMessage(BaseModel): + message: Message \ No newline at end of file diff --git a/maxapi/methods/types/sended_message.py b/maxapi/methods/types/sended_message.py new file mode 100644 index 0000000..d218079 --- /dev/null +++ b/maxapi/methods/types/sended_message.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + +from ...types.message import Message + + +class SendedMessage(BaseModel): + message: Message \ No newline at end of file diff --git a/maxapi/types/__init__.py b/maxapi/types/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/maxapi/types/attachments/attachment.py b/maxapi/types/attachments/attachment.py new file mode 100644 index 0000000..02439ed --- /dev/null +++ b/maxapi/types/attachments/attachment.py @@ -0,0 +1,59 @@ +from typing import List, Optional, Union +from pydantic import BaseModel + +from ...types.attachments.buttons.chat_button import ChatButton +from ...types.attachments.buttons.request_contact import RequestContact +from ...types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton +from ...types.attachments.buttons.link_button import LinkButton +from ...types.users import User +from ...enums.attachment import AttachmentType +from .buttons.callback_button import CallbackButton + +AttachmentUnion = [] + + +class StickerAttachmentPayload(BaseModel): + url: str + code: str + + +class PhotoAttachmentPayload(BaseModel): + photo_id: int + token: str + url: str + + +class OtherAttachmentPayload(BaseModel): + url: str + token: Optional[str] = None + + +class ContactAttachmentPayload(BaseModel): + vcf_info: Optional[str] = None + max_info: Optional[User] = None + + +class ButtonsPayload(BaseModel): + buttons: List[List[ + Union[ + LinkButton, + CallbackButton, + RequestGeoLocationButton, + RequestContact, + ChatButton + ] + ]] + + +class Attachment(BaseModel): + type: AttachmentType + payload: Optional[Union[ + PhotoAttachmentPayload, + OtherAttachmentPayload, + ContactAttachmentPayload, + ButtonsPayload, + StickerAttachmentPayload + ]] = None + + class Config: + use_enum_values = True \ No newline at end of file diff --git a/maxapi/types/attachments/audio.py b/maxapi/types/attachments/audio.py new file mode 100644 index 0000000..84955ce --- /dev/null +++ b/maxapi/types/attachments/audio.py @@ -0,0 +1,8 @@ +from typing import Literal, Optional + +from .attachment import Attachment + + +class Audio(Attachment): + type: Literal['audio'] = 'audio' + transcription: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/__init__.py b/maxapi/types/attachments/buttons/__init__.py new file mode 100644 index 0000000..18732c3 --- /dev/null +++ b/maxapi/types/attachments/buttons/__init__.py @@ -0,0 +1,12 @@ +from typing import Literal +from pydantic import BaseModel + +from ....enums.button_type import ButtonType + + +class Button(BaseModel): + type: ButtonType + text: str + + class Config: + use_enum_values = True \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/attachment_button.py b/maxapi/types/attachments/buttons/attachment_button.py new file mode 100644 index 0000000..7d9658c --- /dev/null +++ b/maxapi/types/attachments/buttons/attachment_button.py @@ -0,0 +1,9 @@ +from typing import Literal +from pydantic import BaseModel + +from ..attachment import ButtonsPayload + + +class AttachmentButton(BaseModel): + type: Literal['inline_keyboard'] = 'inline_keyboard' + payload: ButtonsPayload \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/callback_button.py b/maxapi/types/attachments/buttons/callback_button.py new file mode 100644 index 0000000..c5e057a --- /dev/null +++ b/maxapi/types/attachments/buttons/callback_button.py @@ -0,0 +1,9 @@ +from typing import Optional + +from ....enums.intent import Intent +from . import Button + + +class CallbackButton(Button): + payload: Optional[str] = None + intent: Intent \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/chat_button.py b/maxapi/types/attachments/buttons/chat_button.py new file mode 100644 index 0000000..a56eda3 --- /dev/null +++ b/maxapi/types/attachments/buttons/chat_button.py @@ -0,0 +1,11 @@ +from typing import Optional + +from ....types.attachments.buttons import Button + + +class ChatButton(Button): + chat_title: Optional[str] = None + chat_description: Optional[str] = None + start_payload: Optional[str] = None + chat_title: Optional[str] = None + uuid: Optional[int] = None \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/link_button.py b/maxapi/types/attachments/buttons/link_button.py new file mode 100644 index 0000000..3b393e6 --- /dev/null +++ b/maxapi/types/attachments/buttons/link_button.py @@ -0,0 +1,7 @@ +from typing import Optional + +from ....types.attachments.buttons import Button + + +class LinkButton(Button): + url: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/request_contact.py b/maxapi/types/attachments/buttons/request_contact.py new file mode 100644 index 0000000..1db3212 --- /dev/null +++ b/maxapi/types/attachments/buttons/request_contact.py @@ -0,0 +1,5 @@ +from ....types.attachments.buttons import Button + + +class RequestContact(Button): + ... \ No newline at end of file diff --git a/maxapi/types/attachments/buttons/request_geo_location_button.py b/maxapi/types/attachments/buttons/request_geo_location_button.py new file mode 100644 index 0000000..cd101bb --- /dev/null +++ b/maxapi/types/attachments/buttons/request_geo_location_button.py @@ -0,0 +1,5 @@ +from ....types.attachments.buttons import Button + + +class RequestGeoLocationButton(Button): + quick: bool = False \ No newline at end of file diff --git a/maxapi/types/attachments/contact.py b/maxapi/types/attachments/contact.py new file mode 100644 index 0000000..9e6026d --- /dev/null +++ b/maxapi/types/attachments/contact.py @@ -0,0 +1,7 @@ +from typing import Literal + +from .attachment import Attachment + + +class Contact(Attachment): + type: Literal['contact'] = 'contact' \ No newline at end of file diff --git a/maxapi/types/attachments/file.py b/maxapi/types/attachments/file.py new file mode 100644 index 0000000..99a38b1 --- /dev/null +++ b/maxapi/types/attachments/file.py @@ -0,0 +1,9 @@ +from typing import Literal, Optional + +from .attachment import Attachment + + +class File(Attachment): + type: Literal['file'] = 'file' + filename: Optional[str] = None + size: Optional[int] = None \ No newline at end of file diff --git a/maxapi/types/attachments/image.py b/maxapi/types/attachments/image.py new file mode 100644 index 0000000..2d49a94 --- /dev/null +++ b/maxapi/types/attachments/image.py @@ -0,0 +1,6 @@ +from typing import Literal +from .attachment import Attachment + + +class Image(Attachment): + type: Literal['image'] = 'image' \ No newline at end of file diff --git a/maxapi/types/attachments/location.py b/maxapi/types/attachments/location.py new file mode 100644 index 0000000..5ffd7fc --- /dev/null +++ b/maxapi/types/attachments/location.py @@ -0,0 +1,9 @@ +from typing import Literal, Optional + +from .attachment import Attachment + + +class Location(Attachment): + type: Literal['location'] = 'location' + latitude: Optional[float] = None + longitude: Optional[float] = None \ No newline at end of file diff --git a/maxapi/types/attachments/share.py b/maxapi/types/attachments/share.py new file mode 100644 index 0000000..3b70bd2 --- /dev/null +++ b/maxapi/types/attachments/share.py @@ -0,0 +1,9 @@ +from typing import Optional + +from .attachment import Attachment + + +class Share(Attachment): + title: Optional[str] = None + description: Optional[str] = None + image_url: Optional[str] = None diff --git a/maxapi/types/attachments/sticker.py b/maxapi/types/attachments/sticker.py new file mode 100644 index 0000000..29b1509 --- /dev/null +++ b/maxapi/types/attachments/sticker.py @@ -0,0 +1,9 @@ +from typing import Literal, Optional + +from .attachment import Attachment + + +class Sticker(Attachment): + type: Literal['sticker'] = 'sticker' + width: Optional[int] = None + height: Optional[int] = None \ No newline at end of file diff --git a/maxapi/types/attachments/video.py b/maxapi/types/attachments/video.py new file mode 100644 index 0000000..58dba39 --- /dev/null +++ b/maxapi/types/attachments/video.py @@ -0,0 +1,16 @@ +from typing import Literal, Optional +from pydantic import BaseModel + +from .attachment import Attachment + + +class VideoThumbnail(BaseModel): + url: str + + +class Video(Attachment): + type: Literal['video'] = 'video' + thumbnail: VideoThumbnail + width: Optional[int] = None + height: Optional[int] = None + duration: Optional[int] = None diff --git a/maxapi/types/callback.py b/maxapi/types/callback.py new file mode 100644 index 0000000..bfb74fc --- /dev/null +++ b/maxapi/types/callback.py @@ -0,0 +1,11 @@ +from typing import Optional +from pydantic import BaseModel + +from ..types.users import User + + +class Callback(BaseModel): + timestamp: int + callback_id: str + payload: Optional[str] = None + user: User \ No newline at end of file diff --git a/maxapi/types/chats.py b/maxapi/types/chats.py new file mode 100644 index 0000000..0c60f6a --- /dev/null +++ b/maxapi/types/chats.py @@ -0,0 +1,46 @@ +from pydantic import BaseModel +from typing import List, Optional +from enum import Enum +from datetime import datetime + +from ..types.users import User +from ..types.message import Message + +class ChatType(str, Enum): + DIALOG = "dialog" + CHAT = "chat" + +class ChatStatus(str, Enum): + ACTIVE = "active" + REMOVED = "removed" + LEFT = "left" + CLOSED = "closed" + SUSPENDED = "suspended" + +class Icon(BaseModel): + url: str + +class Chat(BaseModel): + chat_id: int + type: ChatType + status: ChatStatus + title: Optional[str] = None + icon: Optional[Icon] = None + last_event_time: int + participants_count: int + owner_id: Optional[int] = None + participants: None = None + is_public: bool + link: Optional[str] = None + description: Optional[str] = None + dialog_with_user: Optional[User] = None + messages_count: Optional[int] = None + chat_message_id: Optional[str] = None + pinned_message: Optional[Message] = None + + class Config: + arbitrary_types_allowed=True + + +class Chats(BaseModel): + chats: List[Chat] = [] \ No newline at end of file diff --git a/maxapi/types/errors.py b/maxapi/types/errors.py new file mode 100644 index 0000000..6a8f143 --- /dev/null +++ b/maxapi/types/errors.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class Error(BaseModel): + code: int + text: str \ No newline at end of file diff --git a/maxapi/types/message.py b/maxapi/types/message.py new file mode 100644 index 0000000..de97c38 --- /dev/null +++ b/maxapi/types/message.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from pydantic import BaseModel, Field +from typing import Any, Optional, List, Union, TYPE_CHECKING + +from ..enums.parse_mode import ParseMode +from ..types.attachments.attachment import Attachment +from ..types.attachments.share import Share +from .attachments.buttons.attachment_button import AttachmentButton +from ..enums.text_style import TextStyle +from ..enums.chat_type import ChatType +from ..enums.message_link_type import MessageLinkType +from .attachments.sticker import Sticker +from .attachments.file import File +from .attachments.image import Image +from .attachments.video import Video +from .attachments.audio import Audio +from ..types.users import User + + +if TYPE_CHECKING: + from ..bot import Bot + + +class MarkupElement(BaseModel): + type: TextStyle + from_: int = Field(..., alias='from') + length: int + + class Config: + populate_by_name = True + + +class MarkupLink(MarkupElement): + url: Optional[str] = None + + +class Recipient(BaseModel): + user_id: Optional[int] = None # Для пользователя + chat_id: Optional[int] = None # Для чата + chat_type: ChatType # Тип получателя (диалог или чат) + + +class MessageBody(BaseModel): + mid: str + seq: int + text: str = None + attachments: Optional[ + List[ + Union[ + AttachmentButton, + Audio, + Video, + File, + Image, + Sticker, + Share + ] + ] + ] = [] + + markup: Optional[ + List[ + Union[ + MarkupLink, MarkupElement + ] + ] + ] = [] + + +class MessageStat(BaseModel): + views: int + + +class LinkedMessage(BaseModel): + type: MessageLinkType + sender: User + chat_id: Optional[int] = None + message: MessageBody + + +class Message(BaseModel): + sender: User + recipient: Recipient + timestamp: int + link: Optional[LinkedMessage] = None + body: Optional[MessageBody] = None + stat: Optional[MessageStat] = None + url: Optional[str] = None + bot: Optional[Any] = None + + async def answer(self, + text: str = None, + disable_link_preview: bool = False, + attachments: List[Attachment] = None, + link: NewMessageLink = None, + notify: bool = True, + parse_mode: ParseMode = None + ): + bot: Bot = self.bot + return await bot.send_message( + chat_id=self.recipient.chat_id, + user_id=self.recipient.user_id, + text=text, + disable_link_preview=disable_link_preview, + attachments=attachments, + link=link, + notify=notify, + parse_mode=parse_mode + ) + + +class Messages(BaseModel): + messages: List[Message] + + +class NewMessageLink(BaseModel): + type: MessageLinkType + mid: str \ No newline at end of file diff --git a/maxapi/types/updates/__init__.py b/maxapi/types/updates/__init__.py new file mode 100644 index 0000000..c600837 --- /dev/null +++ b/maxapi/types/updates/__init__.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + +from ...enums.update import UpdateType + + +class Update(BaseModel): + update_type: UpdateType + timestamp: int + + class Config: + arbitrary_types_allowed=True \ No newline at end of file diff --git a/maxapi/types/updates/bot_added.py b/maxapi/types/updates/bot_added.py new file mode 100644 index 0000000..53f6518 --- /dev/null +++ b/maxapi/types/updates/bot_added.py @@ -0,0 +1,8 @@ +from typing import Optional + +from . import Update +from ...types.users import User + +class BotAdded(Update): + chat_id: Optional[int] = None + user: User \ No newline at end of file diff --git a/maxapi/types/updates/bot_removed.py b/maxapi/types/updates/bot_removed.py new file mode 100644 index 0000000..7131a35 --- /dev/null +++ b/maxapi/types/updates/bot_removed.py @@ -0,0 +1,8 @@ +from typing import Optional + +from . import Update +from ...types.users import User + +class BotRemoved(Update): + chat_id: Optional[int] = None + user: User \ No newline at end of file diff --git a/maxapi/types/updates/bot_started.py b/maxapi/types/updates/bot_started.py new file mode 100644 index 0000000..d6d4dc3 --- /dev/null +++ b/maxapi/types/updates/bot_started.py @@ -0,0 +1,10 @@ +from typing import Optional + +from . import Update +from ...types.users import User + +class BotStarted(Update): + chat_id: Optional[int] = None + user: User + user_locale: Optional[str] = None + payload: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/updates/chat_title_changed.py b/maxapi/types/updates/chat_title_changed.py new file mode 100644 index 0000000..2f88f79 --- /dev/null +++ b/maxapi/types/updates/chat_title_changed.py @@ -0,0 +1,9 @@ +from typing import Optional + +from . import Update +from ...types.users import User + +class ChatTitleChanged(Update): + chat_id: Optional[int] = None + user: User + title: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/updates/message_callback.py b/maxapi/types/updates/message_callback.py new file mode 100644 index 0000000..3cc2da9 --- /dev/null +++ b/maxapi/types/updates/message_callback.py @@ -0,0 +1,11 @@ +from typing import Optional + +from . import Update +from ...types.callback import Callback +from ...types.message import Message + + +class MessageCallback(Update): + message: Message + user_locale: Optional[str] = None + callback: Callback \ No newline at end of file diff --git a/maxapi/types/updates/message_chat_created.py b/maxapi/types/updates/message_chat_created.py new file mode 100644 index 0000000..9f990dc --- /dev/null +++ b/maxapi/types/updates/message_chat_created.py @@ -0,0 +1,11 @@ +from typing import Optional + +from ...types.chats import Chat + +from . import Update + +class MessageChatCreated(Update): + chat: Chat + title: Optional[str] = None + message_id: Optional[str] = None + start_payload: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/updates/message_created.py b/maxapi/types/updates/message_created.py new file mode 100644 index 0000000..4e8e7d4 --- /dev/null +++ b/maxapi/types/updates/message_created.py @@ -0,0 +1,9 @@ +from typing import Optional + +from . import Update +from ...types.message import Message + + +class MessageCreated(Update): + message: Message + user_locale: Optional[str] = None \ No newline at end of file diff --git a/maxapi/types/updates/message_edited.py b/maxapi/types/updates/message_edited.py new file mode 100644 index 0000000..2ae865c --- /dev/null +++ b/maxapi/types/updates/message_edited.py @@ -0,0 +1,6 @@ +from . import Update +from ...types.message import Message + + +class MessageEdited(Update): + message: Message \ No newline at end of file diff --git a/maxapi/types/updates/message_removed.py b/maxapi/types/updates/message_removed.py new file mode 100644 index 0000000..c04cd55 --- /dev/null +++ b/maxapi/types/updates/message_removed.py @@ -0,0 +1,9 @@ +from typing import Optional + +from . import Update + + +class MessageRemoved(Update): + message_id: Optional[str] = None + chat_id: Optional[int] = None + user_id: Optional[int] = None \ No newline at end of file diff --git a/maxapi/types/updates/user_added.py b/maxapi/types/updates/user_added.py new file mode 100644 index 0000000..7ac9303 --- /dev/null +++ b/maxapi/types/updates/user_added.py @@ -0,0 +1,10 @@ +from typing import Optional + +from . import Update +from ...types.users import User + + +class UserAdded(Update): + inviter_id: Optional[int] = None + chat_id: Optional[int] = None + user: User diff --git a/maxapi/types/updates/user_removed.py b/maxapi/types/updates/user_removed.py new file mode 100644 index 0000000..f7157c6 --- /dev/null +++ b/maxapi/types/updates/user_removed.py @@ -0,0 +1,10 @@ +from typing import Optional + +from . import Update +from ...types.users import User + + +class UserRemoved(Update): + admin_id: Optional[int] = None + chat_id: Optional[int] = None + user: User diff --git a/maxapi/types/users.py b/maxapi/types/users.py new file mode 100644 index 0000000..57f1553 --- /dev/null +++ b/maxapi/types/users.py @@ -0,0 +1,26 @@ +from pydantic import BaseModel +from typing import List, Optional +from datetime import datetime + + +class BotCommand(BaseModel): + name: str + description: Optional[str] = None + + +class User(BaseModel): + user_id: int + first_name: str + last_name: Optional[str] = None + username: Optional[str] = None + is_bot: bool + last_activity_time: int + description: Optional[str] = None + avatar_url: Optional[str] = None + full_avatar_url: Optional[str] = None + commands: Optional[List[BotCommand]] = None + + class Config: + json_encoders = { + datetime: lambda v: int(v.timestamp() * 1000) # Конвертация datetime в Unix-время (ms) + } \ No newline at end of file