├── .python-version ├── maxapi ├── enums │ ├── __init__.py │ ├── chat_type.py │ ├── message_link_type.py │ ├── parse_mode.py │ ├── intent.py │ ├── upload_type.py │ ├── http_method.py │ ├── chat_status.py │ ├── button_type.py │ ├── attachment.py │ ├── sender_action.py │ ├── text_style.py │ ├── api_path.py │ ├── chat_permission.py │ └── update.py ├── methods │ ├── __init__.py │ ├── types │ │ ├── __init__.py │ │ ├── getted_upload_url.py │ │ ├── sended_message.py │ │ ├── getted_subscriptions.py │ │ ├── deleted_chat.py │ │ ├── getted_pineed_message.py │ │ ├── deleted_message.py │ │ ├── edited_message.py │ │ ├── subscribed.py │ │ ├── unsubscribed.py │ │ ├── deleted_bot_from_chat.py │ │ ├── pinned_message.py │ │ ├── sended_action.py │ │ ├── added_admin_chat.py │ │ ├── added_members_chat.py │ │ ├── removed_admin.py │ │ ├── removed_member_chat.py │ │ ├── deleted_pin_message.py │ │ ├── getted_members_chat.py │ │ ├── getted_list_admin_chat.py │ │ ├── sended_callback.py │ │ └── getted_updates.py │ ├── get_me.py │ ├── get_chat_by_id.py │ ├── get_subscriptions.py │ ├── get_video.py │ ├── delete_chat.py │ ├── get_message.py │ ├── get_pinned_message.py │ ├── get_me_from_chat.py │ ├── get_list_admin_chat.py │ ├── delete_pin_message.py │ ├── get_chat_by_link.py │ ├── get_upload_url.py │ ├── delete_bot_from_chat.py │ ├── remove_admin.py │ ├── unsubscribe_webhook.py │ ├── delete_message.py │ ├── get_chats.py │ ├── send_action.py │ ├── add_members_chat.py │ ├── pin_message.py │ ├── remove_member_chat.py │ ├── send_callback.py │ ├── add_admin_chat.py │ ├── get_members_chat.py │ ├── get_updates.py │ ├── subscribe_webhook.py │ └── get_messages.py ├── utils │ ├── __init__.py │ ├── inline_keyboard.py │ └── message.py ├── connection │ └── __init__.py ├── exceptions │ ├── download_file.py │ ├── max.py │ ├── __init__.py │ └── dispatcher.py ├── client │ ├── __init__.py │ └── default.py ├── __init__.py ├── context │ ├── __init__.py │ ├── state_machine.py │ └── context.py ├── loggers.py ├── types │ ├── attachments │ │ ├── buttons │ │ │ ├── request_contact.py │ │ │ ├── message_button.py │ │ │ ├── link_button.py │ │ │ ├── button.py │ │ │ ├── attachment_button.py │ │ │ ├── request_geo_location_button.py │ │ │ ├── __init__.py │ │ │ ├── chat_button.py │ │ │ ├── callback_button.py │ │ │ └── open_app_button.py │ │ ├── contact.py │ │ ├── audio.py │ │ ├── file.py │ │ ├── location.py │ │ ├── sticker.py │ │ ├── share.py │ │ ├── upload.py │ │ ├── __init__.py │ │ ├── image.py │ │ ├── video.py │ │ └── attachment.py │ ├── command.py │ ├── subscription.py │ ├── callback.py │ ├── bot_mixin.py │ ├── updates │ │ ├── message_chat_created.py │ │ ├── message_edited.py │ │ ├── chat_title_changed.py │ │ ├── dialog_cleared.py │ │ ├── dialog_removed.py │ │ ├── dialog_unmuted.py │ │ ├── bot_added.py │ │ ├── bot_removed.py │ │ ├── bot_stopped.py │ │ ├── bot_started.py │ │ ├── message_created.py │ │ ├── message_removed.py │ │ ├── __init__.py │ │ ├── user_added.py │ │ ├── user_removed.py │ │ ├── dialog_muted.py │ │ ├── update.py │ │ └── message_callback.py │ ├── users.py │ ├── __init__.py │ └── input_media.py └── filters │ ├── filter.py │ ├── __init__.py │ ├── middleware.py │ └── handler.py ├── tests ├── __init__.py ├── check_env.py └── get_chat_id.py ├── MANIFEST.in ├── setup.py ├── .github ├── FUNDING.yml └── workflows │ ├── lint.yml │ ├── mypy.yml │ ├── docs.yml │ ├── tests.yml │ └── publish.yml ├── docs ├── enums │ ├── index.md │ ├── api_path.md │ ├── chat_type.md │ ├── intent.md │ ├── update.md │ ├── parse_mode.md │ ├── text_style.md │ ├── button_type.md │ ├── chat_status.md │ ├── http_method.md │ ├── upload_type.md │ ├── attachment.md │ ├── sender_action.md │ ├── chat_permission.md │ └── message_link_type.md ├── types │ ├── index.md │ ├── updates │ │ ├── index.md │ │ ├── update.md │ │ ├── bot_added.md │ │ ├── user_added.md │ │ ├── bot_removed.md │ │ ├── bot_started.md │ │ ├── bot_stopped.md │ │ ├── dialog_muted.md │ │ ├── user_removed.md │ │ ├── dialog_cleared.md │ │ ├── dialog_removed.md │ │ ├── dialog_unmuted.md │ │ ├── message_created.md │ │ ├── message_edited.md │ │ ├── message_removed.md │ │ ├── message_callback.md │ │ ├── chat_title_changed.md │ │ └── message_chat_created.md │ ├── attachments │ │ ├── index.md │ │ ├── buttons │ │ │ ├── index.md │ │ │ ├── button.md │ │ │ ├── chat_button.md │ │ │ ├── link_button.md │ │ │ ├── message_button.md │ │ │ ├── callback_button.md │ │ │ ├── open_app_button.md │ │ │ ├── request_contact.md │ │ │ ├── attachment_button.md │ │ │ └── request_geo_location_button.md │ │ ├── audio.md │ │ ├── file.md │ │ ├── image.md │ │ ├── share.md │ │ ├── video.md │ │ ├── upload.md │ │ ├── contact.md │ │ ├── location.md │ │ ├── sticker.md │ │ └── attachment.md │ ├── chats.md │ ├── users.md │ ├── callback.md │ ├── command.md │ ├── message.md │ ├── input_media.md │ ├── subscription.md │ └── errors.md ├── utils │ ├── index.md │ ├── message.md │ ├── updates.md │ └── inline_keyboard.md ├── context │ ├── index.md │ ├── context.md │ └── state_machine.md ├── filters │ ├── index.md │ ├── filter.md │ ├── command.md │ ├── handler.md │ ├── middleware.md │ └── callback_payload.md ├── methods │ ├── index.md │ ├── types │ │ ├── index.md │ │ ├── subscribed.md │ │ ├── deleted_chat.md │ │ ├── removed_admin.md │ │ ├── sended_action.md │ │ ├── unsubscribed.md │ │ ├── deleted_message.md │ │ ├── edited_message.md │ │ ├── getted_updates.md │ │ ├── pinned_message.md │ │ ├── sended_callback.md │ │ ├── sended_message.md │ │ ├── added_admin_chat.md │ │ ├── getted_upload_url.md │ │ ├── added_members_chat.md │ │ ├── deleted_pin_message.md │ │ ├── getted_members_chat.md │ │ ├── removed_member_chat.md │ │ ├── deleted_bot_from_chat.md │ │ ├── getted_subscriptions.md │ │ ├── getted_list_admin_chat.md │ │ └── getted_pineed_message.md │ ├── get_me.md │ ├── pin_message.md │ ├── edit_chat.md │ ├── get_chat_by_id.md │ ├── get_chats.md │ ├── get_video.md │ ├── send_message.md │ ├── add_admin_chat.md │ ├── change_info.md │ ├── delete_chat.md │ ├── get_message.md │ ├── get_updates.md │ ├── send_action.md │ ├── edit_message.md │ ├── get_messages.md │ ├── delete_message.md │ ├── get_upload_url.md │ ├── send_callback.md │ ├── add_members_chat.md │ ├── get_chat_by_link.md │ ├── get_me_from_chat.md │ ├── get_members_chat.md │ ├── remove_admin.md │ ├── delete_pin_message.md │ ├── get_pinned_message.md │ ├── get_subscriptions.md │ ├── subscribe_webhook.md │ ├── delete_bot_from_chat.md │ ├── get_list_admin_chat.md │ ├── remove_member_chat.md │ └── unsubscribe_webhook.md ├── connection │ ├── index.md │ └── base.md ├── bot.md ├── loggers.md ├── dispatcher.md ├── exceptions │ ├── invalid_token.md │ ├── download_file.md │ ├── dispatcher.md │ └── max.md ├── client │ └── default.md ├── guides │ ├── installation.md │ ├── webhook_vs_polling.md │ ├── routers.md │ ├── keyboards.md │ ├── middleware.md │ ├── context.md │ ├── handlers.md │ └── filters.md └── index.md ├── logo.png ├── doc ├── todo.md └── dev.md ├── Makefile ├── LICENSE └── pyproject.toml /.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /maxapi/enums/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maxapi/methods/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maxapi/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maxapi/connection/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /maxapi/methods/types/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Тесты для maxapi.""" 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.md 2 | include README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://boosty.to/loveapples'] -------------------------------------------------------------------------------- /docs/enums/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.enums 2 | 3 | ::: maxapi.enums 4 | -------------------------------------------------------------------------------- /docs/types/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.types 2 | 3 | ::: maxapi.types 4 | -------------------------------------------------------------------------------- /docs/utils/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.utils 2 | 3 | ::: maxapi.utils 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/love-apples/maxapi/HEAD/logo.png -------------------------------------------------------------------------------- /docs/context/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.context 2 | 3 | ::: maxapi.context 4 | -------------------------------------------------------------------------------- /docs/filters/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.filters 2 | 3 | ::: maxapi.filters 4 | -------------------------------------------------------------------------------- /docs/methods/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.methods 2 | 3 | ::: maxapi.methods 4 | -------------------------------------------------------------------------------- /docs/connection/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.connection 2 | 3 | ::: maxapi.connection 4 | -------------------------------------------------------------------------------- /maxapi/exceptions/download_file.py: -------------------------------------------------------------------------------- 1 | class NotAvailableForDownload(Exception): ... 2 | -------------------------------------------------------------------------------- /docs/methods/types/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.methods.types 2 | 3 | ::: maxapi.methods.types 4 | -------------------------------------------------------------------------------- /docs/types/updates/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.types.updates 2 | 3 | ::: maxapi.types.updates 4 | -------------------------------------------------------------------------------- /docs/types/attachments/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.types.attachments 2 | 3 | ::: maxapi.types.attachments 4 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/index.md: -------------------------------------------------------------------------------- 1 | # Package: maxapi.types.attachments.buttons 2 | 3 | ::: maxapi.types.attachments.buttons 4 | -------------------------------------------------------------------------------- /docs/bot.md: -------------------------------------------------------------------------------- 1 | # Bot Module 2 | 3 | ::: maxapi.bot 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/loggers.md: -------------------------------------------------------------------------------- 1 | # Loggers Module 2 | 3 | ::: maxapi.loggers 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /maxapi/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .default import DefaultConnectionProperties 2 | 3 | __all__ = [ 4 | "DefaultConnectionProperties", 5 | ] 6 | -------------------------------------------------------------------------------- /docs/methods/get_me.md: -------------------------------------------------------------------------------- 1 | # GetMe 2 | 3 | ::: maxapi.methods.get_me 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/dispatcher.md: -------------------------------------------------------------------------------- 1 | # Dispatcher Module 2 | 3 | ::: maxapi.dispatcher 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/api_path.md: -------------------------------------------------------------------------------- 1 | # ApiPath 2 | 3 | ::: maxapi.enums.api_path 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/chat_type.md: -------------------------------------------------------------------------------- 1 | # ChatType 2 | 3 | ::: maxapi.enums.chat_type 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/intent.md: -------------------------------------------------------------------------------- 1 | # Intent Module 2 | 3 | ::: maxapi.enums.intent 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/update.md: -------------------------------------------------------------------------------- 1 | # Update Module 2 | 3 | ::: maxapi.enums.update 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/pin_message.md: -------------------------------------------------------------------------------- 1 | ::: maxapi.methods.pin_message.PinMessage 2 | options: 3 | show_root_heading: true 4 | members_order: source 5 | -------------------------------------------------------------------------------- /docs/types/chats.md: -------------------------------------------------------------------------------- 1 | # Chats Module 2 | 3 | ::: maxapi.types.chats 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/users.md: -------------------------------------------------------------------------------- 1 | # Users Module 2 | 3 | ::: maxapi.types.users 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/parse_mode.md: -------------------------------------------------------------------------------- 1 | # ParseMode 2 | 3 | ::: maxapi.enums.parse_mode 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/text_style.md: -------------------------------------------------------------------------------- 1 | # TextStyle 2 | 3 | ::: maxapi.enums.text_style 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/filters/filter.md: -------------------------------------------------------------------------------- 1 | # Filter Module 2 | 3 | ::: maxapi.filters.filter 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/edit_chat.md: -------------------------------------------------------------------------------- 1 | # EditChat 2 | 3 | ::: maxapi.methods.edit_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_chat_by_id.md: -------------------------------------------------------------------------------- 1 | ::: maxapi.methods.get_chat_by_id.GetChatById 2 | options: 3 | show_root_heading: true 4 | members_order: source 5 | -------------------------------------------------------------------------------- /docs/methods/get_chats.md: -------------------------------------------------------------------------------- 1 | # GetChats 2 | 3 | ::: maxapi.methods.get_chats 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_video.md: -------------------------------------------------------------------------------- 1 | # GetVideo 2 | 3 | ::: maxapi.methods.get_video 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/send_message.md: -------------------------------------------------------------------------------- 1 | ::: maxapi.methods.send_message.SendMessage 2 | options: 3 | show_root_heading: true 4 | members_order: source 5 | -------------------------------------------------------------------------------- /docs/types/callback.md: -------------------------------------------------------------------------------- 1 | # Callback Module 2 | 3 | ::: maxapi.types.callback 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/command.md: -------------------------------------------------------------------------------- 1 | # Command Module 2 | 3 | ::: maxapi.types.command 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/message.md: -------------------------------------------------------------------------------- 1 | # Message Module 2 | 3 | ::: maxapi.types.message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/utils/message.md: -------------------------------------------------------------------------------- 1 | # Message Module 2 | 3 | ::: maxapi.utils.message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/utils/updates.md: -------------------------------------------------------------------------------- 1 | # Updates Module 2 | 3 | ::: maxapi.utils.updates 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/connection/base.md: -------------------------------------------------------------------------------- 1 | # BaseConnection 2 | 3 | ::: maxapi.connection.base 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/context/context.md: -------------------------------------------------------------------------------- 1 | # Context Module 2 | 3 | ::: maxapi.context.context 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/button_type.md: -------------------------------------------------------------------------------- 1 | # ButtonType 2 | 3 | ::: maxapi.enums.button_type 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/chat_status.md: -------------------------------------------------------------------------------- 1 | # ChatStatus 2 | 3 | ::: maxapi.enums.chat_status 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/http_method.md: -------------------------------------------------------------------------------- 1 | # HTTPMethod 2 | 3 | ::: maxapi.enums.http_method 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/upload_type.md: -------------------------------------------------------------------------------- 1 | # UploadType 2 | 3 | ::: maxapi.enums.upload_type 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/filters/command.md: -------------------------------------------------------------------------------- 1 | # Command Module 2 | 3 | ::: maxapi.filters.command 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/filters/handler.md: -------------------------------------------------------------------------------- 1 | # Handler Module 2 | 3 | ::: maxapi.filters.handler 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/add_admin_chat.md: -------------------------------------------------------------------------------- 1 | ::: maxapi.methods.add_admin_chat.AddAdminChat 2 | options: 3 | show_root_heading: true 4 | members_order: source 5 | -------------------------------------------------------------------------------- /docs/methods/change_info.md: -------------------------------------------------------------------------------- 1 | # ChangeInfo 2 | 3 | ::: maxapi.methods.change_info 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/delete_chat.md: -------------------------------------------------------------------------------- 1 | # DeleteChat 2 | 3 | ::: maxapi.methods.delete_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_message.md: -------------------------------------------------------------------------------- 1 | # GetMessage 2 | 3 | ::: maxapi.methods.get_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_updates.md: -------------------------------------------------------------------------------- 1 | # GetUpdates 2 | 3 | ::: maxapi.methods.get_updates 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/send_action.md: -------------------------------------------------------------------------------- 1 | # SendAction 2 | 3 | ::: maxapi.methods.send_action 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/input_media.md: -------------------------------------------------------------------------------- 1 | # InputMedia 2 | 3 | ::: maxapi.types.input_media 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/attachment.md: -------------------------------------------------------------------------------- 1 | # Attachment Module 2 | 3 | ::: maxapi.enums.attachment 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/sender_action.md: -------------------------------------------------------------------------------- 1 | # SenderAction 2 | 3 | ::: maxapi.enums.sender_action 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/filters/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware Module 2 | 3 | ::: maxapi.filters.middleware 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/edit_message.md: -------------------------------------------------------------------------------- 1 | # EditMessage 2 | 3 | ::: maxapi.methods.edit_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_messages.md: -------------------------------------------------------------------------------- 1 | # GetMessages 2 | 3 | ::: maxapi.methods.get_messages 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/update.md: -------------------------------------------------------------------------------- 1 | # Update Module 2 | 3 | ::: maxapi.types.updates.update 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /maxapi/__init__.py: -------------------------------------------------------------------------------- 1 | from .bot import Bot 2 | from .dispatcher import Dispatcher, Router 3 | from .filters import F 4 | 5 | __all__ = ["Bot", "Dispatcher", "F", "Router"] 6 | -------------------------------------------------------------------------------- /docs/context/state_machine.md: -------------------------------------------------------------------------------- 1 | # StateMachine 2 | 3 | ::: maxapi.context.state_machine 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/chat_permission.md: -------------------------------------------------------------------------------- 1 | # ChatPermission 2 | 3 | ::: maxapi.enums.chat_permission 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/delete_message.md: -------------------------------------------------------------------------------- 1 | # DeleteMessage 2 | 3 | ::: maxapi.methods.delete_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_upload_url.md: -------------------------------------------------------------------------------- 1 | # GetUploadURL 2 | 3 | ::: maxapi.methods.get_upload_url 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/send_callback.md: -------------------------------------------------------------------------------- 1 | # SendCallback 2 | 3 | ::: maxapi.methods.send_callback 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/subscribed.md: -------------------------------------------------------------------------------- 1 | # Subscribed 2 | 3 | ::: maxapi.methods.types.subscribed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/audio.md: -------------------------------------------------------------------------------- 1 | # Audio Module 2 | 3 | ::: maxapi.types.attachments.audio 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/file.md: -------------------------------------------------------------------------------- 1 | # File Module 2 | 3 | ::: maxapi.types.attachments.file 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/image.md: -------------------------------------------------------------------------------- 1 | # Image Module 2 | 3 | ::: maxapi.types.attachments.image 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/share.md: -------------------------------------------------------------------------------- 1 | # Share Module 2 | 3 | ::: maxapi.types.attachments.share 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/video.md: -------------------------------------------------------------------------------- 1 | # Video Module 2 | 3 | ::: maxapi.types.attachments.video 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/subscription.md: -------------------------------------------------------------------------------- 1 | # Subscription Module 2 | 3 | ::: maxapi.types.subscription 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/bot_added.md: -------------------------------------------------------------------------------- 1 | # BotAdded 2 | 3 | ::: maxapi.types.updates.bot_added 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/user_added.md: -------------------------------------------------------------------------------- 1 | # UserAdded 2 | 3 | ::: maxapi.types.updates.user_added 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/utils/inline_keyboard.md: -------------------------------------------------------------------------------- 1 | # InlineKeyboard 2 | 3 | ::: maxapi.utils.inline_keyboard 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/enums/message_link_type.md: -------------------------------------------------------------------------------- 1 | # MessageLinkType 2 | 3 | ::: maxapi.enums.message_link_type 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/filters/callback_payload.md: -------------------------------------------------------------------------------- 1 | # CallbackPayload 2 | 3 | ::: maxapi.filters.callback_payload 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/add_members_chat.md: -------------------------------------------------------------------------------- 1 | # AddMembersChat 2 | 3 | ::: maxapi.methods.add_members_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_chat_by_link.md: -------------------------------------------------------------------------------- 1 | # GetChatByLink 2 | 3 | ::: maxapi.methods.get_chat_by_link 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_me_from_chat.md: -------------------------------------------------------------------------------- 1 | # GetMeFromChat 2 | 3 | ::: maxapi.methods.get_me_from_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_members_chat.md: -------------------------------------------------------------------------------- 1 | # GetMembersChat 2 | 3 | ::: maxapi.methods.get_members_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/remove_admin.md: -------------------------------------------------------------------------------- 1 | # Remove_admin Module 2 | 3 | ::: maxapi.methods.remove_admin 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/deleted_chat.md: -------------------------------------------------------------------------------- 1 | # DeletedChat 2 | 3 | ::: maxapi.methods.types.deleted_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/upload.md: -------------------------------------------------------------------------------- 1 | # Upload Module 2 | 3 | ::: maxapi.types.attachments.upload 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/bot_removed.md: -------------------------------------------------------------------------------- 1 | # BotRemoved 2 | 3 | ::: maxapi.types.updates.bot_removed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/bot_started.md: -------------------------------------------------------------------------------- 1 | # BotStarted 2 | 3 | ::: maxapi.types.updates.bot_started 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/bot_stopped.md: -------------------------------------------------------------------------------- 1 | # BotStopped 2 | 3 | ::: maxapi.types.updates.bot_stopped 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/dialog_muted.md: -------------------------------------------------------------------------------- 1 | # DialogMuted 2 | 3 | ::: maxapi.types.updates.dialog_muted 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/user_removed.md: -------------------------------------------------------------------------------- 1 | # UserRemoved 2 | 3 | ::: maxapi.types.updates.user_removed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/delete_pin_message.md: -------------------------------------------------------------------------------- 1 | # DeletePinMessage 2 | 3 | ::: maxapi.methods.delete_pin_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_pinned_message.md: -------------------------------------------------------------------------------- 1 | # GetPinnedMessage 2 | 3 | ::: maxapi.methods.get_pinned_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_subscriptions.md: -------------------------------------------------------------------------------- 1 | # GetSubscriptions 2 | 3 | ::: maxapi.methods.get_subscriptions 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/subscribe_webhook.md: -------------------------------------------------------------------------------- 1 | # SubscribeWebhook 2 | 3 | ::: maxapi.methods.subscribe_webhook 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/removed_admin.md: -------------------------------------------------------------------------------- 1 | # RemovedAdmin 2 | 3 | ::: maxapi.methods.types.removed_admin 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/sended_action.md: -------------------------------------------------------------------------------- 1 | # SendedAction 2 | 3 | ::: maxapi.methods.types.sended_action 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/unsubscribed.md: -------------------------------------------------------------------------------- 1 | # Unsubscribed 2 | 3 | ::: maxapi.methods.types.unsubscribed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/contact.md: -------------------------------------------------------------------------------- 1 | # Contact Module 2 | 3 | ::: maxapi.types.attachments.contact 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/location.md: -------------------------------------------------------------------------------- 1 | # Location Module 2 | 3 | ::: maxapi.types.attachments.location 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/sticker.md: -------------------------------------------------------------------------------- 1 | # Sticker Module 2 | 3 | ::: maxapi.types.attachments.sticker 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /maxapi/context/__init__.py: -------------------------------------------------------------------------------- 1 | from ..context.state_machine import State, StatesGroup 2 | from .context import MemoryContext 3 | 4 | __all__ = ["State", "StatesGroup", "MemoryContext"] 5 | -------------------------------------------------------------------------------- /maxapi/loggers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logger_bot = logging.getLogger("bot") 4 | logger_connection = logging.getLogger("connection") 5 | logger_dp = logging.getLogger("dispatcher") 6 | -------------------------------------------------------------------------------- /docs/exceptions/invalid_token.md: -------------------------------------------------------------------------------- 1 | # Invalid Token Exception 2 | 3 | ::: maxapi.exceptions.InvalidToken 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/delete_bot_from_chat.md: -------------------------------------------------------------------------------- 1 | # DeleteBotFromChat 2 | 3 | ::: maxapi.methods.delete_bot_from_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/get_list_admin_chat.md: -------------------------------------------------------------------------------- 1 | # GetListAdminChat 2 | 3 | ::: maxapi.methods.get_list_admin_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/remove_member_chat.md: -------------------------------------------------------------------------------- 1 | сдел# RemoveMemberChat 2 | 3 | ::: maxapi.methods.remove_member_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/deleted_message.md: -------------------------------------------------------------------------------- 1 | # DeletedMessage 2 | 3 | ::: maxapi.methods.types.deleted_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/edited_message.md: -------------------------------------------------------------------------------- 1 | # EditedMessage 2 | 3 | ::: maxapi.methods.types.edited_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_updates.md: -------------------------------------------------------------------------------- 1 | # GettedUpdates 2 | 3 | ::: maxapi.methods.types.getted_updates 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/pinned_message.md: -------------------------------------------------------------------------------- 1 | # PinnedMessage 2 | 3 | ::: maxapi.methods.types.pinned_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/sended_callback.md: -------------------------------------------------------------------------------- 1 | # SendedCallback 2 | 3 | ::: maxapi.methods.types.sended_callback 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/sended_message.md: -------------------------------------------------------------------------------- 1 | # SendedMessage 2 | 3 | ::: maxapi.methods.types.sended_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/unsubscribe_webhook.md: -------------------------------------------------------------------------------- 1 | # UnsubscribeWebhook 2 | 3 | ::: maxapi.methods.unsubscribe_webhook 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/attachment.md: -------------------------------------------------------------------------------- 1 | # Attachment Module 2 | 3 | ::: maxapi.types.attachments.attachment 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/dialog_cleared.md: -------------------------------------------------------------------------------- 1 | # DialogCleared 2 | 3 | ::: maxapi.types.updates.dialog_cleared 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/dialog_removed.md: -------------------------------------------------------------------------------- 1 | # DialogRemoved 2 | 3 | ::: maxapi.types.updates.dialog_removed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/dialog_unmuted.md: -------------------------------------------------------------------------------- 1 | # DialogUnmuted 2 | 3 | ::: maxapi.types.updates.dialog_unmuted 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/message_created.md: -------------------------------------------------------------------------------- 1 | # MessageCreated 2 | 3 | ::: maxapi.types.updates.message_created 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/message_edited.md: -------------------------------------------------------------------------------- 1 | # MessageEdited 2 | 3 | ::: maxapi.types.updates.message_edited 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/message_removed.md: -------------------------------------------------------------------------------- 1 | # MessageRemoved 2 | 3 | ::: maxapi.types.updates.message_removed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/client/default.md: -------------------------------------------------------------------------------- 1 | # Default Connection Properties 2 | 3 | ::: maxapi.client.DefaultConnectionProperties 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/added_admin_chat.md: -------------------------------------------------------------------------------- 1 | # AddedAdminChat 2 | 3 | ::: maxapi.methods.types.added_admin_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_upload_url.md: -------------------------------------------------------------------------------- 1 | # GettedUploadURL 2 | 3 | ::: maxapi.methods.types.getted_upload_url 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/button.md: -------------------------------------------------------------------------------- 1 | # Button Module 2 | 3 | ::: maxapi.types.attachments.buttons.button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/message_callback.md: -------------------------------------------------------------------------------- 1 | # MessageCallback 2 | 3 | ::: maxapi.types.updates.message_callback 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/exceptions/download_file.md: -------------------------------------------------------------------------------- 1 | # Download File Exception 2 | 3 | ::: maxapi.exceptions.NotAvailableForDownload 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/added_members_chat.md: -------------------------------------------------------------------------------- 1 | # AddedMembersChat 2 | 3 | ::: maxapi.methods.types.added_members_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/deleted_pin_message.md: -------------------------------------------------------------------------------- 1 | # DeletedPinMessage 2 | 3 | ::: maxapi.methods.types.deleted_pin_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_members_chat.md: -------------------------------------------------------------------------------- 1 | # GettedMembersChat 2 | 3 | ::: maxapi.methods.types.getted_members_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/removed_member_chat.md: -------------------------------------------------------------------------------- 1 | # RemovedMemberChat 2 | 3 | ::: maxapi.methods.types.removed_member_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/chat_title_changed.md: -------------------------------------------------------------------------------- 1 | # ChatTitleChanged 2 | 3 | ::: maxapi.types.updates.chat_title_changed 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/deleted_bot_from_chat.md: -------------------------------------------------------------------------------- 1 | # DeletedBotFromChat 2 | 3 | ::: maxapi.methods.types.deleted_bot_from_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_subscriptions.md: -------------------------------------------------------------------------------- 1 | # GettedSubscriptions 2 | 3 | ::: maxapi.methods.types.getted_subscriptions 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/chat_button.md: -------------------------------------------------------------------------------- 1 | # ChatButton 2 | 3 | ::: maxapi.types.attachments.buttons.chat_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/link_button.md: -------------------------------------------------------------------------------- 1 | # LinkButton 2 | 3 | ::: maxapi.types.attachments.buttons.link_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/updates/message_chat_created.md: -------------------------------------------------------------------------------- 1 | # MessageChatCreated 2 | 3 | ::: maxapi.types.updates.message_chat_created 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_list_admin_chat.md: -------------------------------------------------------------------------------- 1 | # GettedListAdminChat 2 | 3 | ::: maxapi.methods.types.getted_list_admin_chat 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/methods/types/getted_pineed_message.md: -------------------------------------------------------------------------------- 1 | # GettedPinnedMessage 2 | 3 | ::: maxapi.methods.types.getted_pineed_message 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/message_button.md: -------------------------------------------------------------------------------- 1 | # MessageButton 2 | 3 | ::: maxapi.types.attachments.buttons.message_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/callback_button.md: -------------------------------------------------------------------------------- 1 | # CallbackButton 2 | 3 | ::: maxapi.types.attachments.buttons.callback_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/open_app_button.md: -------------------------------------------------------------------------------- 1 | # OpenAppButton 2 | 3 | ::: maxapi.types.attachments.buttons.open_app_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/request_contact.md: -------------------------------------------------------------------------------- 1 | # RequestContact 2 | 3 | ::: maxapi.types.attachments.buttons.request_contact 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_upload_url.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class GettedUploadUrl(BaseModel): 7 | url: str 8 | token: Optional[str] = None 9 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/attachment_button.md: -------------------------------------------------------------------------------- 1 | # AttachmentButton 2 | 3 | ::: maxapi.types.attachments.buttons.attachment_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /docs/types/attachments/buttons/request_geo_location_button.md: -------------------------------------------------------------------------------- 1 | # RequestGeoLocationButton 2 | 3 | ::: maxapi.types.attachments.buttons.request_geo_location_button 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | -------------------------------------------------------------------------------- /maxapi/enums/chat_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ChatType(str, Enum): 5 | """ 6 | Тип чата. 7 | 8 | Используется для различения личных и групповых чатов. 9 | """ 10 | 11 | DIALOG = "dialog" 12 | CHAT = "chat" 13 | CHANNEL = "channel" 14 | -------------------------------------------------------------------------------- /docs/exceptions/dispatcher.md: -------------------------------------------------------------------------------- 1 | # Dispatcher Exceptions 2 | 3 | ::: maxapi.exceptions 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | filters: 8 | - "!^_[^_]" 9 | - "^HandlerException$" 10 | - "^MiddlewareException$" 11 | -------------------------------------------------------------------------------- /maxapi/enums/message_link_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MessageLinkType(str, Enum): 5 | """ 6 | Тип связи между сообщениями. 7 | 8 | Используется для указания типа привязки: пересылка или ответ. 9 | """ 10 | 11 | FORWARD = "forward" 12 | REPLY = "reply" 13 | -------------------------------------------------------------------------------- /maxapi/enums/parse_mode.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ParseMode(str, Enum): 5 | """ 6 | Формат разметки текста сообщений. 7 | 8 | Используется для указания способа интерпретации стилей (жирный, курсив, ссылки и т.д.). 9 | """ 10 | 11 | MARKDOWN = "markdown" 12 | HTML = "html" 13 | -------------------------------------------------------------------------------- /maxapi/enums/intent.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Intent(str, Enum): 5 | """ 6 | Тип интента (намерения) кнопки. 7 | 8 | Используется для стилизации и логической классификации пользовательских действий. 9 | """ 10 | 11 | DEFAULT = "default" 12 | POSITIVE = "positive" 13 | NEGATIVE = "negative" 14 | -------------------------------------------------------------------------------- /maxapi/enums/upload_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class UploadType(str, Enum): 5 | """ 6 | Типы загружаемых файлов. 7 | 8 | Используются для указания категории контента при загрузке на сервер. 9 | """ 10 | 11 | IMAGE = "image" 12 | VIDEO = "video" 13 | AUDIO = "audio" 14 | FILE = "file" 15 | -------------------------------------------------------------------------------- /maxapi/enums/http_method.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class HTTPMethod(str, Enum): 5 | """ 6 | HTTP-методы, поддерживаемые клиентом API. 7 | 8 | Используются при выполнении запросов к серверу. 9 | """ 10 | 11 | POST = "POST" 12 | GET = "GET" 13 | PATCH = "PATCH" 14 | PUT = "PUT" 15 | DELETE = "DELETE" 16 | -------------------------------------------------------------------------------- /maxapi/methods/types/sended_message.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from ...types.message import Message 4 | 5 | 6 | class SendedMessage(BaseModel): 7 | """ 8 | Ответ API с отправленным сообщением. 9 | 10 | Attributes: 11 | message (Message): Объект отправленного сообщения. 12 | """ 13 | 14 | message: Message 15 | -------------------------------------------------------------------------------- /doc/todo.md: -------------------------------------------------------------------------------- 1 | # TODO for dev 2 | 3 | ## переход на UV 4 | 5 | - [ ] разобратся с зависимостями - как правильно реализовать совместимость `[dependency-groups]` и `[project.optional-dependencies]` 6 | - [ ] переёти на uv build 7 | 8 | ## автоматизация 9 | 10 | - [ ] написать автотесты - юнит/интеграционные/автолинтинг 11 | - [ ] перейти на автотесты и сборку в CI/CD 12 | -------------------------------------------------------------------------------- /docs/types/errors.md: -------------------------------------------------------------------------------- 1 | # Error Types 2 | 3 | Типы ошибок находятся в модуле [exceptions](../exceptions/max.md). 4 | 5 | См. также: 6 | - [Max Exceptions](../exceptions/max.md) - основные исключения API 7 | - [Dispatcher Exceptions](../exceptions/dispatcher.md) - исключения диспетчера 8 | - [Download File Exception](../exceptions/download_file.md) - исключения загрузки файлов 9 | -------------------------------------------------------------------------------- /docs/exceptions/max.md: -------------------------------------------------------------------------------- 1 | # Max Exceptions 2 | 3 | ::: maxapi.exceptions 4 | options: 5 | show_root_heading: false 6 | members_order: source 7 | filters: 8 | - "!^_[^_]" 9 | - "^InvalidToken$" 10 | - "^MaxConnection$" 11 | - "^MaxUploadFileFailed$" 12 | - "^MaxIconParamsException$" 13 | - "^MaxApiError$" 14 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/request_contact.py: -------------------------------------------------------------------------------- 1 | from ....enums.button_type import ButtonType 2 | from .button import Button 3 | 4 | 5 | class RequestContactButton(Button): 6 | """ 7 | Кнопка с контактом 8 | 9 | Attributes: 10 | text (str): Текст кнопки 11 | """ 12 | 13 | type: ButtonType = ButtonType.REQUEST_CONTACT 14 | text: str 15 | -------------------------------------------------------------------------------- /maxapi/types/attachments/contact.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class Contact(Attachment): 8 | """ 9 | Вложение с типом контакта. 10 | """ 11 | 12 | type: Literal[ 13 | AttachmentType.CONTACT 14 | ] # pyright: ignore[reportIncompatibleVariableOverride] 15 | -------------------------------------------------------------------------------- /maxapi/types/command.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class BotCommand(BaseModel): 7 | """ 8 | Модель команды бота для сериализации. 9 | 10 | Attributes: 11 | name (str): Название команды. 12 | description (Optional[str]): Описание команды. Может быть None. 13 | """ 14 | 15 | name: str 16 | description: Optional[str] = None 17 | -------------------------------------------------------------------------------- /docs/guides/installation.md: -------------------------------------------------------------------------------- 1 | # Установка 2 | 3 | ## Через pip 4 | 5 | ```bash 6 | pip install maxapi 7 | ``` 8 | 9 | ## С дополнительными зависимостями для Webhook 10 | 11 | ```bash 12 | pip install maxapi[webhook] 13 | ``` 14 | 15 | ## Из GitHub 16 | 17 | ```bash 18 | pip install git+https://github.com/love-apples/maxapi.git 19 | ``` 20 | 21 | ## Требования 22 | 23 | - Python 3.10+ 24 | - Токен бота MAX 25 | 26 | -------------------------------------------------------------------------------- /maxapi/enums/chat_status.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ChatStatus(str, Enum): 5 | """ 6 | Статус чата относительно пользователя или системы. 7 | 8 | Используется для отображения текущего состояния чата или определения доступных действий. 9 | """ 10 | 11 | ACTIVE = "active" 12 | REMOVED = "removed" 13 | LEFT = "left" 14 | CLOSED = "closed" 15 | SUSPENDED = "suspended" 16 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_subscriptions.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pydantic import BaseModel 4 | 5 | from ...types.subscription import Subscription 6 | 7 | 8 | class GettedSubscriptions(BaseModel): 9 | """ 10 | Ответ API с отправленным сообщением. 11 | 12 | Attributes: 13 | message (Message): Объект отправленного сообщения. 14 | """ 15 | 16 | subscriptions: List[Subscription] 17 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/message_button.py: -------------------------------------------------------------------------------- 1 | from ....enums.button_type import ButtonType 2 | from .button import Button 3 | 4 | 5 | class MessageButton(Button): 6 | """ 7 | Кнопка для отправки текста 8 | 9 | Attributes: 10 | type: Тип кнопки (определяет её поведение и функционал) 11 | text: Отправляемый текст 12 | """ 13 | 14 | type: ButtonType = ButtonType.MESSAGE 15 | text: str 16 | -------------------------------------------------------------------------------- /maxapi/methods/types/deleted_chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class DeletedChat(BaseModel): 7 | """ 8 | Ответ API при удалении чата (?). 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_pineed_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from ...types.message import Message 6 | 7 | 8 | class GettedPin(BaseModel): 9 | """ 10 | Ответ API с информацией о закреплённом сообщении. 11 | 12 | Attributes: 13 | message (Optional[Message]): Закреплённое сообщение, если оно есть. 14 | """ 15 | 16 | message: Optional[Message] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/deleted_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class DeletedMessage(BaseModel): 7 | """ 8 | Ответ API при удалении сообщения. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/edited_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class EditedMessage(BaseModel): 7 | """ 8 | Ответ API при изменении сообщения. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/subscribed.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Subscribed(BaseModel): 7 | """ 8 | Результат подписки на обновления на Webhook 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/unsubscribed.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Unsubscribed(BaseModel): 7 | """ 8 | Результат отписки от обновлений на Webhook 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/link_button.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ....enums.button_type import ButtonType 4 | from .button import Button 5 | 6 | 7 | class LinkButton(Button): 8 | """ 9 | Кнопка с внешней ссылкой. 10 | 11 | Attributes: 12 | url (Optional[str]): Ссылка для перехода (должна содержать http/https) 13 | """ 14 | 15 | type: ButtonType = ButtonType.LINK 16 | url: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/deleted_bot_from_chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class DeletedBotFromChat(BaseModel): 7 | """ 8 | Ответ API при удалении бота из чата. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/pinned_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class PinnedMessage(BaseModel): 7 | """ 8 | Ответ API при добавлении списка администраторов в чат. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/sended_action.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class SendedAction(BaseModel): 7 | """ 8 | Ответ API после выполнения действия. 9 | 10 | Attributes: 11 | success (bool): Статус успешности выполнения операции. 12 | message (Optional[str]): Дополнительное сообщение или описание ошибки. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | 3 | 4 | .PHONY: run-test 5 | run-test: 6 | @echo "Running linters and tests in parallel (uv run)..." 7 | @status=0; \ 8 | uv run -- ruff check . & p1=$$!; \ 9 | uv run -- black --check . & p2=$$!; \ 10 | uv run -- isort --check-only . & p3=$$!; \ 11 | uv run -- mypy maxapi & p4=$$!; \ 12 | uv run -- pytest -q & p5=$$!; \ 13 | for p in $$p1 $$p2 $$p3 $$p4 $$p5; do \ 14 | wait $$p || status=1; \ 15 | done; \ 16 | exit $$status 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/added_admin_chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class AddedListAdminChat(BaseModel): 7 | """ 8 | Ответ API при добавлении списка администраторов в чат. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/added_members_chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class AddedMembersChat(BaseModel): 7 | """ 8 | Ответ API при добавлении списка пользователей в чат. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/removed_admin.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class RemovedAdmin(BaseModel): 7 | """ 8 | Ответ API при отмене прав администратора у пользователя в чате 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/methods/types/removed_member_chat.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class RemovedMemberChat(BaseModel): 7 | """ 8 | Ответ API при удалении участника из чата. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или описание ошибки. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/enums/button_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ButtonType(str, Enum): 5 | """ 6 | Типы кнопок, доступных в интерфейсе бота. 7 | 8 | Определяют поведение при нажатии на кнопку в сообщении. 9 | """ 10 | 11 | REQUEST_CONTACT = "request_contact" 12 | CALLBACK = "callback" 13 | LINK = "link" 14 | REQUEST_GEO_LOCATION = "request_geo_location" 15 | CHAT = "chat" 16 | MESSAGE = "message" 17 | OPEN_APP = "open_app" 18 | -------------------------------------------------------------------------------- /maxapi/methods/types/deleted_pin_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class DeletedPinMessage(BaseModel): 7 | """ 8 | Ответ API при удалении закрепленного в чате сообщения. 9 | 10 | Attributes: 11 | success (bool): Статус успешности операции. 12 | message (Optional[str]): Дополнительное сообщение или ошибка. 13 | """ 14 | 15 | success: bool 16 | message: Optional[str] = None 17 | -------------------------------------------------------------------------------- /maxapi/exceptions/max.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | class InvalidToken(Exception): ... 5 | 6 | 7 | class MaxConnection(Exception): ... 8 | 9 | 10 | class MaxUploadFileFailed(Exception): ... 11 | 12 | 13 | class MaxIconParamsException(Exception): ... 14 | 15 | 16 | @dataclass(slots=True) 17 | class MaxApiError(Exception): 18 | code: int 19 | raw: str 20 | 21 | def __str__(self) -> str: 22 | return f"Ошибка от API: {self.code=} {self.raw=}" 23 | -------------------------------------------------------------------------------- /maxapi/types/subscription.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Subscription(BaseModel): 7 | """ 8 | Подписка для вебхука 9 | 10 | Attributes: 11 | url (str): URL вебхука 12 | time (int): Unix-время, когда была создана подписка 13 | update_types (List[str]): Типы обновлений, на которые подписан бот 14 | """ 15 | 16 | url: str 17 | time: int 18 | update_types: Optional[List[str]] = None 19 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/button.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from ....enums.button_type import ButtonType 4 | 5 | 6 | class Button(BaseModel): 7 | """ 8 | Базовая модель кнопки для сообщений. 9 | 10 | Attributes: 11 | type: Тип кнопки (определяет её поведение и функционал) 12 | text: Текст, отображаемый на кнопке (1-64 символа) 13 | """ 14 | 15 | type: ButtonType 16 | text: str 17 | 18 | class Config: 19 | use_enum_values = True 20 | -------------------------------------------------------------------------------- /maxapi/enums/attachment.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class AttachmentType(str, Enum): 5 | """ 6 | Типы вложений, поддерживаемые в сообщениях. 7 | 8 | Используется для указания типа содержимого при отправке или обработке вложений. 9 | """ 10 | 11 | IMAGE = "image" 12 | VIDEO = "video" 13 | AUDIO = "audio" 14 | FILE = "file" 15 | STICKER = "sticker" 16 | CONTACT = "contact" 17 | INLINE_KEYBOARD = "inline_keyboard" 18 | LOCATION = "location" 19 | SHARE = "share" 20 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/attachment_button.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | 3 | from ....enums.attachment import AttachmentType 4 | from ..attachment import Attachment 5 | 6 | 7 | class AttachmentButton(Attachment): 8 | """ 9 | Модель кнопки вложения для сообщения. 10 | 11 | Attributes: 12 | type: Тип кнопки, фиксированное значение 'inline_keyboard' 13 | payload: Полезная нагрузка кнопки (массив рядов кнопок) 14 | """ 15 | 16 | type: Literal[AttachmentType.INLINE_KEYBOARD] 17 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/request_geo_location_button.py: -------------------------------------------------------------------------------- 1 | from ....enums.button_type import ButtonType 2 | from .button import Button 3 | 4 | 5 | class RequestGeoLocationButton(Button): 6 | """ 7 | Кнопка запроса геолокации пользователя. 8 | 9 | Attributes: 10 | quick: Если True, запрашивает геолокацию без дополнительного 11 | подтверждения пользователя (по умолчанию False) 12 | """ 13 | 14 | type: ButtonType = ButtonType.REQUEST_GEO_LOCATION 15 | quick: bool = False 16 | -------------------------------------------------------------------------------- /maxapi/enums/sender_action.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class SenderAction(str, Enum): 5 | """ 6 | Действия отправителя, отображаемые получателю в интерфейсе. 7 | 8 | Используются для имитации активности (например, "печатает...") перед отправкой сообщения или медиа. 9 | """ 10 | 11 | TYPING_ON = "typing_on" 12 | SENDING_PHOTO = "sending_photo" 13 | SENDING_VIDEO = "sending_video" 14 | SENDING_AUDIO = "sending_audio" 15 | SENDING_FILE = "sending_file" 16 | MARK_SEEN = "mark_seen" 17 | -------------------------------------------------------------------------------- /maxapi/types/attachments/audio.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class Audio(Attachment): 8 | """ 9 | Вложение с типом аудио. 10 | 11 | Attributes: 12 | transcription (Optional[str]): Транскрипция аудио (если есть). 13 | """ 14 | 15 | type: Literal[ 16 | AttachmentType.AUDIO 17 | ] # pyright: ignore[reportIncompatibleVariableOverride] 18 | transcription: Optional[str] = None 19 | -------------------------------------------------------------------------------- /maxapi/enums/text_style.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TextStyle(Enum): 5 | """ 6 | Стили текста, применяемые в сообщениях. 7 | 8 | Используются для форматирования и выделения частей текста в сообщении. 9 | """ 10 | 11 | UNDERLINE = "underline" 12 | STRONG = "strong" 13 | EMPHASIZED = "emphasized" 14 | MONOSPACED = "monospaced" 15 | LINK = "link" 16 | STRIKETHROUGH = "strikethrough" 17 | USER_MENTION = "user_mention" 18 | HEADING = "heading" 19 | HIGHLIGHTED = "highlighted" 20 | -------------------------------------------------------------------------------- /maxapi/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | from .dispatcher import HandlerException, MiddlewareException 2 | from .download_file import NotAvailableForDownload 3 | from .max import ( 4 | InvalidToken, 5 | MaxApiError, 6 | MaxConnection, 7 | MaxIconParamsException, 8 | MaxUploadFileFailed, 9 | ) 10 | 11 | __all__ = [ 12 | "HandlerException", 13 | "MiddlewareException", 14 | "InvalidToken", 15 | "MaxConnection", 16 | "MaxUploadFileFailed", 17 | "MaxIconParamsException", 18 | "MaxApiError", 19 | "NotAvailableForDownload", 20 | ] 21 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_members_chat.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from ...types.chats import ChatMember 6 | 7 | 8 | class GettedMembersChat(BaseModel): 9 | """ 10 | Ответ API с полученным списком участников чата. 11 | 12 | Attributes: 13 | members (List[ChatMember]): Список участников с правами администратора. 14 | marker (Optional[int]): Маркер для постраничной навигации (если есть). 15 | """ 16 | 17 | members: List[ChatMember] 18 | marker: Optional[int] = None 19 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_list_admin_chat.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from ...types.chats import ChatMember 6 | 7 | 8 | class GettedListAdminChat(BaseModel): 9 | """ 10 | Ответ API с полученным списком администраторов чата. 11 | 12 | Attributes: 13 | members (List[ChatMember]): Список участников с правами администратора. 14 | marker (Optional[int]): Маркер для постраничной навигации (если есть). 15 | """ 16 | 17 | members: List[ChatMember] 18 | marker: Optional[int] = None 19 | -------------------------------------------------------------------------------- /maxapi/filters/filter.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from ..types.updates import UpdateUnion 7 | 8 | 9 | class BaseFilter: 10 | """ 11 | Базовый класс для фильтров. 12 | 13 | Определяет интерфейс фильтрации событий. 14 | Потомки должны переопределять метод __call__. 15 | 16 | Methods: 17 | __call__(event): Асинхронная проверка события на соответствие фильтру. 18 | """ 19 | 20 | async def __call__(self, event: UpdateUnion) -> bool | dict: 21 | return True 22 | -------------------------------------------------------------------------------- /maxapi/types/attachments/file.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class File(Attachment): 8 | """ 9 | Вложение с типом файла. 10 | 11 | Attributes: 12 | filename (Optional[str]): Имя файла. 13 | size (Optional[int]): Размер файла в байтах. 14 | """ 15 | 16 | type: Literal[ 17 | AttachmentType.FILE 18 | ] # pyright: ignore[reportIncompatibleVariableOverride] 19 | filename: Optional[str] = None 20 | size: Optional[int] = None 21 | -------------------------------------------------------------------------------- /maxapi/enums/api_path.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ApiPath(str, Enum): 5 | """ 6 | Перечисление всех доступных API-эндпоинтов. 7 | 8 | Используется для унифицированного указания путей при отправке запросов. 9 | """ 10 | 11 | ME = "/me" 12 | CHATS = "/chats" 13 | MESSAGES = "/messages" 14 | UPDATES = "/updates" 15 | VIDEOS = "/videos" 16 | ANSWERS = "/answers" 17 | ACTIONS = "/actions" 18 | PIN = "/pin" 19 | MEMBERS = "/members" 20 | ADMINS = "/admins" 21 | UPLOADS = "/uploads" 22 | SUBSCRIPTIONS = "/subscriptions" 23 | -------------------------------------------------------------------------------- /maxapi/types/attachments/location.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class Location(Attachment): 8 | """ 9 | Вложение с типом геолокации. 10 | 11 | Attributes: 12 | latitude (Optional[float]): Широта. 13 | longitude (Optional[float]): Долгота. 14 | """ 15 | 16 | type: Literal[ 17 | AttachmentType.LOCATION 18 | ] # pyright: ignore[reportIncompatibleVariableOverride] 19 | latitude: Optional[float] = None 20 | longitude: Optional[float] = None 21 | -------------------------------------------------------------------------------- /maxapi/types/attachments/sticker.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class Sticker(Attachment): 8 | """ 9 | Вложение с типом стикера. 10 | 11 | Attributes: 12 | width (Optional[int]): Ширина стикера в пикселях. 13 | height (Optional[int]): Высота стикера в пикселях. 14 | """ 15 | 16 | type: Literal[ 17 | AttachmentType.STICKER 18 | ] # pyright: ignore[reportIncompatibleVariableOverride] 19 | width: Optional[int] = None 20 | height: Optional[int] = None 21 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from .callback_button import CallbackButton 4 | from .chat_button import ChatButton 5 | from .link_button import LinkButton 6 | from .message_button import MessageButton 7 | from .open_app_button import OpenAppButton 8 | from .request_contact import RequestContactButton 9 | from .request_geo_location_button import RequestGeoLocationButton 10 | 11 | InlineButtonUnion = Union[ 12 | CallbackButton, 13 | ChatButton, 14 | LinkButton, 15 | RequestContactButton, 16 | RequestGeoLocationButton, 17 | MessageButton, 18 | OpenAppButton, 19 | ] 20 | -------------------------------------------------------------------------------- /maxapi/types/callback.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from ..types.users import User 6 | 7 | 8 | class Callback(BaseModel): 9 | """ 10 | Модель callback-запроса. 11 | 12 | Attributes: 13 | timestamp (int): Временная метка callback. 14 | callback_id (str): Уникальный идентификатор callback. 15 | payload (Optional[str]): Дополнительные данные callback. Может быть None. 16 | user (User): Объект пользователя, инициировавшего callback. 17 | """ 18 | 19 | timestamp: int 20 | callback_id: str 21 | payload: Optional[str] = None 22 | user: User 23 | -------------------------------------------------------------------------------- /maxapi/enums/chat_permission.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ChatPermission(str, Enum): 5 | """ 6 | Права доступа пользователя в чате. 7 | 8 | Используются для управления разрешениями при добавлении участников или изменении настроек чата. 9 | """ 10 | 11 | READ_ALL_MESSAGES = "read_all_messages" 12 | ADD_REMOVE_MEMBERS = "add_remove_members" 13 | ADD_ADMINS = "add_admins" 14 | CHANGE_CHAT_INFO = "change_chat_info" 15 | PIN_MESSAGE = "pin_message" 16 | WRITE = "write" 17 | CAN_CALL = "can_call" 18 | EDIT_LINK = "edit_link" 19 | EDIT = "edit" 20 | DELETE = "delete" 21 | VIEW_STATS = "view_stats" 22 | -------------------------------------------------------------------------------- /maxapi/types/bot_mixin.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any 2 | 3 | if TYPE_CHECKING: 4 | from ..bot import Bot 5 | 6 | 7 | class BotMixin: 8 | """Миксин для проверки инициализации bot.""" 9 | 10 | bot: Any 11 | 12 | def _ensure_bot(self) -> "Bot": 13 | """ 14 | Проверяет, что bot инициализирован, и возвращает его. 15 | 16 | Returns: 17 | Bot: Объект бота. 18 | 19 | Raises: 20 | RuntimeError: Если bot не инициализирован. 21 | """ 22 | 23 | if self.bot is None: 24 | raise RuntimeError("Bot не инициализирован") 25 | 26 | return self.bot # type: ignore 27 | -------------------------------------------------------------------------------- /maxapi/types/attachments/share.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from ...enums.attachment import AttachmentType 4 | from .attachment import Attachment 5 | 6 | 7 | class Share(Attachment): 8 | """ 9 | Вложение с типом "share" (поделиться). 10 | 11 | Attributes: 12 | title (Optional[str]): Заголовок для шаринга. 13 | description (Optional[str]): Описание. 14 | image_url (Optional[str]): URL изображения для предпросмотра. 15 | """ 16 | 17 | type: Literal[ 18 | AttachmentType.SHARE 19 | ] # pyright: ignore[reportIncompatibleVariableOverride] 20 | title: Optional[str] = None 21 | description: Optional[str] = None 22 | image_url: Optional[str] = None 23 | -------------------------------------------------------------------------------- /maxapi/types/attachments/upload.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | from ...enums.upload_type import UploadType 4 | 5 | 6 | class AttachmentPayload(BaseModel): 7 | """ 8 | Полезная нагрузка вложения с токеном. 9 | 10 | Attributes: 11 | token (str): Токен для доступа или идентификации вложения. 12 | """ 13 | 14 | token: str 15 | 16 | 17 | class AttachmentUpload(BaseModel): 18 | """ 19 | Вложение с полезной нагрузкой для загрузки на сервера MAX. 20 | 21 | Attributes: 22 | type (UploadType): Тип вложения (например, image, video, audio). 23 | payload (AttachmentPayload): Полезная нагрузка с токеном. 24 | """ 25 | 26 | type: UploadType 27 | payload: AttachmentPayload 28 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/chat_button.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ....enums.button_type import ButtonType 4 | from .button import Button 5 | 6 | 7 | class ChatButton(Button): 8 | """ 9 | Attributes: 10 | text: Текст кнопки (наследуется от Button) 11 | chat_title: Название чата (до 128 символов) 12 | chat_description: Описание чата (до 256 символов) 13 | start_payload: Данные, передаваемые при старте чата (до 512 символов) 14 | uuid: Уникальный идентификатор чата 15 | """ 16 | 17 | type: ButtonType = ButtonType.CHAT 18 | chat_title: str 19 | chat_description: Optional[str] = None 20 | start_payload: Optional[str] = None 21 | uuid: Optional[int] = None 22 | -------------------------------------------------------------------------------- /maxapi/types/updates/message_chat_created.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ...types.chats import Chat 4 | from .update import Update 5 | 6 | 7 | class MessageChatCreated(Update): 8 | """ 9 | Событие создания чата. 10 | 11 | Attributes: 12 | chat (Chat): Объект чата. 13 | title (Optional[str]): Название чата. 14 | message_id (Optional[str]): ID сообщения. 15 | start_payload (Optional[str]): Payload для старта. 16 | """ 17 | 18 | chat: Chat # type: ignore[assignment] 19 | title: Optional[str] = None 20 | message_id: Optional[str] = None 21 | start_payload: Optional[str] = None 22 | 23 | def get_ids(self): 24 | return (self.chat.chat_id, self.chat.owner_id) 25 | -------------------------------------------------------------------------------- /maxapi/filters/__init__.py: -------------------------------------------------------------------------------- 1 | from magic_filter import MagicFilter 2 | 3 | from .filter import BaseFilter 4 | 5 | F = MagicFilter() 6 | 7 | __all__ = ["BaseFilter"] 8 | 9 | 10 | def filter_attrs(obj: object, *filters: MagicFilter) -> bool: 11 | """ 12 | Применяет один или несколько фильтров MagicFilter к объекту. 13 | 14 | Args: 15 | obj (object): Объект, к которому применяются фильтры (например, event или message). 16 | *filters (MagicFilter): Один или несколько выражений MagicFilter. 17 | 18 | Returns: 19 | bool: True, если все фильтры возвращают True, иначе False. 20 | """ 21 | 22 | try: 23 | return all(f.resolve(obj) for f in filters) 24 | except Exception: 25 | return False 26 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/callback_button.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ....enums.button_type import ButtonType 4 | from ....enums.intent import Intent 5 | from .button import Button 6 | 7 | 8 | class CallbackButton(Button): 9 | """ 10 | Кнопка с callback-действием. 11 | 12 | Attributes: 13 | type: Тип кнопки (фиксированное значение ButtonType.CALLBACK) 14 | text: Текст, отображаемый на кнопке (наследуется от Button) 15 | payload: Дополнительные данные (до 256 символов), передаваемые при нажатии 16 | intent: Намерение кнопки (визуальный стиль и поведение) 17 | """ 18 | 19 | type: ButtonType = ButtonType.CALLBACK 20 | payload: Optional[str] = None 21 | intent: Intent = Intent.DEFAULT 22 | -------------------------------------------------------------------------------- /maxapi/types/updates/message_edited.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple 2 | 3 | from ...types.message import Message 4 | from .update import Update 5 | 6 | 7 | class MessageEdited(Update): 8 | """ 9 | Обновление, сигнализирующее об изменении сообщения. 10 | 11 | Attributes: 12 | message (Message): Объект измененного сообщения. 13 | """ 14 | 15 | message: Message 16 | 17 | def get_ids(self) -> Tuple[Optional[int], Optional[int]]: 18 | """ 19 | Возвращает кортеж идентификаторов (chat_id, user_id). 20 | 21 | Returns: 22 | Tuple[Optional[int], Optional[int]]: Идентификаторы чата и пользователя. 23 | """ 24 | 25 | return (self.message.recipient.chat_id, self.message.recipient.user_id) 26 | -------------------------------------------------------------------------------- /maxapi/types/attachments/buttons/open_app_button.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ....enums.button_type import ButtonType 4 | from .button import Button 5 | 6 | 7 | class OpenAppButton(Button): 8 | """ 9 | Кнопка для открытия приложения 10 | 11 | Attributes: 12 | text: Видимый текст кнопки 13 | web_app: Публичное имя (username) бота или ссылка на него, чьё мини-приложение надо запустить 14 | contact_id: Идентификатор бота, чьё мини-приложение надо запустить 15 | payload: Параметр запуска, который будет передан в initData мини-приложения 16 | """ 17 | 18 | type: ButtonType = ButtonType.OPEN_APP 19 | text: str 20 | web_app: Optional[str] = None 21 | contact_id: Optional[int] = None 22 | payload: Optional[str] = None 23 | -------------------------------------------------------------------------------- /maxapi/types/updates/chat_title_changed.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class ChatTitleChanged(Update): 11 | """ 12 | Обновление, сигнализирующее об изменении названия чата. 13 | 14 | Attributes: 15 | chat_id (Optional[int]): Идентификатор чата. 16 | user (User): Пользователь, совершивший изменение. 17 | title (str): Новое название чата. 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | title: str 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint with ruff 2 | 3 | on: 4 | push: 5 | branches: [main, develop] 6 | pull_request: 7 | branches: [main, develop] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ruff: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Install uv 19 | uses: astral-sh/setup-uv@v4 20 | with: 21 | version: "latest" 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.11" 27 | 28 | - name: Install dependencies 29 | run: | 30 | uv sync --all-groups 31 | 32 | - name: Run ruff check 33 | run: | 34 | uv run ruff check . 35 | 36 | -------------------------------------------------------------------------------- /maxapi/types/updates/dialog_cleared.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class DialogCleared(Update): 11 | """ 12 | Обновление, сигнализирующее об очистке диалога с ботом. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата. 16 | user (User): Пользователь (бот). 17 | user_locale (Optional[str]): Локаль пользователя. 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | user_locale: Optional[str] = None 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /maxapi/types/updates/dialog_removed.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class DialogRemoved(Update): 11 | """ 12 | Обновление, сигнализирующее об удалении диалога с ботом. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата. 16 | user (User): Пользователь (бот). 17 | user_locale (Optional[str]): Локаль пользователя. 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | user_locale: Optional[str] = None 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /maxapi/types/updates/dialog_unmuted.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class DialogUnmuted(Update): 11 | """ 12 | Обновление, сигнализирующее о включении оповещений от бота. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата. 16 | user (User): Пользователь (бот). 17 | user_locale (Optional[str]): Локаль пользователя. 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | user_locale: Optional[str] = None 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /maxapi/types/updates/bot_added.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class BotAdded(Update): 11 | """ 12 | Обновление, сигнализирующее о добавлении бота в чат. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата, куда добавлен бот. 16 | user (User): Объект пользователя-бота. 17 | is_channel (bool): Указывает, был ли бот добавлен в канал или нет 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | is_channel: bool 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /maxapi/types/attachments/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated, Union 2 | 3 | from pydantic import Field 4 | 5 | from ..attachments.audio import Audio 6 | from ..attachments.buttons.attachment_button import AttachmentButton 7 | from ..attachments.contact import Contact 8 | from ..attachments.file import File 9 | from ..attachments.image import Image 10 | from ..attachments.location import Location 11 | from ..attachments.share import Share 12 | from ..attachments.sticker import Sticker 13 | from ..attachments.video import Video 14 | 15 | Attachments = Annotated[ 16 | Union[ 17 | Audio, 18 | Video, 19 | File, 20 | Image, 21 | Sticker, 22 | Share, 23 | Location, 24 | AttachmentButton, 25 | Contact, 26 | ], 27 | Field(discriminator="type"), 28 | ] 29 | -------------------------------------------------------------------------------- /maxapi/types/updates/bot_removed.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class BotRemoved(Update): 11 | """ 12 | Обновление, сигнализирующее об удалении бота из чата. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата, из которого удалён бот. 16 | user (User): Объект пользователя-бота. 17 | is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет 18 | """ 19 | 20 | chat_id: int 21 | user: User 22 | is_channel: bool 23 | 24 | if TYPE_CHECKING: 25 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 26 | 27 | def get_ids(self): 28 | return (self.chat_id, self.user.user_id) 29 | -------------------------------------------------------------------------------- /.github/workflows/mypy.yml: -------------------------------------------------------------------------------- 1 | name: Type check with mypy 2 | 3 | on: 4 | push: 5 | branches: [main, develop] 6 | pull_request: 7 | branches: [main, develop] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | mypy: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Install uv 19 | uses: astral-sh/setup-uv@v4 20 | with: 21 | version: "latest" 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v5 25 | with: 26 | python-version: "3.11" 27 | 28 | - name: Install dependencies 29 | run: | 30 | uv sync --all-groups 31 | 32 | - name: Run mypy 33 | run: | 34 | uv run mypy maxapi 35 | continue-on-error: true 36 | 37 | -------------------------------------------------------------------------------- /maxapi/filters/middleware.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Awaitable, Callable 2 | 3 | 4 | class BaseMiddleware: 5 | """ 6 | Базовый класс для мидлварей. 7 | 8 | Используется для обработки события до и после вызова хендлера. 9 | """ 10 | 11 | async def __call__( 12 | self, 13 | handler: Callable[[Any, dict[str, Any]], Awaitable[Any]], 14 | event_object: Any, 15 | data: dict[str, Any], 16 | ) -> Any: 17 | """ 18 | Вызывает хендлер с переданным событием и данными. 19 | 20 | Args: 21 | handler (Callable): Хендлер события. 22 | event_object (Any): Событие. 23 | data (dict): Дополнительные данные. 24 | 25 | Returns: 26 | Any: Результат работы хендлера. 27 | """ 28 | 29 | return await handler(event_object, data) 30 | -------------------------------------------------------------------------------- /maxapi/methods/types/sended_callback.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | if TYPE_CHECKING: 6 | from ...bot import Bot 7 | 8 | 9 | class SendedCallback(BaseModel): 10 | """ 11 | Ответ API после выполнения callback-действия. 12 | 13 | Attributes: 14 | success (bool): Статус успешности выполнения callback. 15 | message (Optional[str]): Дополнительное сообщение или описание ошибки. 16 | bot (Optional[Bot]): Внутреннее поле для хранения ссылки на экземпляр бота (не сериализуется). 17 | """ 18 | 19 | success: bool 20 | message: Optional[str] = None 21 | bot: Optional[Any] = Field( 22 | default=None, exclude=True 23 | ) # pyright: ignore[reportRedeclaration] 24 | 25 | if TYPE_CHECKING: 26 | bot: Optional[Bot] # type: ignore 27 | -------------------------------------------------------------------------------- /tests/check_env.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Скрипт для проверки загрузки переменных окружения.""" 3 | 4 | import os 5 | import sys 6 | from pathlib import Path 7 | 8 | # Добавляем путь к conftest 9 | sys.path.insert(0, str(Path(__file__).parent.parent)) 10 | 11 | # Имитируем загрузку как в conftest.py 12 | try: 13 | from dotenv import load_dotenv 14 | 15 | project_root = Path(__file__).parent.parent 16 | env_file = project_root / ".env" 17 | tests_env = Path(__file__).parent / ".env" 18 | 19 | if env_file.exists(): 20 | load_dotenv(env_file, override=True) 21 | elif tests_env.exists(): 22 | load_dotenv(tests_env, override=True) 23 | else: 24 | load_dotenv(override=True) 25 | 26 | token = os.getenv("MAX_BOT_TOKEN") 27 | 28 | if not token: 29 | sys.exit(1) 30 | 31 | except ImportError: 32 | sys.exit(1) 33 | -------------------------------------------------------------------------------- /maxapi/types/updates/bot_stopped.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class BotStopped(Update): 11 | """ 12 | Обновление, сигнализирующее об остановке бота. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата. 16 | user (User): Пользователь (бот). 17 | user_locale (Optional[str]): Локаль пользователя. 18 | payload (Optional[str]): Дополнительные данные. 19 | """ 20 | 21 | chat_id: int 22 | user: User 23 | user_locale: Optional[str] = None 24 | payload: Optional[str] = None 25 | 26 | if TYPE_CHECKING: 27 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 28 | 29 | def get_ids(self): 30 | return (self.chat_id, self.user.user_id) 31 | -------------------------------------------------------------------------------- /maxapi/types/updates/bot_started.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | if TYPE_CHECKING: 7 | from ...bot import Bot 8 | 9 | 10 | class BotStarted(Update): 11 | """ 12 | Обновление, сигнализирующее о первом старте бота. 13 | 14 | Attributes: 15 | chat_id (int): Идентификатор чата. 16 | user (User): Пользователь (бот). 17 | user_locale (Optional[str]): Локаль пользователя. 18 | payload (Optional[str]): Дополнительные данные. 19 | """ 20 | 21 | chat_id: int 22 | user: User 23 | user_locale: Optional[str] = None 24 | payload: Optional[str] = None 25 | 26 | if TYPE_CHECKING: 27 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 28 | 29 | def get_ids(self): 30 | return (self.chat_id, self.user.user_id) 31 | -------------------------------------------------------------------------------- /maxapi/types/updates/message_created.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Optional, Tuple 4 | 5 | from ...types.message import Message 6 | from .update import Update 7 | 8 | 9 | class MessageCreated(Update): 10 | """ 11 | Обновление, сигнализирующее о создании нового сообщения. 12 | 13 | Attributes: 14 | message (Message): Объект сообщения. 15 | user_locale (Optional[str]): Локаль пользователя. 16 | """ 17 | 18 | message: Message 19 | user_locale: Optional[str] = None 20 | 21 | def get_ids(self) -> Tuple[Optional[int], int]: 22 | """ 23 | Возвращает кортеж идентификаторов (chat_id, user_id). 24 | 25 | Returns: 26 | tuple[Optional[int], int]: Идентификатор чата и пользователя. 27 | """ 28 | 29 | return (self.message.recipient.chat_id, self.message.sender.user_id) 30 | -------------------------------------------------------------------------------- /maxapi/types/updates/message_removed.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple 2 | 3 | from .update import Update 4 | 5 | 6 | class MessageRemoved(Update): 7 | """ 8 | Класс для обработки события удаления сообщения в чате. 9 | 10 | Attributes: 11 | message_id (str): Идентификатор удаленного сообщения. Может быть None. 12 | chat_id (int): Идентификатор чата. Может быть None. 13 | user_id (int): Идентификатор пользователя. Может быть None. 14 | """ 15 | 16 | message_id: str 17 | chat_id: int 18 | user_id: int 19 | 20 | def get_ids(self) -> Tuple[Optional[int], Optional[int]]: 21 | """ 22 | Возвращает кортеж идентификаторов (chat_id, user_id). 23 | 24 | Returns: 25 | Tuple[Optional[int], Optional[int]]: Идентификаторы чата и пользователя. 26 | """ 27 | 28 | return (self.chat_id, self.user_id) 29 | -------------------------------------------------------------------------------- /doc/dev.md: -------------------------------------------------------------------------------- 1 | # Описание процесса разработки 2 | 3 | ## Базовые вещи 4 | 5 | - используем инструмент [uv](https://docs.astral.sh/uv/) для создания рабочего окружения 6 | - версия python **3.10** 7 | 8 | ## Настройка среды для локальной разработки 9 | 10 | ### 1. Устанавливаем UV 11 | 12 | Установка uv [тут](https://docs.astral.sh/uv/getting-started/installation/#installing-uv) 13 | 14 | ### 2. Cоздаем виртуальное окружение и устанавливаем зависимости 15 | 16 | Для создания виртуального окружения и установки зависимостей, выполните следующие команды: 17 | 18 | ```bash 19 | uv sync --all-groups 20 | ``` 21 | 22 | После выполнения данной команды появится `.venv` папка 23 | 24 | ```bash 25 | source .venv/bin/activate 26 | ``` 27 | 28 | ### 3. Запускаем тесты для проверки 29 | 30 | Для проверки того, что все настроено правильно, запустите тесты: 31 | 32 | ```bash 33 | make run-test 34 | ``` 35 | 36 | Если все тесты прошли успешно, вы готовы к разработке! 37 | -------------------------------------------------------------------------------- /maxapi/enums/update.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class UpdateType(str, Enum): 5 | """ 6 | Типы обновлений (ивентов) от API. 7 | 8 | Используются для обработки различных событий в боте или чате. 9 | """ 10 | 11 | MESSAGE_CREATED = "message_created" 12 | BOT_ADDED = "bot_added" 13 | BOT_REMOVED = "bot_removed" 14 | BOT_STARTED = "bot_started" 15 | CHAT_TITLE_CHANGED = "chat_title_changed" 16 | MESSAGE_CALLBACK = "message_callback" 17 | MESSAGE_CHAT_CREATED = "message_chat_created" 18 | MESSAGE_EDITED = "message_edited" 19 | MESSAGE_REMOVED = "message_removed" 20 | USER_ADDED = "user_added" 21 | USER_REMOVED = "user_removed" 22 | BOT_STOPPED = "bot_stopped" 23 | DIALOG_CLEARED = "dialog_cleared" 24 | DIALOG_MUTED = "dialog_muted" 25 | DIALOG_UNMUTED = "dialog_unmuted" 26 | DIALOG_REMOVED = "dialog_removed" 27 | 28 | # Для начинки диспатчера 29 | ON_STARTED = "on_started" 30 | -------------------------------------------------------------------------------- /maxapi/types/attachments/image.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from ...enums.attachment import AttachmentType 6 | from .attachment import Attachment 7 | 8 | 9 | class PhotoAttachmentRequestPayload(BaseModel): 10 | """ 11 | Полезная нагрузка для запроса фото-вложения. 12 | 13 | Attributes: 14 | url (Optional[str]): URL изображения. 15 | token (Optional[str]): Токен доступа к изображению. 16 | photos (Optional[str]): Дополнительные данные о фотографиях. 17 | """ 18 | 19 | url: Optional[str] = None 20 | token: Optional[str] = None 21 | photos: Optional[str] = None 22 | 23 | 24 | class Image(Attachment): 25 | """ 26 | Вложение с типом изображения. 27 | 28 | Attributes: 29 | type (Literal['image']): Тип вложения, всегда 'image'. 30 | """ 31 | 32 | type: Literal[ 33 | AttachmentType.IMAGE 34 | ] # pyright: ignore[reportIncompatibleVariableOverride] 35 | -------------------------------------------------------------------------------- /maxapi/types/updates/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from ...types.updates.bot_added import BotAdded 4 | from ...types.updates.bot_removed import BotRemoved 5 | from ...types.updates.bot_started import BotStarted 6 | from ...types.updates.chat_title_changed import ChatTitleChanged 7 | from ...types.updates.message_callback import MessageCallback 8 | from ...types.updates.message_chat_created import MessageChatCreated 9 | from ...types.updates.message_created import MessageCreated 10 | from ...types.updates.message_edited import MessageEdited 11 | from ...types.updates.message_removed import MessageRemoved 12 | from ...types.updates.user_added import UserAdded 13 | from ...types.updates.user_removed import UserRemoved 14 | 15 | UpdateUnion = Union[ 16 | BotAdded, 17 | BotRemoved, 18 | BotStarted, 19 | ChatTitleChanged, 20 | MessageCallback, 21 | MessageChatCreated, 22 | MessageCreated, 23 | MessageEdited, 24 | MessageRemoved, 25 | UserAdded, 26 | UserRemoved, 27 | ] 28 | -------------------------------------------------------------------------------- /maxapi/types/updates/user_added.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | 7 | class UserAdded(Update): 8 | """ 9 | Класс для обработки события добавления пользователя в чат. 10 | 11 | Attributes: 12 | inviter_id (int): Идентификатор пользователя, добавившего нового участника. Может быть None. 13 | chat_id (int): Идентификатор чата. Может быть None. 14 | user (User): Объект пользователя, добавленного в чат. 15 | is_channel (bool): Указывает, был ли пользователь добавлен в канал или нет 16 | """ 17 | 18 | inviter_id: Optional[int] = None 19 | chat_id: int 20 | user: User 21 | is_channel: bool 22 | 23 | def get_ids(self) -> Tuple[Optional[int], Optional[int]]: 24 | """ 25 | Возвращает кортеж идентификаторов (chat_id, user_id). 26 | 27 | Returns: 28 | Tuple[Optional[int], Optional[int]]: Идентификаторы чата и пользователя. 29 | """ 30 | 31 | return (self.chat_id, self.inviter_id) 32 | -------------------------------------------------------------------------------- /maxapi/types/updates/user_removed.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Tuple 2 | 3 | from ...types.users import User 4 | from .update import Update 5 | 6 | 7 | class UserRemoved(Update): 8 | """ 9 | Класс для обработки события выходе/удаления пользователя из чата. 10 | 11 | Attributes: 12 | admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. None при выходе из чата самим пользователем. 13 | chat_id (int): Идентификатор чата. Может быть None. 14 | user (User): Объект пользователя, удаленного из чата. 15 | is_channel (bool): Указывает, был ли пользователь удален из канала или нет 16 | """ 17 | 18 | admin_id: Optional[int] = None 19 | chat_id: int 20 | user: User 21 | is_channel: bool 22 | 23 | def get_ids(self) -> Tuple[Optional[int], Optional[int]]: 24 | """ 25 | Возвращает кортеж идентификаторов (chat_id, user_id). 26 | 27 | Returns: 28 | Tuple[Optional[int], Optional[int]]: Идентификаторы чата и пользователя. 29 | """ 30 | 31 | return (self.chat_id, self.admin_id) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Denis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /maxapi/types/updates/dialog_muted.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import TYPE_CHECKING, Optional 3 | 4 | from ...types.users import User 5 | from .update import Update 6 | 7 | if TYPE_CHECKING: 8 | from ...bot import Bot 9 | 10 | 11 | class DialogMuted(Update): 12 | """ 13 | Обновление, сигнализирующее об отключении оповещений от бота. 14 | 15 | Attributes: 16 | chat_id (int): Идентификатор чата. 17 | muted_until (int): Время до включения оповещений от бота. 18 | user (User): Пользователь (бот). 19 | user_locale (Optional[str]): Локаль пользователя. 20 | """ 21 | 22 | chat_id: int 23 | muted_until: int 24 | user: User 25 | user_locale: Optional[str] = None 26 | 27 | if TYPE_CHECKING: 28 | bot: Optional[Bot] # pyright: ignore[reportGeneralTypeIssues] 29 | 30 | @property 31 | def muted_until_datetime(self): 32 | try: 33 | return datetime.fromtimestamp(self.muted_until // 1000) 34 | except (OverflowError, OSError): 35 | return datetime.max 36 | 37 | def get_ids(self): 38 | return (self.chat_id, self.user.user_id) 39 | -------------------------------------------------------------------------------- /tests/get_chat_id.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Утилита для получения chat_id для тестов. 3 | 4 | Использование: 5 | python tests/get_chat_id.py 6 | """ 7 | 8 | import os 9 | import asyncio 10 | import sys 11 | from pathlib import Path 12 | 13 | # Загружаем .env 14 | try: 15 | from dotenv import load_dotenv 16 | 17 | project_root = Path(__file__).parent.parent 18 | env_file = project_root / ".env" 19 | if env_file.exists(): 20 | load_dotenv(env_file, override=True) 21 | else: 22 | load_dotenv(override=True) 23 | except ImportError: 24 | sys.exit(1) 25 | 26 | from maxapi import Bot 27 | 28 | 29 | async def main(): 30 | """Получает список чатов.""" 31 | token = os.environ.get("MAX_BOT_TOKEN") 32 | 33 | if not token: 34 | sys.exit(1) 35 | 36 | bot = Bot(token=token) 37 | 38 | try: 39 | chats = await bot.get_chats(count=10) 40 | 41 | if not chats.chats or len(chats.chats) == 0: 42 | return 43 | 44 | except Exception: 45 | sys.exit(1) 46 | finally: 47 | await bot.close_session() 48 | 49 | 50 | if __name__ == "__main__": 51 | asyncio.run(main()) 52 | -------------------------------------------------------------------------------- /maxapi/context/state_machine.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class State: 5 | """ 6 | Представляет отдельное состояние в FSM-группе. 7 | 8 | При использовании внутри StatesGroup, автоматически присваивает уникальное имя в формате 'ИмяКласса:имя_поля'. 9 | """ 10 | 11 | def __init__(self): 12 | self.name = None 13 | 14 | def __set_name__(self, owner: type, attr_name: str): 15 | self.name = f"{owner.__name__}:{attr_name}" 16 | 17 | def __str__(self): 18 | return self.name 19 | 20 | 21 | class StatesGroup: 22 | """ 23 | Базовый класс для описания группы состояний FSM. 24 | 25 | Атрибуты должны быть экземплярами State. Метод `states()` возвращает список всех состояний в виде строк. 26 | """ 27 | 28 | @classmethod 29 | def states(cls) -> List[str]: 30 | """ 31 | Получить список всех состояний в формате 'ИмяКласса:имя_состояния'. 32 | 33 | Returns: 34 | Список строковых представлений состояний 35 | """ 36 | 37 | return [ 38 | str(getattr(cls, attr)) 39 | for attr in dir(cls) 40 | if isinstance(getattr(cls, attr), State) 41 | ] 42 | -------------------------------------------------------------------------------- /maxapi/client/default.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | from aiohttp import ClientTimeout 4 | 5 | 6 | class DefaultConnectionProperties: 7 | """ 8 | Класс для хранения параметров соединения по умолчанию для aiohttp-клиента. 9 | 10 | Args: 11 | timeout (float): Таймаут всего соединения в секундах (по умолчанию 5 * 30). 12 | sock_connect (int): Таймаут установки TCP-соединения в секундах (по умолчанию 30). 13 | **kwargs (Any): Дополнительные параметры, которые будут сохранены как есть. 14 | 15 | Attributes: 16 | timeout (ClientTimeout): Экземпляр aiohttp.ClientTimeout с заданными параметрами. 17 | kwargs (dict): Дополнительные параметры. 18 | """ 19 | 20 | def __init__( 21 | self, timeout: float = 5 * 30, sock_connect: int = 30, **kwargs: Any 22 | ): 23 | """ 24 | Инициализация параметров соединения. 25 | 26 | Args: 27 | timeout (float): Таймаут всего соединения в секундах. 28 | sock_connect (int): Таймаут установки TCP-соединения в секундах. 29 | **kwargs (Any): Дополнительные параметры. 30 | """ 31 | self.timeout = ClientTimeout(total=timeout, sock_connect=sock_connect) 32 | self.kwargs = kwargs 33 | -------------------------------------------------------------------------------- /maxapi/methods/get_me.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.users import User 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetMe(BaseConnection): 13 | """ 14 | Возвращает информацию о текущем боте, который идентифицируется с помощью токена доступа. 15 | Метод возвращает ID бота, его имя и аватар (если есть). 16 | 17 | https://dev.max.ru/docs-api/methods/GET/me 18 | 19 | Args: 20 | bot (Bot): Экземпляр бота для выполнения запроса. 21 | """ 22 | 23 | def __init__(self, bot: "Bot"): 24 | self.bot = bot 25 | 26 | async def fetch(self) -> User: 27 | """ 28 | Выполняет GET-запрос для получения данных о боте. 29 | 30 | Returns: 31 | User: Объект пользователя с полной информацией. 32 | """ 33 | 34 | bot = self._ensure_bot() 35 | 36 | response = await super().request( 37 | method=HTTPMethod.GET, 38 | path=ApiPath.ME, 39 | model=User, 40 | params=bot.params, 41 | ) 42 | 43 | return cast(User, response) 44 | -------------------------------------------------------------------------------- /docs/guides/webhook_vs_polling.md: -------------------------------------------------------------------------------- 1 | # Webhook vs Polling 2 | 3 | ## Polling 4 | 5 | ```python 6 | async def main(): 7 | await dp.start_polling(bot, skip_updates=False) 8 | ``` 9 | 10 | **Параметры:** 11 | 12 | - `bot` — экземпляр бота 13 | - `skip_updates` — пропускать старые события (по умолчанию `False`) 14 | 15 | **Плюсы:** 16 | 17 | - Простая настройка 18 | - Не требует публичного URL 19 | - Подходит для разработки 20 | 21 | **Минусы:** 22 | 23 | - Постоянное подключение к API 24 | 25 | ## Webhook 26 | 27 | ```python 28 | async def main(): 29 | await dp.handle_webhook(bot, host='localhost', port=8080) 30 | ``` 31 | 32 | **Параметры:** 33 | 34 | - `bot` — экземпляр бота 35 | - `host` — хост сервера (по умолчанию `'localhost'`) 36 | - `port` — порт сервера (по умолчанию `8080`) 37 | - `**kwargs` — дополнительные параметры для `init_serve` 38 | 39 | **Плюсы:** 40 | 41 | - Эффективнее для больших нагрузок 42 | - События приходят мгновенно 43 | - Меньше нагрузка на API 44 | 45 | **Минусы:** 46 | 47 | - Требует публичный URL 48 | - Нужна настройка сервера 49 | - Требует `maxapi[webhook]` 50 | 51 | 52 | !!! warning "Важно" 53 | Если у бота есть подписки на Webhook, `start_polling` предупредит об этом в логах. 54 | Удалите подписки через `await bot.delete_webhook()`. 55 | 56 | -------------------------------------------------------------------------------- /maxapi/methods/get_chat_by_id.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.chats import Chat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetChatById(BaseConnection): 13 | """ 14 | Класс для получения информации о чате по его идентификатору. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats/-chatId- 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | id (int): Идентификатор чата. 21 | """ 22 | 23 | def __init__(self, bot: "Bot", id: int): 24 | self.bot = bot 25 | self.id = id 26 | 27 | async def fetch(self) -> Chat: 28 | """ 29 | Выполняет GET-запрос для получения данных чата. 30 | 31 | Returns: 32 | Chat: Объект чата с полной информацией. 33 | """ 34 | 35 | bot = self._ensure_bot() 36 | 37 | response = await super().request( 38 | method=HTTPMethod.GET, 39 | path=ApiPath.CHATS.value + "/" + str(self.id), 40 | model=Chat, 41 | params=bot.params, 42 | ) 43 | 44 | return cast(Chat, response) 45 | -------------------------------------------------------------------------------- /docs/guides/routers.md: -------------------------------------------------------------------------------- 1 | # Роутеры 2 | 3 | Роутеры позволяют модульно организовать обработчики. `Router` наследуется от `Dispatcher`. 4 | 5 | ## Создание роутера 6 | 7 | ```python 8 | from maxapi import Router 9 | from maxapi.types import MessageCreated, Command 10 | 11 | router = Router(router_id="my_router") 12 | 13 | @router.message_created(Command('help')) 14 | async def help_handler(event: MessageCreated): 15 | await event.message.answer("Помощь") 16 | ``` 17 | 18 | ## Подключение роутера 19 | 20 | ```python 21 | from maxapi import Dispatcher 22 | 23 | dp = Dispatcher() 24 | dp.include_routers(router) # Множественное число, можно несколько 25 | ``` 26 | 27 | ## Фильтры для роутера 28 | 29 | ```python 30 | from maxapi import F 31 | from maxapi.enums.chat_type import ChatType 32 | 33 | router = Router() 34 | router.filter(...) # Базовые фильтры 35 | router.filters.append(F.chat.type == ChatType.PRIVATE) # MagicFilter 36 | ``` 37 | 38 | ## Middleware для роутера 39 | 40 | ```python 41 | from maxapi.filters.middleware import BaseMiddleware 42 | 43 | class RouterMiddleware(BaseMiddleware): 44 | async def __call__(self, handler, event_object, data): 45 | # Логика только для этого роутера 46 | return await handler(event_object, data) 47 | 48 | router.middleware(RouterMiddleware()) 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /maxapi/methods/get_subscriptions.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.getted_subscriptions import GettedSubscriptions 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetSubscriptions(BaseConnection): 13 | """ 14 | Если ваш бот получает данные через WebHook, этот класс возвращает список всех подписок. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/subscriptions 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота 20 | """ 21 | 22 | def __init__( 23 | self, 24 | bot: "Bot", 25 | ): 26 | self.bot = bot 27 | 28 | async def fetch(self) -> GettedSubscriptions: 29 | """ 30 | Отправляет запрос на получение списка всех подписок. 31 | 32 | Returns: 33 | GettedSubscriptions: Объект со списком подписок 34 | """ 35 | 36 | bot = self._ensure_bot() 37 | 38 | response = await super().request( 39 | method=HTTPMethod.GET, 40 | path=ApiPath.SUBSCRIPTIONS, 41 | model=GettedSubscriptions, 42 | params=bot.params, 43 | ) 44 | 45 | return cast(GettedSubscriptions, response) 46 | -------------------------------------------------------------------------------- /maxapi/types/updates/update.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any, Optional 4 | 5 | from pydantic import BaseModel, Field 6 | 7 | from ...enums.update import UpdateType 8 | from ...types.bot_mixin import BotMixin 9 | 10 | if TYPE_CHECKING: 11 | from ...bot import Bot 12 | from ...types.chats import Chat 13 | from ...types.users import User 14 | 15 | 16 | class Update(BaseModel, BotMixin): 17 | """ 18 | Базовая модель обновления. 19 | 20 | Attributes: 21 | update_type (UpdateType): Тип обновления. 22 | timestamp (int): Временная метка обновления. 23 | """ 24 | 25 | update_type: UpdateType 26 | timestamp: int 27 | 28 | bot: Optional[Any] = Field( 29 | default=None, exclude=True 30 | ) # pyright: ignore[reportRedeclaration] 31 | from_user: Optional[Any] = Field( 32 | default=None, exclude=True 33 | ) # pyright: ignore[reportRedeclaration] 34 | chat: Optional[Any] = Field( 35 | default=None, exclude=True 36 | ) # pyright: ignore[reportRedeclaration] 37 | 38 | if TYPE_CHECKING: 39 | bot: Optional[Bot] # type: ignore 40 | from_user: Optional[User] # type: ignore 41 | chat: Optional[Chat] # type: ignore 42 | 43 | class Config: 44 | arbitrary_types_allowed = True 45 | -------------------------------------------------------------------------------- /maxapi/methods/get_video.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.attachments.video import Video 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetVideo(BaseConnection): 13 | """ 14 | Класс для получения информации о видео по его токену. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/videos/-videoToken- 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | video_token (str): Токен видео для запроса. 21 | """ 22 | 23 | def __init__(self, bot: "Bot", video_token: str): 24 | self.bot = bot 25 | self.video_token = video_token 26 | 27 | async def fetch(self) -> Video: 28 | """ 29 | Выполняет GET-запрос для получения данных видео по токену. 30 | 31 | Returns: 32 | Video: Объект с информацией о видео. 33 | """ 34 | 35 | bot = self._ensure_bot() 36 | 37 | response = await super().request( 38 | method=HTTPMethod.GET, 39 | path=ApiPath.VIDEOS.value + "/" + self.video_token, 40 | model=Video, 41 | params=bot.params, 42 | ) 43 | 44 | return cast(Video, response) 45 | -------------------------------------------------------------------------------- /maxapi/methods/delete_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.deleted_chat import DeletedChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class DeleteChat(BaseConnection): 13 | """ 14 | Класс для удаления чата через API. 15 | 16 | https://dev.max.ru/docs-api/methods/DELETE/chats/-chatId- 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата, который необходимо удалить. 21 | """ 22 | 23 | def __init__(self, bot: "Bot", chat_id: int): 24 | self.bot = bot 25 | self.chat_id = chat_id 26 | 27 | async def fetch(self) -> DeletedChat: 28 | """ 29 | Отправляет DELETE-запрос для удаления указанного чата. 30 | 31 | Returns: 32 | DeletedChat: Результат операции удаления чата. 33 | """ 34 | 35 | bot = self._ensure_bot() 36 | 37 | response = await super().request( 38 | method=HTTPMethod.DELETE, 39 | path=ApiPath.CHATS.value + "/" + str(self.chat_id), 40 | model=DeletedChat, 41 | params=bot.params, 42 | ) 43 | 44 | return cast(DeletedChat, response) 45 | -------------------------------------------------------------------------------- /maxapi/methods/get_message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.message import Message 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetMessage(BaseConnection): 13 | """ 14 | Класс для получения сообщения. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/messages/-messageId- 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | message_id (Optional[str]): ID сообщения (mid), чтобы получить одно сообщение в чате. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | message_id: str, 27 | ): 28 | self.bot = bot 29 | self.message_id = message_id 30 | 31 | async def fetch(self) -> Message: 32 | """ 33 | Выполняет GET-запрос для получения сообщения. 34 | 35 | Returns: 36 | Message: Объект с полученным сообщением. 37 | """ 38 | 39 | bot = self._ensure_bot() 40 | 41 | response = await super().request( 42 | method=HTTPMethod.GET, 43 | path=ApiPath.MESSAGES + "/" + self.message_id, 44 | model=Message, 45 | params=bot.params, 46 | ) 47 | 48 | return cast(Message, response) 49 | -------------------------------------------------------------------------------- /maxapi/methods/get_pinned_message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from .types.getted_pineed_message import GettedPin 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetPinnedMessage(BaseConnection): 13 | """ 14 | Класс для получения закреплённого сообщения в указанном чате. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats/-chatId-/pin 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | chat_id: int, 27 | ): 28 | self.bot = bot 29 | self.chat_id = chat_id 30 | 31 | async def fetch(self) -> GettedPin: 32 | """ 33 | Выполняет GET-запрос для получения закреплённого сообщения. 34 | 35 | Returns: 36 | GettedPin: Объект с информацией о закреплённом сообщении. 37 | """ 38 | 39 | bot = self._ensure_bot() 40 | 41 | response = await super().request( 42 | method=HTTPMethod.GET, 43 | path=ApiPath.CHATS + "/" + str(self.chat_id) + ApiPath.PIN, 44 | model=GettedPin, 45 | params=bot.params, 46 | ) 47 | 48 | return cast(GettedPin, response) 49 | -------------------------------------------------------------------------------- /maxapi/methods/get_me_from_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.chats import ChatMember 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetMeFromChat(BaseConnection): 13 | """ 14 | Класс для получения информации о текущем боте в конкретном чате. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats/-chatId-/members/me 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота. 20 | chat_id (int): Идентификатор чата. 21 | """ 22 | 23 | def __init__(self, bot: "Bot", chat_id: int): 24 | self.bot = bot 25 | self.chat_id = chat_id 26 | 27 | async def fetch(self) -> ChatMember: 28 | """ 29 | Выполняет GET-запрос для получения информации о боте в указанном чате. 30 | 31 | Returns: 32 | ChatMember: Информация о боте как участнике чата. 33 | """ 34 | 35 | bot = self._ensure_bot() 36 | 37 | response = await super().request( 38 | method=HTTPMethod.GET, 39 | path=ApiPath.CHATS 40 | + "/" 41 | + str(self.chat_id) 42 | + ApiPath.MEMBERS 43 | + ApiPath.ME, 44 | model=ChatMember, 45 | params=bot.params, 46 | ) 47 | 48 | return cast(ChatMember, response) 49 | -------------------------------------------------------------------------------- /maxapi/utils/inline_keyboard.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from ..enums.attachment import AttachmentType 4 | from ..types.attachments.attachment import Attachment, ButtonsPayload 5 | from ..types.attachments.buttons import InlineButtonUnion 6 | 7 | 8 | class InlineKeyboardBuilder: 9 | """ 10 | Конструктор инлайн-клавиатур. 11 | 12 | Позволяет удобно собирать кнопки в ряды и формировать из них клавиатуру 13 | для отправки в сообщениях. 14 | """ 15 | 16 | def __init__(self): 17 | self.payload: List[List[InlineButtonUnion]] = [[]] 18 | 19 | def row(self, *buttons: InlineButtonUnion): 20 | """ 21 | Добавить новый ряд кнопок в клавиатуру. 22 | 23 | Args: 24 | *buttons: Произвольное количество кнопок для добавления в ряд. 25 | """ 26 | 27 | self.payload.append([*buttons]) 28 | 29 | def add(self, button: InlineButtonUnion): 30 | """ 31 | Добавить кнопку в последний ряд клавиатуры. 32 | 33 | Args: 34 | button: Кнопка для добавления. 35 | """ 36 | 37 | self.payload[-1].append(button) 38 | 39 | def as_markup(self) -> Attachment: 40 | """ 41 | Собрать клавиатуру в объект для отправки. 42 | 43 | Returns: 44 | Attachment: Объект вложения с типом INLINE_KEYBOARD. 45 | """ 46 | 47 | return Attachment( 48 | type=AttachmentType.INLINE_KEYBOARD, 49 | payload=ButtonsPayload(buttons=self.payload), 50 | ) # type: ignore 51 | -------------------------------------------------------------------------------- /maxapi/methods/get_list_admin_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.getted_list_admin_chat import GettedListAdminChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetListAdminChat(BaseConnection): 13 | """ 14 | Класс для получения списка администраторов чата через API. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats/-chatId-/members/admins 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота. 20 | chat_id (int): Идентификатор чата. 21 | """ 22 | 23 | def __init__(self, bot: "Bot", chat_id: int): 24 | self.bot = bot 25 | self.chat_id = chat_id 26 | 27 | async def fetch(self) -> GettedListAdminChat: 28 | """ 29 | Выполняет GET-запрос для получения списка администраторов указанного чата. 30 | 31 | Returns: 32 | GettedListAdminChat: Объект с информацией о администраторах чата. 33 | """ 34 | 35 | bot = self._ensure_bot() 36 | 37 | response = await super().request( 38 | method=HTTPMethod.GET, 39 | path=ApiPath.CHATS.value 40 | + "/" 41 | + str(self.chat_id) 42 | + ApiPath.MEMBERS 43 | + ApiPath.ADMINS, 44 | model=GettedListAdminChat, 45 | params=bot.params, 46 | ) 47 | 48 | return cast(GettedListAdminChat, response) 49 | -------------------------------------------------------------------------------- /docs/guides/keyboards.md: -------------------------------------------------------------------------------- 1 | # Клавиатуры 2 | 3 | ## InlineKeyboardBuilder 4 | 5 | Рекомендуемый способ создания клавиатур: 6 | 7 | ```python 8 | from maxapi.utils.inline_keyboard import InlineKeyboardBuilder 9 | from maxapi.types.attachments.buttons import LinkButton, CallbackButton 10 | 11 | builder = InlineKeyboardBuilder() 12 | builder.row( 13 | LinkButton(text="Сайт", url="https://example.com"), 14 | CallbackButton(text="Нажми", payload="data"), 15 | ) 16 | builder.row(CallbackButton(text="Ещё кнопка", payload="more")) 17 | 18 | await event.message.answer( 19 | text="Выберите действие:", 20 | attachments=[builder.as_markup()] 21 | ) 22 | ``` 23 | 24 | ## Через ButtonsPayload 25 | 26 | Альтернативный способ: 27 | 28 | ```python 29 | from maxapi.types.attachments.attachment import ButtonsPayload 30 | from maxapi.types.attachments.buttons import LinkButton, CallbackButton 31 | 32 | buttons = [ 33 | [LinkButton(text="Сайт", url="https://example.com")], 34 | [CallbackButton(text="Callback", payload="data")] 35 | ] 36 | payload = ButtonsPayload(buttons=buttons).pack() 37 | 38 | await event.message.answer( 39 | text="Клавиатура", 40 | attachments=[payload] 41 | ) 42 | ``` 43 | 44 | ## Типы кнопок 45 | 46 | - `CallbackButton` — кнопка с payload 47 | - `LinkButton` — ссылка 48 | - `ChatButton` — переход в чат 49 | - `MessageButton` — отправка сообщения 50 | - `AttachmentButton` — вложение 51 | - `OpenAppButton` — открытие приложения 52 | - `RequestContact` — запрос контакта 53 | - `RequestGeoLocationButton` — запрос геолокации 54 | 55 | -------------------------------------------------------------------------------- /maxapi/methods/delete_pin_message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.deleted_pin_message import DeletedPinMessage 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class DeletePinMessage(BaseConnection): 13 | """ 14 | Класс для удаления закреплённого сообщения в чате через API. 15 | 16 | https://dev.max.ru/docs-api/methods/DELETE/chats/-chatId-/pin 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата, из которого нужно удалить закреплённое сообщение. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | chat_id: int, 27 | ): 28 | self.bot = bot 29 | self.chat_id = chat_id 30 | 31 | async def fetch(self) -> DeletedPinMessage: 32 | """ 33 | Выполняет DELETE-запрос для удаления закреплённого сообщения. 34 | 35 | Returns: 36 | DeletedPinMessage: Результат операции удаления закреплённого сообщения. 37 | """ 38 | 39 | bot = self._ensure_bot() 40 | 41 | response = await super().request( 42 | method=HTTPMethod.DELETE, 43 | path=ApiPath.CHATS + "/" + str(self.chat_id) + ApiPath.PIN, 44 | model=DeletedPinMessage, 45 | params=bot.params, 46 | ) 47 | 48 | return cast(DeletedPinMessage, response) 49 | -------------------------------------------------------------------------------- /maxapi/methods/get_chat_by_link.py: -------------------------------------------------------------------------------- 1 | from re import findall 2 | from typing import TYPE_CHECKING, cast 3 | 4 | from ..connection.base import BaseConnection 5 | from ..enums.api_path import ApiPath 6 | from ..enums.http_method import HTTPMethod 7 | from ..types.chats import Chat 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class GetChatByLink(BaseConnection): 14 | """ 15 | Класс для получения информации о чате по ссылке. 16 | 17 | https://dev.max.ru/docs-api/methods/GET/chats/-chatLink- 18 | 19 | Attributes: 20 | link (list[str]): Список валидных частей ссылки. 21 | PATTERN_LINK (str): Регулярное выражение для парсинга ссылки. 22 | """ 23 | 24 | PATTERN_LINK = r"@?[a-zA-Z]+[a-zA-Z0-9-_]*" 25 | 26 | def __init__(self, bot: "Bot", link: str): 27 | self.bot = bot 28 | self.link = findall(self.PATTERN_LINK, link) 29 | 30 | if not self.link: 31 | raise ValueError(f"link не соответствует {self.PATTERN_LINK!r}") 32 | 33 | async def fetch(self) -> Chat: 34 | """ 35 | Выполняет GET-запрос для получения данных чата по ссылке. 36 | 37 | Returns: 38 | Chat: Объект с информацией о чате. 39 | """ 40 | 41 | bot = self._ensure_bot() 42 | 43 | response = await super().request( 44 | method=HTTPMethod.GET, 45 | path=ApiPath.CHATS.value + "/" + self.link[-1], 46 | model=Chat, 47 | params=bot.params, 48 | ) 49 | 50 | return cast(Chat, response) 51 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - 'docs/**' 9 | - 'mkdocs.yml' 10 | - 'maxapi/**' 11 | - '.github/workflows/docs.yml' 12 | workflow_dispatch: 13 | 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: false 22 | 23 | jobs: 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Install uv 31 | uses: astral-sh/setup-uv@v4 32 | with: 33 | version: "latest" 34 | 35 | - name: Set up Python 36 | uses: actions/setup-python@v5 37 | with: 38 | python-version: "3.11" 39 | 40 | - name: Install dependencies 41 | run: | 42 | uv sync --group dev 43 | 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v4 46 | 47 | - name: Build with MkDocs 48 | run: | 49 | uv run mkdocs build 50 | 51 | - name: Upload artifact 52 | uses: actions/upload-pages-artifact@v3 53 | with: 54 | path: ./site 55 | 56 | deploy: 57 | environment: 58 | name: github-pages 59 | url: ${{ steps.deployment.outputs.page_url }} 60 | runs-on: ubuntu-latest 61 | needs: build 62 | steps: 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 66 | 67 | -------------------------------------------------------------------------------- /maxapi/methods/get_upload_url.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..enums.upload_type import UploadType 7 | from ..methods.types.getted_upload_url import GettedUploadUrl 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class GetUploadURL(BaseConnection): 14 | """ 15 | Класс для получения URL загрузки файла определённого типа. 16 | 17 | https://dev.max.ru/docs-api/methods/POST/uploads 18 | 19 | Attributes: 20 | bot (Bot): Экземпляр бота для выполнения запроса. 21 | type (UploadType): Тип загружаемого файла (например, image, video и т.д.). 22 | """ 23 | 24 | def __init__(self, bot: "Bot", type: UploadType): 25 | self.bot = bot 26 | self.type = type 27 | 28 | async def fetch(self) -> GettedUploadUrl: 29 | """ 30 | Выполняет POST-запрос для получения URL загрузки файла. 31 | 32 | Возвращает объект с данными URL. 33 | 34 | Returns: 35 | GettedUploadUrl: Результат с URL для загрузки. 36 | """ 37 | 38 | bot = self._ensure_bot() 39 | 40 | params = bot.params.copy() 41 | 42 | params["type"] = self.type.value 43 | 44 | response = await super().request( 45 | method=HTTPMethod.POST, 46 | path=ApiPath.UPLOADS, 47 | model=GettedUploadUrl, 48 | params=params, 49 | ) 50 | 51 | return cast(GettedUploadUrl, response) 52 | -------------------------------------------------------------------------------- /maxapi/methods/delete_bot_from_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.deleted_bot_from_chat import DeletedBotFromChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class DeleteMeFromMessage(BaseConnection): 13 | """ 14 | Класс для удаления бота из участников указанного чата. 15 | 16 | https://dev.max.ru/docs-api/methods/DELETE/chats/-chatId-/members/me 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата, из которого нужно удалить бота. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | chat_id: int, 27 | ): 28 | self.bot = bot 29 | self.chat_id = chat_id 30 | 31 | async def fetch(self) -> DeletedBotFromChat: 32 | """ 33 | Отправляет DELETE-запрос для удаления бота из чата. 34 | 35 | Returns: 36 | DeletedBotFromChat: Результат операции удаления. 37 | """ 38 | 39 | bot = self._ensure_bot() 40 | 41 | response = await super().request( 42 | method=HTTPMethod.DELETE, 43 | path=ApiPath.CHATS 44 | + "/" 45 | + str(self.chat_id) 46 | + ApiPath.MEMBERS 47 | + ApiPath.ME, 48 | model=DeletedBotFromChat, 49 | params=bot.params, 50 | ) 51 | 52 | return cast(DeletedBotFromChat, response) 53 | -------------------------------------------------------------------------------- /maxapi/methods/remove_admin.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from .types.removed_admin import RemovedAdmin 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class RemoveAdmin(BaseConnection): 13 | """ 14 | Класс для отмены прав администратора в чате. 15 | 16 | https://dev.max.ru/docs-api/methods/DELETE/chats/-chatId-/members/admins/-userId- 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота. 20 | chat_id (int): Идентификатор чата. 21 | user_id (int): Идентификатор пользователя. 22 | """ 23 | 24 | def __init__(self, bot: "Bot", chat_id: int, user_id: int): 25 | self.bot = bot 26 | self.chat_id = chat_id 27 | self.user_id = user_id 28 | 29 | async def fetch(self) -> RemovedAdmin: 30 | """ 31 | Выполняет DELETE-запрос для отмены прав администратора в чате. 32 | 33 | Returns: 34 | RemovedAdmin: Объект с результатом отмены прав администратора. 35 | """ 36 | 37 | bot = self._ensure_bot() 38 | 39 | response = await super().request( 40 | method=HTTPMethod.DELETE, 41 | path=ApiPath.CHATS 42 | + "/" 43 | + str(self.chat_id) 44 | + ApiPath.MEMBERS 45 | + ApiPath.ADMINS 46 | + "/" 47 | + str(self.user_id), 48 | model=RemovedAdmin, 49 | params=bot.params, 50 | ) 51 | 52 | return cast(RemovedAdmin, response) 53 | -------------------------------------------------------------------------------- /maxapi/exceptions/dispatcher.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from dataclasses import dataclass 4 | from typing import Any, Dict, Optional 5 | 6 | 7 | @dataclass(slots=True) 8 | class HandlerException(Exception): 9 | handler_title: str 10 | router_id: str | int | None 11 | process_info: str 12 | memory_context: Dict[str, Any] 13 | cause: Optional[BaseException] = None 14 | 15 | def __str__(self) -> str: 16 | parts = [ 17 | f"handler={self.handler_title!s}", 18 | f"router_id={self.router_id!s}", 19 | f"process={self.process_info}", 20 | f"context_keys={list(self.memory_context.keys())}", 21 | ] 22 | if self.cause: 23 | parts.append( 24 | f"cause={self.cause.__class__.__name__}: {self.cause}" 25 | ) 26 | return "HandlerException(" + ", ".join(parts) + ")" 27 | 28 | 29 | @dataclass(slots=True) 30 | class MiddlewareException(Exception): 31 | middleware_title: str 32 | router_id: str | int | None 33 | process_info: str 34 | memory_context: Dict[str, Any] 35 | cause: Optional[BaseException] = None 36 | 37 | def __str__(self) -> str: 38 | parts = [ 39 | f"middleware={self.middleware_title!s}", 40 | f"router_id={self.router_id!s}", 41 | f"process={self.process_info}", 42 | f"context_keys={list(self.memory_context.keys())}", 43 | ] 44 | if self.cause: 45 | parts.append( 46 | f"cause={self.cause.__class__.__name__}: {self.cause}" 47 | ) 48 | return "MiddlewareException(" + ", ".join(parts) + ")" 49 | -------------------------------------------------------------------------------- /maxapi/methods/unsubscribe_webhook.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.unsubscribed import Unsubscribed 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class UnsubscribeWebhook(BaseConnection): 13 | """ 14 | Отписывает бота от получения обновлений через WebHook. 15 | После вызова этого метода бот перестает получать уведомления о новых событиях, 16 | и доступна доставка уведомлений через API с длительным опросом. 17 | 18 | https://dev.max.ru/docs-api/methods/DELETE/subscriptions 19 | 20 | Attributes: 21 | bot (Bot): Экземпляр бота для выполнения запроса. 22 | url (str): URL, который нужно удалить из подписок на WebHook 23 | """ 24 | 25 | def __init__( 26 | self, 27 | bot: "Bot", 28 | url: str, 29 | ): 30 | self.bot = bot 31 | self.url = url 32 | 33 | async def fetch(self) -> Unsubscribed: 34 | """ 35 | Отправляет запрос на подписку бота на получение обновлений через WebHook 36 | 37 | Returns: 38 | Unsubscribed: Объект с информацией об операции 39 | """ 40 | 41 | bot = self._ensure_bot() 42 | 43 | params = bot.params.copy() 44 | 45 | params["url"] = self.url 46 | 47 | response = await super().request( 48 | method=HTTPMethod.DELETE, 49 | path=ApiPath.SUBSCRIPTIONS, 50 | model=Unsubscribed, 51 | params=params, 52 | ) 53 | 54 | return cast(Unsubscribed, response) 55 | -------------------------------------------------------------------------------- /docs/guides/middleware.md: -------------------------------------------------------------------------------- 1 | # Middleware 2 | 3 | Middleware позволяет обрабатывать события до и после обработчиков. 4 | 5 | ## Создание middleware 6 | 7 | ```python 8 | from maxapi.filters.middleware import BaseMiddleware 9 | from typing import Any, Awaitable, Callable, Dict 10 | 11 | class LoggingMiddleware(BaseMiddleware): 12 | async def __call__( 13 | self, 14 | handler: Callable[[Any, Dict[str, Any]], Awaitable[Any]], 15 | event_object: Any, 16 | data: Dict[str, Any], 17 | ) -> Any: 18 | print(f"Обработка события: {event_object.update_type}") 19 | result = await handler(event_object, data) 20 | print(f"Обработка завершена") 21 | return result 22 | ``` 23 | 24 | ## Глобальный middleware 25 | 26 | ```python 27 | dp.middleware(LoggingMiddleware()) 28 | ``` 29 | 30 | ## Middleware в обработчике 31 | 32 | ```python 33 | @dp.message_created(Command('start'), LoggingMiddleware()) 34 | async def start_handler(event: MessageCreated): 35 | await event.message.answer("Привет!") 36 | ``` 37 | 38 | ## Middleware с данными 39 | 40 | ```python 41 | class CustomDataMiddleware(BaseMiddleware): 42 | async def __call__(self, handler, event_object, data): 43 | data['custom_data'] = f'User ID: {event_object.from_user.user_id}' 44 | return await handler(event_object, data) 45 | 46 | @dp.message_created(Command('data'), CustomDataMiddleware()) 47 | async def handler(event: MessageCreated, custom_data: str): 48 | await event.message.answer(custom_data) 49 | ``` 50 | 51 | ## Примеры использования 52 | 53 | - Логирование 54 | - Авторизация 55 | - Обработка ошибок 56 | - Измерение времени выполнения 57 | - Модификация данных 58 | 59 | -------------------------------------------------------------------------------- /maxapi/methods/delete_message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.deleted_message import DeletedMessage 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class DeleteMessage(BaseConnection): 13 | """ 14 | Класс для удаления сообщения через API. 15 | 16 | https://dev.max.ru/docs-api/methods/DELETE/messages 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | message_id (str): Идентификатор сообщения, которое нужно удалить. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | message_id: str, 27 | ): 28 | if len(message_id) < 1: 29 | raise ValueError("message_id не должен быть меньше 1 символа") 30 | 31 | self.bot = bot 32 | self.message_id = message_id 33 | 34 | async def fetch(self) -> DeletedMessage: 35 | """ 36 | Выполняет DELETE-запрос для удаления сообщения. 37 | 38 | Использует параметр message_id для идентификации сообщения. 39 | 40 | Returns: 41 | DeletedMessage: Результат операции удаления сообщения. 42 | """ 43 | 44 | bot = self._ensure_bot() 45 | 46 | params = bot.params.copy() 47 | 48 | params["message_id"] = self.message_id 49 | 50 | response = await super().request( 51 | method=HTTPMethod.DELETE, 52 | path=ApiPath.MESSAGES, 53 | model=DeletedMessage, 54 | params=params, 55 | ) 56 | 57 | return cast(DeletedMessage, response) 58 | -------------------------------------------------------------------------------- /maxapi/methods/get_chats.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.chats import Chats 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetChats(BaseConnection): 13 | """ 14 | Класс для получения списка чатов. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats 17 | 18 | Attributes: 19 | bot (Bot): Инициализированный клиент бота. 20 | count (Optional[int]): Максимальное количество чатов, возвращаемых за один запрос. 21 | marker (Optional[int]): Маркер для продолжения пагинации. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | bot: "Bot", 27 | count: Optional[int] = None, 28 | marker: Optional[int] = None, 29 | ): 30 | if count is not None and not (1 <= count <= 100): 31 | raise ValueError("count не должен быть меньше 1 или больше 100") 32 | 33 | self.bot = bot 34 | self.count = count 35 | self.marker = marker 36 | 37 | async def fetch(self) -> Chats: 38 | """ 39 | Выполняет GET-запрос для получения списка чатов. 40 | 41 | Returns: 42 | Chats: Объект с данными по списку чатов. 43 | """ 44 | 45 | bot = self._ensure_bot() 46 | 47 | params = bot.params.copy() 48 | 49 | if self.count: 50 | params["count"] = self.count 51 | 52 | if self.marker: 53 | params["marker"] = self.marker 54 | 55 | response = await super().request( 56 | method=HTTPMethod.GET, 57 | path=ApiPath.CHATS, 58 | model=Chats, 59 | params=params, 60 | ) 61 | 62 | return cast(Chats, response) 63 | -------------------------------------------------------------------------------- /docs/guides/context.md: -------------------------------------------------------------------------------- 1 | # Контекст и состояния 2 | 3 | ## MemoryContext 4 | 5 | Встроенная система состояний для диалогов. Контекст автоматически передается в обработчики: 6 | 7 | ```python 8 | from maxapi.context import MemoryContext, StatesGroup, State 9 | from maxapi.types import MessageCreated, Command 10 | 11 | class Form(StatesGroup): 12 | name = State() 13 | age = State() 14 | 15 | @dp.message_created(Command('start')) 16 | async def start_handler(event: MessageCreated, context: MemoryContext): 17 | await context.set_state(Form.name) 18 | await event.message.answer("Как вас зовут?") 19 | 20 | @dp.message_created(Form.name) 21 | async def name_handler(event: MessageCreated, context: MemoryContext): 22 | await context.update_data(name=event.message.body.text) 23 | await context.set_state(Form.age) 24 | await event.message.answer("Сколько вам лет?") 25 | 26 | @dp.message_created(Form.age) 27 | async def age_handler(event: MessageCreated, context: MemoryContext): 28 | data = await context.get_data() 29 | await event.message.answer( 30 | f"Приятно познакомиться, {data['name']}! " 31 | f"Вам {event.message.body.text} лет." 32 | ) 33 | await context.set_state(None) # Сброс состояния 34 | ``` 35 | 36 | ## Методы MemoryContext 37 | 38 | - `set_state(state)` — установить состояние (State или None для сброса) 39 | - `get_state()` — получить текущее состояние 40 | - `get_data()` — получить все данные контекста 41 | - `update_data(**kwargs)` — обновить данные 42 | - `set_data(data)` — полностью заменить данные 43 | - `clear()` — очистить контекст и сбросить состояние 44 | 45 | ## StatesGroup 46 | 47 | Группа состояний для FSM: 48 | 49 | ```python 50 | class Form(StatesGroup): 51 | name = State() # Автоматически получит имя 'Form:name' 52 | age = State() # Автоматически получит имя 'Form:age' 53 | ``` 54 | 55 | -------------------------------------------------------------------------------- /maxapi/methods/send_action.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..enums.sender_action import SenderAction 7 | from ..methods.types.sended_action import SendedAction 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class SendAction(BaseConnection): 14 | """ 15 | Класс для отправки действия пользователя (например, индикатора печати) в чат. 16 | 17 | https://dev.max.ru/docs-api/methods/POST/chats/-chatId-/actions 18 | 19 | Attributes: 20 | bot (Bot): Экземпляр бота для выполнения запроса. 21 | chat_id (Optional[int]): Идентификатор чата. Если None, действие не отправляется. 22 | action (Optional[SenderAction]): Тип действия. По умолчанию SenderAction.TYPING_ON. 23 | """ 24 | 25 | def __init__( 26 | self, 27 | bot: "Bot", 28 | chat_id: Optional[int] = None, 29 | action: SenderAction = SenderAction.TYPING_ON, 30 | ): 31 | self.bot = bot 32 | self.chat_id = chat_id 33 | self.action = action 34 | 35 | async def fetch(self) -> SendedAction: 36 | """ 37 | Выполняет POST-запрос для отправки действия в указанный чат. 38 | 39 | Returns: 40 | SendedAction: Результат выполнения запроса. 41 | """ 42 | 43 | bot = self._ensure_bot() 44 | 45 | json: Dict[str, Any] = {} 46 | 47 | json["action"] = self.action.value 48 | 49 | response = await super().request( 50 | method=HTTPMethod.POST, 51 | path=ApiPath.CHATS + "/" + str(self.chat_id) + ApiPath.ACTIONS, 52 | model=SendedAction, 53 | params=bot.params, 54 | json=json, 55 | ) 56 | 57 | return cast(SendedAction, response) 58 | -------------------------------------------------------------------------------- /maxapi/methods/add_members_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, List, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.added_members_chat import AddedMembersChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class AddMembersChat(BaseConnection): 13 | """ 14 | Класс для добавления участников в чат через API. 15 | 16 | https://dev.max.ru/docs-api/methods/POST/chats/-chatId-/members 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота, через который выполняется запрос. 20 | chat_id (int): Идентификатор целевого чата. 21 | user_ids (List[int]): Список ID пользователей для добавления в чат. 22 | """ 23 | 24 | def __init__( 25 | self, 26 | bot: "Bot", 27 | chat_id: int, 28 | user_ids: List[int], 29 | ): 30 | self.bot = bot 31 | self.chat_id = chat_id 32 | self.user_ids = user_ids 33 | 34 | async def fetch(self) -> AddedMembersChat: 35 | """ 36 | Отправляет POST-запрос на добавление пользователей в чат. 37 | 38 | Формирует JSON с ID пользователей и вызывает базовый метод запроса. 39 | 40 | Returns: 41 | AddedMembersChat: Результат операции с информацией об успешности добавления. 42 | """ 43 | 44 | bot = self._ensure_bot() 45 | 46 | json: Dict[str, Any] = {} 47 | 48 | json["user_ids"] = self.user_ids 49 | 50 | response = await super().request( 51 | method=HTTPMethod.POST, 52 | path=ApiPath.CHATS.value 53 | + "/" 54 | + str(self.chat_id) 55 | + ApiPath.MEMBERS, 56 | model=AddedMembersChat, 57 | params=bot.params, 58 | json=json, 59 | ) 60 | 61 | return cast(AddedMembersChat, response) 62 | -------------------------------------------------------------------------------- /maxapi/filters/handler.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, List, Optional 2 | 3 | from magic_filter import MagicFilter 4 | 5 | from ..context.state_machine import State 6 | from ..enums.update import UpdateType 7 | from ..filters.filter import BaseFilter 8 | from ..filters.middleware import BaseMiddleware 9 | from ..loggers import logger_dp 10 | 11 | 12 | class Handler: 13 | """ 14 | Обработчик события. 15 | 16 | Связывает функцию-обработчик с типом события, состояниями и фильтрами. 17 | """ 18 | 19 | def __init__( 20 | self, 21 | *args: Any, 22 | func_event: Callable, 23 | update_type: UpdateType, 24 | **kwargs: Any, 25 | ): 26 | """ 27 | Создаёт обработчик события. 28 | 29 | Args: 30 | *args (Any): Список фильтров (MagicFilter, State, Command, BaseFilter, BaseMiddleware). 31 | func_event (Callable): Функция-обработчик. 32 | update_type (UpdateType): Тип обновления. 33 | **kwargs (Any): Дополнительные параметры. 34 | """ 35 | 36 | self.func_event: Callable = func_event 37 | self.update_type: UpdateType = update_type 38 | self.filters: Optional[List[MagicFilter]] = [] 39 | self.base_filters: Optional[List[BaseFilter]] = [] 40 | self.states: Optional[List[State]] = [] 41 | self.middlewares: List[BaseMiddleware] = [] 42 | 43 | for arg in args: 44 | if isinstance(arg, MagicFilter): 45 | self.filters.append(arg) 46 | elif isinstance(arg, State): 47 | self.states.append(arg) 48 | elif isinstance(arg, BaseMiddleware): 49 | self.middlewares.append(arg) 50 | elif isinstance(arg, BaseFilter): 51 | self.base_filters.append(arg) 52 | else: 53 | logger_dp.info( 54 | f"Неизвестный фильтр `{arg}` при регистрации `{func_event.__name__}`" 55 | ) 56 | -------------------------------------------------------------------------------- /maxapi/methods/pin_message.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from .types.pinned_message import PinnedMessage 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class PinMessage(BaseConnection): 13 | """ 14 | Класс для закрепления сообщения в чате. 15 | 16 | https://dev.max.ru/docs-api/methods/PUT/chats/-chatId-/pin 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата, в котором закрепляется сообщение. 21 | message_id (str): Идентификатор сообщения для закрепления. 22 | notify (bool, optional): Отправлять ли уведомление о закреплении (по умолчанию True). 23 | """ 24 | 25 | def __init__( 26 | self, 27 | bot: "Bot", 28 | chat_id: int, 29 | message_id: str, 30 | notify: Optional[bool] = None, 31 | ): 32 | self.bot = bot 33 | self.chat_id = chat_id 34 | self.message_id = message_id 35 | self.notify = notify 36 | 37 | async def fetch(self) -> PinnedMessage: 38 | """ 39 | Выполняет PUT-запрос для закрепления сообщения в чате. 40 | 41 | Формирует тело запроса с ID сообщения и флагом уведомления. 42 | 43 | Returns: 44 | PinnedMessage: Объект с информацией о закреплённом сообщении. 45 | """ 46 | 47 | bot = self._ensure_bot() 48 | 49 | json: Dict[str, Any] = {} 50 | 51 | json["message_id"] = self.message_id 52 | json["notify"] = self.notify 53 | 54 | response = await super().request( 55 | method=HTTPMethod.PUT, 56 | path=ApiPath.CHATS + "/" + str(self.chat_id) + ApiPath.PIN, 57 | model=PinnedMessage, 58 | params=bot.params, 59 | json=json, 60 | ) 61 | 62 | return cast(PinnedMessage, response) 63 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [main, develop] 6 | pull_request: 7 | branches: [main, develop] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ["3.10", "3.11", "3.12", "3.13"] 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | 21 | - name: Install uv 22 | uses: astral-sh/setup-uv@v4 23 | with: 24 | version: "latest" 25 | 26 | - name: Set up Python ${{ matrix.python-version }} 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: ${{ matrix.python-version }} 30 | 31 | - name: Install dependencies 32 | run: | 33 | uv sync --all-groups 34 | 35 | - name: Run linting 36 | run: | 37 | uv run ruff check . 38 | uv run black --check . 39 | uv run isort --check-only maxapi tests 40 | 41 | - name: Run type checking 42 | run: | 43 | uv run mypy maxapi || true # Пока mypy может быть не строгим 44 | 45 | - name: Run unit tests 46 | run: | 47 | uv run pytest tests/ -v -m "not integration" --cov=maxapi --cov-report=xml --cov-report=term 48 | 49 | - name: Run integration tests 50 | env: 51 | MAX_BOT_TOKEN: ${{ secrets.MAX_BOT_TOKEN }} 52 | TEST_CHAT_ID: ${{ secrets.TEST_CHAT_ID }} 53 | run: | 54 | if [ -n "$MAX_BOT_TOKEN" ]; then 55 | uv run pytest tests/ -v -m integration 56 | else 57 | echo "MAX_BOT_TOKEN не установлен, пропускаем интеграционные тесты" 58 | fi 59 | 60 | - name: Upload coverage to Codecov 61 | if: matrix.python-version == '3.11' 62 | uses: codecov/codecov-action@v4 63 | with: 64 | file: ./coverage.xml 65 | fail_ci_if_error: false 66 | flags: unittests 67 | name: codecov-umbrella 68 | 69 | -------------------------------------------------------------------------------- /maxapi/methods/remove_member_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from .types.removed_member_chat import RemovedMemberChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class RemoveMemberChat(BaseConnection): 13 | """ 14 | Класс для удаления участника из чата с опцией блокировки. 15 | 16 | Attributes: 17 | bot (Bot): Экземпляр бота для выполнения запроса. 18 | chat_id (int): Идентификатор чата. 19 | user_id (int): Идентификатор пользователя, которого необходимо удалить. 20 | block (bool, optional): Блокировать пользователя после удаления. По умолчанию False. 21 | """ 22 | 23 | def __init__( 24 | self, 25 | bot: "Bot", 26 | chat_id: int, 27 | user_id: int, 28 | block: bool = False, 29 | ): 30 | self.bot = bot 31 | self.chat_id = chat_id 32 | self.user_id = user_id 33 | self.block = block 34 | 35 | async def fetch(self) -> RemovedMemberChat: 36 | """ 37 | Выполняет DELETE-запрос для удаления пользователя из чата. 38 | 39 | Параметр `block` определяет, будет ли пользователь заблокирован после удаления. 40 | 41 | Returns: 42 | RemovedMemberChat: Результат удаления участника. 43 | """ 44 | 45 | bot = self._ensure_bot() 46 | 47 | params = bot.params.copy() 48 | 49 | params["chat_id"] = self.chat_id 50 | params["user_id"] = self.user_id 51 | params["block"] = str(self.block).lower() 52 | 53 | response = await super().request( 54 | method=HTTPMethod.DELETE, 55 | path=ApiPath.CHATS.value 56 | + "/" 57 | + str(self.chat_id) 58 | + ApiPath.MEMBERS, 59 | model=RemovedMemberChat, 60 | params=params, 61 | ) 62 | 63 | return cast(RemovedMemberChat, response) 64 | -------------------------------------------------------------------------------- /maxapi/types/users.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import List, Optional 3 | 4 | from pydantic import BaseModel 5 | 6 | from ..enums.chat_permission import ChatPermission 7 | from ..types.command import BotCommand 8 | 9 | 10 | class User(BaseModel): 11 | """ 12 | Модель пользователя. 13 | 14 | Attributes: 15 | user_id (int): Уникальный идентификатор пользователя. 16 | first_name (str): Имя пользователя. 17 | last_name (Optional[str]): Фамилия пользователя. Может быть None. 18 | username (Optional[str]): Имя пользователя (ник). Может быть None. 19 | is_bot (bool): Флаг, указывающий, является ли пользователь ботом. 20 | last_activity_time (int): Временная метка последней активности. 21 | description (Optional[str]): Описание пользователя. Может быть None. 22 | avatar_url (Optional[str]): URL аватара пользователя. Может быть None. 23 | full_avatar_url (Optional[str]): URL полного аватара пользователя. Может быть None. 24 | commands (Optional[List[BotCommand]]): Список команд бота. Может быть None. 25 | """ 26 | 27 | user_id: int 28 | first_name: str 29 | last_name: Optional[str] = None 30 | username: Optional[str] = None 31 | is_bot: bool 32 | last_activity_time: int 33 | description: Optional[str] = None 34 | avatar_url: Optional[str] = None 35 | full_avatar_url: Optional[str] = None 36 | commands: Optional[List[BotCommand]] = None 37 | 38 | @property 39 | def full_name(self): 40 | if self.last_name is None: 41 | return self.first_name 42 | 43 | return f"{self.first_name} {self.last_name}" 44 | 45 | class Config: 46 | json_encoders = {datetime: lambda v: int(v.timestamp() * 1000)} 47 | 48 | 49 | class ChatAdmin(BaseModel): 50 | """ 51 | Модель администратора чата. 52 | 53 | Attributes: 54 | user_id (int): Уникальный идентификатор администратора. 55 | permissions (List[ChatPermission]): Список разрешений администратора. 56 | """ 57 | 58 | user_id: int 59 | permissions: List[ChatPermission] 60 | -------------------------------------------------------------------------------- /maxapi/methods/send_callback.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any, Dict, Optional, cast 4 | 5 | from ..connection.base import BaseConnection 6 | from ..enums.api_path import ApiPath 7 | from ..enums.http_method import HTTPMethod 8 | from ..methods.types.sended_callback import SendedCallback 9 | 10 | if TYPE_CHECKING: 11 | from ..bot import Bot 12 | from ..types.updates.message_callback import MessageForCallback 13 | 14 | 15 | class SendCallback(BaseConnection): 16 | """ 17 | Класс для отправки callback-ответа с опциональным сообщением и уведомлением. 18 | 19 | https://dev.max.ru/docs-api/methods/POST/answers 20 | 21 | Attributes: 22 | bot (Bot): Экземпляр бота. 23 | callback_id (str): Идентификатор callback. 24 | message (Optional[MessageForCallback]): Сообщение для отправки. 25 | notification (Optional[str]): Текст уведомления. 26 | """ 27 | 28 | def __init__( 29 | self, 30 | bot: "Bot", 31 | callback_id: str, 32 | message: Optional[MessageForCallback] = None, 33 | notification: Optional[str] = None, 34 | ): 35 | self.bot = bot 36 | self.callback_id = callback_id 37 | self.message = message 38 | self.notification = notification 39 | 40 | async def fetch(self) -> SendedCallback: 41 | """ 42 | Выполняет POST-запрос для отправки callback-ответа. 43 | 44 | Возвращает результат отправки. 45 | 46 | Returns: 47 | SendedCallback: Объект с результатом отправки callback. 48 | """ 49 | 50 | bot = self._ensure_bot() 51 | 52 | params = bot.params.copy() 53 | 54 | params["callback_id"] = self.callback_id 55 | 56 | json: Dict[str, Any] = {} 57 | 58 | if self.message: 59 | json["message"] = self.message.model_dump() 60 | if self.notification: 61 | json["notification"] = self.notification 62 | 63 | response = await super().request( 64 | method=HTTPMethod.POST, 65 | path=ApiPath.ANSWERS, 66 | model=SendedCallback, 67 | params=params, 68 | json=json, 69 | ) 70 | 71 | return cast(SendedCallback, response) 72 | -------------------------------------------------------------------------------- /maxapi/methods/add_admin_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..types.users import ChatAdmin 7 | from .types.added_admin_chat import AddedListAdminChat 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class AddAdminChat(BaseConnection): 14 | """ 15 | Класс для добавления списка администраторов в чат через API. 16 | 17 | https://dev.max.ru/docs-api/methods/POST/chats/-chatId-/members/admins 18 | 19 | Attributes: 20 | bot (Bot): Экземпляр бота, через который выполняется запрос. 21 | chat_id (int): Идентификатор чата. 22 | admins (List[ChatAdmin]): Список администраторов для добавления. 23 | marker (int, optional): Маркер для пагинации или дополнительных настроек. По умолчанию None. 24 | """ 25 | 26 | def __init__( 27 | self, 28 | bot: "Bot", 29 | chat_id: int, 30 | admins: List[ChatAdmin], 31 | marker: Optional[int] = None, 32 | ): 33 | self.bot = bot 34 | self.chat_id = chat_id 35 | self.admins = admins 36 | self.marker = marker 37 | 38 | async def fetch(self) -> AddedListAdminChat: 39 | """ 40 | Выполняет HTTP POST запрос для добавления администраторов в чат. 41 | 42 | Формирует JSON с данными администраторов и отправляет запрос на соответствующий API-эндпоинт. 43 | 44 | Returns: 45 | AddedListAdminChat: Результат операции с информацией об успешности. 46 | """ 47 | 48 | bot = self._ensure_bot() 49 | 50 | json: Dict[str, Any] = {} 51 | 52 | json["admins"] = [admin.model_dump() for admin in self.admins] 53 | json["marker"] = self.marker 54 | 55 | response = await super().request( 56 | method=HTTPMethod.POST, 57 | path=ApiPath.CHATS.value 58 | + "/" 59 | + str(self.chat_id) 60 | + ApiPath.MEMBERS 61 | + ApiPath.ADMINS, 62 | model=AddedListAdminChat, 63 | params=bot.params, 64 | json=json, 65 | ) 66 | 67 | return cast(AddedListAdminChat, response) 68 | -------------------------------------------------------------------------------- /maxapi/types/attachments/video.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Literal, Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from ...enums.attachment import AttachmentType 6 | from .attachment import Attachment 7 | 8 | if TYPE_CHECKING: 9 | from ...bot import Bot 10 | 11 | 12 | class VideoUrl(BaseModel): 13 | """ 14 | URLs различных разрешений видео. 15 | 16 | Attributes: 17 | mp4_1080 (Optional[str]): URL видео в 1080p. 18 | mp4_720 (Optional[str]): URL видео в 720p. 19 | mp4_480 (Optional[str]): URL видео в 480p. 20 | mp4_360 (Optional[str]): URL видео в 360p. 21 | mp4_240 (Optional[str]): URL видео в 240p. 22 | mp4_144 (Optional[str]): URL видео в 144p. 23 | hls (Optional[str]): URL HLS потока. 24 | """ 25 | 26 | mp4_1080: Optional[str] = None 27 | mp4_720: Optional[str] = None 28 | mp4_480: Optional[str] = None 29 | mp4_360: Optional[str] = None 30 | mp4_240: Optional[str] = None 31 | mp4_144: Optional[str] = None 32 | hls: Optional[str] = None 33 | 34 | 35 | class VideoThumbnail(BaseModel): 36 | """ 37 | Миниатюра видео. 38 | 39 | Attributes: 40 | url (str): URL миниатюры. 41 | """ 42 | 43 | url: str 44 | 45 | 46 | class Video(Attachment): 47 | """ 48 | Вложение с типом видео. 49 | 50 | Attributes: 51 | token (Optional[str]): Токен видео. 52 | urls (Optional[VideoUrl]): URLs видео разных разрешений. 53 | thumbnail (VideoThumbnail): Миниатюра видео. 54 | width (Optional[int]): Ширина видео. 55 | height (Optional[int]): Высота видео. 56 | duration (Optional[int]): Продолжительность видео в секундах. 57 | bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется. 58 | """ 59 | 60 | type: Literal[ 61 | AttachmentType.VIDEO 62 | ] # pyright: ignore[reportIncompatibleVariableOverride] 63 | token: Optional[str] = None 64 | urls: Optional[VideoUrl] = None 65 | thumbnail: VideoThumbnail 66 | width: Optional[int] = None 67 | height: Optional[int] = None 68 | duration: Optional[int] = None 69 | bot: Optional[Any] = Field( 70 | default=None, exclude=True 71 | ) # pyright: ignore[reportRedeclaration] 72 | 73 | if TYPE_CHECKING: 74 | bot: Optional["Bot"] # type: ignore 75 | -------------------------------------------------------------------------------- /maxapi/context/context.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Any, Dict, Optional, Union 3 | 4 | from ..context.state_machine import State 5 | 6 | 7 | class MemoryContext: 8 | """ 9 | Контекст хранения данных пользователя с блокировками. 10 | 11 | Args: 12 | chat_id (Optional[int]): Идентификатор чата 13 | user_id (Optional[int]): Идентификатор пользователя 14 | """ 15 | 16 | def __init__(self, chat_id: Optional[int], user_id: Optional[int]): 17 | self.chat_id = chat_id 18 | self.user_id = user_id 19 | self._context: Dict[str, Any] = {} 20 | self._state: State | str | None = None 21 | self._lock = asyncio.Lock() 22 | 23 | async def get_data(self) -> dict[str, Any]: 24 | """ 25 | Возвращает текущий контекст данных. 26 | 27 | Returns: 28 | Словарь с данными контекста 29 | """ 30 | 31 | async with self._lock: 32 | return self._context 33 | 34 | async def set_data(self, data: dict[str, Any]): 35 | """ 36 | Полностью заменяет контекст данных. 37 | 38 | Args: 39 | data: Новый словарь контекста 40 | """ 41 | 42 | async with self._lock: 43 | self._context = data 44 | 45 | async def update_data(self, **kwargs: Any) -> None: 46 | """ 47 | Обновляет контекст данных новыми значениями. 48 | 49 | Args: 50 | **kwargs: Пары ключ-значение для обновления 51 | """ 52 | 53 | async with self._lock: 54 | self._context.update(kwargs) 55 | 56 | async def set_state(self, state: Optional[Union[State, str]] = None): 57 | """ 58 | Устанавливает новое состояние. 59 | 60 | Args: 61 | state: Новое состояние или None для сброса 62 | """ 63 | 64 | async with self._lock: 65 | self._state = state 66 | 67 | async def get_state(self) -> Optional[State | str]: 68 | """ 69 | Возвращает текущее состояние. 70 | 71 | Returns: 72 | Текущее состояние или None 73 | """ 74 | 75 | async with self._lock: 76 | return self._state 77 | 78 | async def clear(self): 79 | """ 80 | Очищает контекст и сбрасывает состояние. 81 | """ 82 | 83 | async with self._lock: 84 | self._state = None 85 | self._context = {} 86 | -------------------------------------------------------------------------------- /maxapi/methods/types/getted_updates.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from ...enums.update import UpdateType 4 | from ...types.updates.bot_added import BotAdded 5 | from ...types.updates.bot_removed import BotRemoved 6 | from ...types.updates.bot_started import BotStarted 7 | from ...types.updates.bot_stopped import BotStopped 8 | from ...types.updates.chat_title_changed import ChatTitleChanged 9 | from ...types.updates.dialog_cleared import DialogCleared 10 | from ...types.updates.dialog_muted import DialogMuted 11 | from ...types.updates.dialog_removed import DialogRemoved 12 | from ...types.updates.dialog_unmuted import DialogUnmuted 13 | from ...types.updates.message_callback import MessageCallback 14 | from ...types.updates.message_chat_created import MessageChatCreated 15 | from ...types.updates.message_created import MessageCreated 16 | from ...types.updates.message_edited import MessageEdited 17 | from ...types.updates.message_removed import MessageRemoved 18 | from ...types.updates.user_added import UserAdded 19 | from ...types.updates.user_removed import UserRemoved 20 | from ...utils.updates import enrich_event 21 | 22 | if TYPE_CHECKING: 23 | from ...bot import Bot 24 | 25 | 26 | UPDATE_MODEL_MAPPING = { 27 | UpdateType.BOT_ADDED: BotAdded, 28 | UpdateType.BOT_REMOVED: BotRemoved, 29 | UpdateType.BOT_STARTED: BotStarted, 30 | UpdateType.CHAT_TITLE_CHANGED: ChatTitleChanged, 31 | UpdateType.MESSAGE_CALLBACK: MessageCallback, 32 | UpdateType.MESSAGE_CHAT_CREATED: MessageChatCreated, 33 | UpdateType.MESSAGE_CREATED: MessageCreated, 34 | UpdateType.MESSAGE_EDITED: MessageEdited, 35 | UpdateType.MESSAGE_REMOVED: MessageRemoved, 36 | UpdateType.USER_ADDED: UserAdded, 37 | UpdateType.USER_REMOVED: UserRemoved, 38 | UpdateType.BOT_STOPPED: BotStopped, 39 | UpdateType.DIALOG_CLEARED: DialogCleared, 40 | UpdateType.DIALOG_MUTED: DialogMuted, 41 | UpdateType.DIALOG_UNMUTED: DialogUnmuted, 42 | UpdateType.DIALOG_REMOVED: DialogRemoved, 43 | } 44 | 45 | 46 | async def get_update_model(event: dict, bot: "Bot"): 47 | update_type = event["update_type"] 48 | model_cls = UPDATE_MODEL_MAPPING.get(update_type) 49 | 50 | if not model_cls: 51 | raise ValueError(f"Unknown update type: {update_type}") 52 | 53 | event_object = await enrich_event(event_object=model_cls(**event), bot=bot) 54 | 55 | return event_object 56 | 57 | 58 | async def process_update_request(events: dict, bot: "Bot"): 59 | return [await get_update_model(event, bot) for event in events["updates"]] 60 | 61 | 62 | async def process_update_webhook(event_json: dict, bot: "Bot"): 63 | return await get_update_model(bot=bot, event=event_json) 64 | -------------------------------------------------------------------------------- /maxapi/methods/get_members_chat.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..methods.types.getted_members_chat import GettedMembersChat 7 | 8 | if TYPE_CHECKING: 9 | from ..bot import Bot 10 | 11 | 12 | class GetMembersChat(BaseConnection): 13 | """ 14 | Класс для получения списка участников чата через API. 15 | 16 | https://dev.max.ru/docs-api/methods/GET/chats/-chatId-/members 17 | 18 | Attributes: 19 | bot (Bot): Экземпляр бота для выполнения запроса. 20 | chat_id (int): Идентификатор чата. 21 | user_ids (Optional[List[str]]): Список ID пользователей для фильтрации. По умолчанию None. 22 | marker (Optional[int]): Маркер для пагинации (начальная позиция). По умолчанию None. 23 | count (Optional[int]): Максимальное количество участников для получения. По умолчанию None. 24 | 25 | """ 26 | 27 | def __init__( 28 | self, 29 | bot: "Bot", 30 | chat_id: int, 31 | user_ids: Optional[List[int]] = None, 32 | marker: Optional[int] = None, 33 | count: Optional[int] = None, 34 | ): 35 | if count is not None and not (1 <= count <= 100): 36 | raise ValueError("count не должен быть меньше 1 или больше 100") 37 | 38 | self.bot = bot 39 | self.chat_id = chat_id 40 | self.user_ids = user_ids 41 | self.marker = marker 42 | self.count = count 43 | 44 | async def fetch(self) -> GettedMembersChat: 45 | """ 46 | Выполняет GET-запрос для получения участников чата с опциональной фильтрацией. 47 | 48 | Формирует параметры запроса с учётом фильтров и передаёт их базовому методу. 49 | 50 | Returns: 51 | GettedMembersChat: Объект с данными по участникам чата. 52 | """ 53 | 54 | bot = self._ensure_bot() 55 | 56 | params = bot.params.copy() 57 | 58 | if self.user_ids: 59 | params["user_ids"] = ",".join( 60 | [str(user_id) for user_id in self.user_ids] 61 | ) 62 | 63 | if self.marker: 64 | params["marker"] = self.marker 65 | if self.count: 66 | params["count"] = self.count 67 | 68 | response = await super().request( 69 | method=HTTPMethod.GET, 70 | path=ApiPath.CHATS.value 71 | + "/" 72 | + str(self.chat_id) 73 | + ApiPath.MEMBERS, 74 | model=GettedMembersChat, 75 | params=params, 76 | ) 77 | 78 | return cast(GettedMembersChat, response) 79 | -------------------------------------------------------------------------------- /maxapi/methods/get_updates.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, cast 4 | 5 | from ..connection.base import BaseConnection 6 | from ..enums.api_path import ApiPath 7 | from ..enums.http_method import HTTPMethod 8 | from ..enums.update import UpdateType 9 | 10 | if TYPE_CHECKING: 11 | from ..bot import Bot 12 | 13 | 14 | class GetUpdates(BaseConnection): 15 | """ 16 | Класс для получения обновлений (updates) от API. 17 | 18 | https://dev.max.ru/docs-api/methods/GET/updates 19 | 20 | Запрашивает новые события для бота через long polling 21 | с возможностью фильтрации по типам и маркеру последнего обновления. 22 | 23 | Attributes: 24 | bot (Bot): Экземпляр бота. 25 | limit (int): Лимит на количество получаемых обновлений. 26 | timeout (int): Таймаут ожидания. 27 | marker (Optional[int]): ID последнего обработанного события. 28 | types (Optional[Sequence[UpdateType]]): Список типов событий для фильтрации. 29 | """ 30 | 31 | def __init__( 32 | self, 33 | bot: Bot, 34 | limit: Optional[int], 35 | timeout: Optional[int], 36 | marker: Optional[int] = None, 37 | types: Optional[Sequence[UpdateType]] = None, 38 | ): 39 | if limit is not None and not (1 <= limit <= 1000): 40 | raise ValueError("limit не должен быть меньше 1 и больше 1000") 41 | 42 | if timeout is not None and not (0 <= timeout <= 90): 43 | raise ValueError("timeout не должен быть меньше 0 и больше 90") 44 | 45 | self.bot = bot 46 | self.limit = limit 47 | self.timeout = timeout 48 | self.marker = marker 49 | self.types = types 50 | 51 | async def fetch(self) -> Dict[str, Any]: 52 | """ 53 | Выполняет GET-запрос к API для получения новых событий. 54 | 55 | Returns: 56 | Dict: Сырой JSON-ответ от API с новыми событиями. 57 | """ 58 | bot = self._ensure_bot() 59 | 60 | params = bot.params.copy() 61 | 62 | if self.limit: 63 | params["limit"] = self.limit 64 | if self.marker is not None: 65 | params["marker"] = self.marker 66 | if self.timeout is not None: 67 | params["timeout"] = self.timeout 68 | if self.types: 69 | params["types"] = ",".join(self.types) 70 | 71 | event_json = await super().request( 72 | method=HTTPMethod.GET, 73 | path=ApiPath.UPDATES, 74 | model=None, 75 | params=params, 76 | is_return_raw=True, 77 | ) 78 | 79 | return cast(Dict[str, Any], event_json) 80 | -------------------------------------------------------------------------------- /maxapi/methods/subscribe_webhook.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast 2 | 3 | from ..connection.base import BaseConnection 4 | from ..enums.api_path import ApiPath 5 | from ..enums.http_method import HTTPMethod 6 | from ..enums.update import UpdateType 7 | from ..methods.types.subscribed import Subscribed 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class SubscribeWebhook(BaseConnection): 14 | """ 15 | Подписывает бота на получение обновлений через WebHook. 16 | После вызова этого метода бот будет получать уведомления о новых событиях в чатах на указанный URL. 17 | Ваш сервер должен прослушивать один из следующих портов: `80`, `8080`, `443`, `8443`, `16384`-`32383`. 18 | 19 | Attributes: 20 | bot (Bot): Экземпляр бота для выполнения запроса. 21 | url (str): URL HTTP(S)-эндпойнта вашего бота. Должен начинаться с http(s):// 22 | update_types (Optional[List[str]]): Список типов обновлений, которые ваш бот хочет получать. Для полного списка типов см. объект 23 | secret (Optional[str]): От 5 до 256 символов. Cекрет, который должен быть отправлен в заголовке X-Max-Bot-Api-Secret в каждом запросе Webhook. 24 | Разрешены только символы A-Z, a-z, 0-9, и дефис. Заголовок рекомендован, чтобы запрос поступал из установленного веб-узла 25 | """ 26 | 27 | def __init__( 28 | self, 29 | bot: "Bot", 30 | url: str, 31 | update_types: Optional[List[UpdateType]] = None, 32 | secret: Optional[str] = None, 33 | ): 34 | if secret is not None and not (5 <= len(secret) <= 256): 35 | raise ValueError( 36 | "secret не должен быть меньше 5 или больше 256 символов" 37 | ) 38 | 39 | self.bot = bot 40 | self.url = url 41 | self.update_types = update_types 42 | self.secret = secret 43 | 44 | async def fetch(self) -> Subscribed: 45 | """ 46 | Отправляет запрос на подписку бота на получение обновлений через WebHook 47 | 48 | Returns: 49 | Subscribed: Объект с информацией об операции 50 | """ 51 | 52 | bot = self._ensure_bot() 53 | 54 | json: Dict[str, Any] = {} 55 | 56 | json["url"] = self.url 57 | 58 | if self.update_types: 59 | json["update_types"] = self.update_types 60 | 61 | if self.secret: 62 | json["secret"] = self.secret 63 | 64 | response = await super().request( 65 | method=HTTPMethod.POST, 66 | path=ApiPath.SUBSCRIPTIONS, 67 | model=Subscribed, 68 | params=bot.params, 69 | json=json, 70 | ) 71 | 72 | return cast(Subscribed, response) 73 | -------------------------------------------------------------------------------- /maxapi/utils/message.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from json import loads 4 | from typing import TYPE_CHECKING 5 | from uuid import uuid4 6 | 7 | from ..enums.upload_type import UploadType 8 | from ..exceptions.max import MaxApiError, MaxUploadFileFailed 9 | from ..types.attachments.upload import AttachmentPayload, AttachmentUpload 10 | from ..types.input_media import InputMedia, InputMediaBuffer 11 | 12 | if TYPE_CHECKING: 13 | from ..bot import Bot 14 | from ..connection.base import BaseConnection 15 | 16 | 17 | async def process_input_media( 18 | base_connection: BaseConnection, 19 | bot: Bot, 20 | att: InputMedia | InputMediaBuffer, 21 | ) -> AttachmentUpload: 22 | # очень нестабильный метод независящий от модуля 23 | # ждем обновлений MAX API 24 | 25 | """ 26 | Загружает файл вложения и формирует объект AttachmentUpload. 27 | 28 | Args: 29 | base_connection (BaseConnection): Базовое соединение для загрузки файла. 30 | bot (Bot): Экземпляр бота. 31 | att (InputMedia | InputMediaBuffer): Объект вложения для загрузки. 32 | 33 | Returns: 34 | AttachmentUpload: Загруженное вложение с токеном. 35 | """ 36 | 37 | try: 38 | upload = await bot.get_upload_url(att.type) 39 | except MaxApiError as e: 40 | raise MaxUploadFileFailed( 41 | f"Ошибка при загрузке файла: code={e.code}, raw={e.raw}" 42 | ) 43 | 44 | if isinstance(att, InputMedia): 45 | upload_file_response = await base_connection.upload_file( 46 | url=upload.url, 47 | path=att.path, 48 | type=att.type, 49 | ) 50 | elif isinstance(att, InputMediaBuffer): 51 | upload_file_response = await base_connection.upload_file_buffer( 52 | filename=att.filename or str(uuid4()), 53 | url=upload.url, 54 | buffer=att.buffer, 55 | type=att.type, 56 | ) 57 | 58 | token = "" 59 | 60 | if att.type in (UploadType.VIDEO, UploadType.AUDIO): 61 | if upload.token is None: 62 | if bot.session is not None: 63 | await bot.session.close() 64 | 65 | raise MaxUploadFileFailed( 66 | "По неизвестной причине token не был получен" 67 | ) 68 | 69 | token = upload.token 70 | 71 | elif att.type == UploadType.FILE: 72 | json_r = loads(upload_file_response) 73 | token = json_r["token"] 74 | 75 | elif att.type == UploadType.IMAGE: 76 | json_r = loads(upload_file_response) 77 | json_r_keys = list(json_r["photos"].keys()) 78 | token = json_r["photos"][json_r_keys[0]]["token"] 79 | 80 | return AttachmentUpload( 81 | type=att.type, payload=AttachmentPayload(token=token) 82 | ) 83 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Документация библиотеки maxapi 2 | 3 | **MaxAPI** — современная асинхронная Python-библиотека для разработки чат-ботов с помощью API мессенджера MAX. 4 | 5 | Библиотека предоставляет удобный и типобезопасный интерфейс для работы с API MAX, поддерживает асинхронную работу, гибкую систему фильтров, middleware, роутеры и множество других возможностей для создания мощных чат-ботов. 6 | 7 | ## Быстрый старт 8 | 9 | Установите библиотеку через pip: 10 | 11 | ```bash 12 | pip install maxapi 13 | ``` 14 | 15 | Для работы с Webhook установите дополнительные зависимости: 16 | 17 | ```bash 18 | pip install maxapi[webhook] 19 | ``` 20 | 21 | ### Простой пример 22 | 23 | Создайте простого эхо-бота, который отвечает на команду `/start`: 24 | 25 | ```python 26 | import asyncio 27 | import logging 28 | 29 | from maxapi import Bot, Dispatcher 30 | from maxapi.types import MessageCreated, Command 31 | 32 | # Настройка логирования 33 | logging.basicConfig(level=logging.INFO) 34 | 35 | # Инициализация бота и диспетчера 36 | # Токен можно задать через переменную окружения MAX_BOT_TOKEN 37 | # или передать напрямую: Bot(token='ваш_токен') 38 | bot = Bot() 39 | dp = Dispatcher() 40 | 41 | # Обработчик команды /start 42 | @dp.message_created(Command('start')) 43 | async def start_handler(event: MessageCreated): 44 | await event.message.answer("Привет! 👋\nЯ простой бот на MaxAPI.") 45 | 46 | # Обработчик всех текстовых сообщений 47 | @dp.message_created() 48 | async def echo_handler(event: MessageCreated): 49 | if event.message.body.text: 50 | await event.message.answer(f"Вы написали: {event.message.body.text}") 51 | 52 | async def main(): 53 | # Запуск бота в режиме polling 54 | await dp.start_polling(bot) 55 | 56 | if __name__ == '__main__': 57 | asyncio.run(main()) 58 | ``` 59 | 60 | !!! warning "Важно" 61 | Если вы тестируете бота в чате, не забудьте дать ему права администратора! 62 | 63 | Если у бота установлены подписки на Webhook, события не будут приходить при методе `start_polling`. 64 | Удалите подписки через `await bot.delete_webhook()` перед `start_polling`. 65 | 66 | ### Пример с фильтрами 67 | 68 | Использование MagicFilter для более гибкой фильтрации: 69 | 70 | ```python 71 | from maxapi import Bot, Dispatcher, F 72 | from maxapi.types import MessageCreated 73 | 74 | bot = Bot() 75 | dp = Dispatcher() 76 | 77 | # Обработчик только текстовых сообщений 78 | @dp.message_created(F.message.body.text) 79 | async def text_handler(event: MessageCreated): 80 | text = event.message.body.text 81 | await event.message.answer(f"Длина вашего сообщения: {len(text)} символов") 82 | 83 | # Обработчик сообщений с вложениями 84 | @dp.message_created(F.message.attachments) 85 | async def attachment_handler(event: MessageCreated): 86 | await event.message.answer("Вы отправили вложение!") 87 | ``` 88 | 89 | ## Лицензия 90 | 91 | См. файл [LICENSE](https://github.com/love-apples/maxapi/blob/main/LICENSE) для подробной информации. -------------------------------------------------------------------------------- /docs/guides/handlers.md: -------------------------------------------------------------------------------- 1 | # Обработчики событий 2 | 3 | ## Синтаксис 4 | 5 | ```python 6 | @dp.<тип_события>(<фильтры>, , ...) 7 | async def handler(event: <тип_события>, context: MemoryContext, ...): 8 | ... 9 | ``` 10 | 11 | ## Примеры 12 | 13 | ### Обработка команды 14 | 15 | ```python 16 | from maxapi.types import MessageCreated, Command 17 | 18 | @dp.message_created(Command('start')) 19 | async def start_handler(event: MessageCreated): 20 | await event.message.answer("Привет!") 21 | ``` 22 | 23 | ### Обработка с фильтром 24 | 25 | ```python 26 | from maxapi import F 27 | 28 | @dp.message_created(F.message.body.text) 29 | async def text_handler(event: MessageCreated): 30 | await event.message.answer(f"Вы написали: {event.message.body.text}") 31 | ``` 32 | 33 | ### Обработка без фильтра 34 | 35 | ```python 36 | @dp.message_created() 37 | async def any_message(event: MessageCreated): 38 | await event.message.answer("Получено сообщение") 39 | ``` 40 | 41 | ### Комбинация фильтров и состояний 42 | 43 | ```python 44 | from maxapi.context import State, StatesGroup 45 | 46 | class Form(StatesGroup): 47 | name = State() 48 | 49 | @dp.message_created(F.message.body.text, Form.name) 50 | async def name_handler(event: MessageCreated, context: MemoryContext): 51 | await context.update_data(name=event.message.body.text) 52 | await event.message.answer(f"Привет, {event.message.body.text}!") 53 | ``` 54 | 55 | ### Обработка с контекстом 56 | 57 | ```python 58 | @dp.message_created(Command('data')) 59 | async def data_handler(event: MessageCreated, context: MemoryContext): 60 | data = await context.get_data() 61 | await event.message.answer(f"Данные: {data}") 62 | ``` 63 | 64 | ## Доступные события 65 | 66 | ### События сообщений 67 | 68 | - `message_created` — создание нового сообщения 69 | - `message_edited` — редактирование сообщения 70 | - `message_removed` — удаление сообщения 71 | - `message_callback` — нажатие на callback-кнопку 72 | - `message_chat_created` — создание чата через сообщение 73 | 74 | ### События бота 75 | 76 | - `bot_added` — бот добавлен в чат 77 | - `bot_removed` — бот удален из чата 78 | - `bot_started` — пользователь нажал кнопку "Начать" с ботом 79 | - `bot_stopped` — бот остановлен 80 | 81 | ### События пользователей 82 | 83 | - `user_added` — пользователь добавлен в чат 84 | - `user_removed` — пользователь удален из чата 85 | 86 | ### События чата 87 | 88 | - `chat_title_changed` — изменено название чата 89 | 90 | ### События диалога 91 | 92 | - `dialog_cleared` — диалог очищен 93 | - `dialog_muted` — диалог заглушен (уведомления отключены) 94 | - `dialog_unmuted` — диалог разглушен (уведомления включены) 95 | - `dialog_removed` — диалог удален 96 | 97 | ### Служебные события 98 | 99 | - `on_started` — событие при старте диспетчера (после инициализации) 100 | 101 | Подробнее о типах событий см. [Updates](../types/updates/index.md) 102 | 103 | -------------------------------------------------------------------------------- /docs/guides/filters.md: -------------------------------------------------------------------------------- 1 | # Фильтры 2 | 3 | Фильтры определяют, когда должен сработать обработчик. Можно использовать несколько фильтров одновременно. 4 | 5 | ## MagicFilter (F) 6 | 7 | Гибкая система фильтрации через объект `F`: 8 | 9 | ```python 10 | from maxapi import F 11 | 12 | # Только текстовые сообщения 13 | @dp.message_created(F.message.body.text) 14 | async def text_handler(event: MessageCreated): 15 | ... 16 | 17 | # Сообщения с вложениями 18 | @dp.message_created(F.message.attachments) 19 | async def attachment_handler(event: MessageCreated): 20 | ... 21 | 22 | # Комбинация условий 23 | from maxapi.enums.chat_type import ChatType 24 | 25 | @dp.message_created(F.message.body.text & F.message.chat.type == ChatType.PRIVATE) 26 | async def private_text_handler(event: MessageCreated): 27 | ... 28 | ``` 29 | 30 | ## Command фильтр 31 | 32 | ```python 33 | from maxapi.types import Command 34 | 35 | # Одна команда 36 | @dp.message_created(Command('start')) 37 | async def start_handler(event: MessageCreated): 38 | ... 39 | 40 | # Несколько команд 41 | @dp.message_created(Command(['start', 'help', 'info'])) 42 | async def commands_handler(event: MessageCreated): 43 | ... 44 | ``` 45 | 46 | ## Callback Payload фильтр 47 | 48 | ```python 49 | from maxapi.filters.callback_payload import CallbackPayload 50 | 51 | # Простой payload (строка) 52 | @dp.message_callback(F.callback.payload == 'button_click') 53 | async def callback_handler(event: MessageCallback): 54 | ... 55 | 56 | # Структурированный payload (класс) 57 | class MyPayload(CallbackPayload, prefix='mypayload'): 58 | action: str 59 | value: int 60 | 61 | # Без дополнительных условий 62 | @dp.message_callback(MyPayload.filter()) 63 | async def callback_handler(event: MessageCallback, payload: MyPayload): 64 | await event.answer(f"Action: {payload.action}, Value: {payload.value}") 65 | 66 | # С дополнительным фильтром 67 | @dp.message_callback(MyPayload.filter(F.action == 'edit')) 68 | async def callback_handler(event: MessageCallback, payload: MyPayload): 69 | await event.answer(f"Edit action: {payload.value}") 70 | ``` 71 | 72 | ## Комбинация фильтров 73 | 74 | ```python 75 | # И (AND) 76 | F.message.body.text & F.message.chat.type == ChatType.PRIVATE 77 | 78 | # Или (OR) 79 | F.message.body.text | F.message.attachments 80 | 81 | # Отрицание (NOT) 82 | ~F.message.body.text 83 | 84 | # Несколько фильтров в декораторе 85 | @dp.message_created(F.message.body.text, Command('start'), Form.name) 86 | async def handler(event: MessageCreated): 87 | ... 88 | ``` 89 | 90 | ## Базовые фильтры (BaseFilter) 91 | 92 | Можно создать собственный фильтр, наследуясь от `BaseFilter`: 93 | 94 | ```python 95 | from maxapi.filters.filter import BaseFilter 96 | 97 | class MyFilter(BaseFilter): 98 | async def __call__(self, event): 99 | # Возвращает True/False или dict с данными 100 | return True 101 | ``` 102 | 103 | -------------------------------------------------------------------------------- /maxapi/types/__init__.py: -------------------------------------------------------------------------------- 1 | from ..filters.command import Command, CommandStart 2 | from ..types.attachments.attachment import ( 3 | Attachment, 4 | ButtonsPayload, 5 | ContactAttachmentPayload, 6 | OtherAttachmentPayload, 7 | PhotoAttachmentPayload, 8 | StickerAttachmentPayload, 9 | ) 10 | from ..types.attachments.buttons.callback_button import CallbackButton 11 | from ..types.attachments.buttons.chat_button import ChatButton 12 | from ..types.attachments.buttons.link_button import LinkButton 13 | from ..types.attachments.buttons.message_button import MessageButton 14 | from ..types.attachments.buttons.open_app_button import OpenAppButton 15 | from ..types.attachments.buttons.request_contact import RequestContactButton 16 | from ..types.attachments.buttons.request_geo_location_button import ( 17 | RequestGeoLocationButton, 18 | ) 19 | from ..types.attachments.image import PhotoAttachmentRequestPayload 20 | from ..types.command import BotCommand 21 | from ..types.message import Message, NewMessageLink 22 | from ..types.updates import UpdateUnion 23 | from ..types.updates.bot_added import BotAdded 24 | from ..types.updates.bot_removed import BotRemoved 25 | from ..types.updates.bot_started import BotStarted 26 | from ..types.updates.bot_stopped import BotStopped 27 | from ..types.updates.chat_title_changed import ChatTitleChanged 28 | from ..types.updates.dialog_cleared import DialogCleared 29 | from ..types.updates.dialog_muted import DialogMuted 30 | from ..types.updates.dialog_unmuted import DialogUnmuted 31 | from ..types.updates.message_callback import MessageCallback 32 | from ..types.updates.message_chat_created import MessageChatCreated 33 | from ..types.updates.message_created import MessageCreated 34 | from ..types.updates.message_edited import MessageEdited 35 | from ..types.updates.message_removed import MessageRemoved 36 | from ..types.updates.user_added import UserAdded 37 | from ..types.updates.user_removed import UserRemoved 38 | from .input_media import InputMedia, InputMediaBuffer 39 | 40 | __all__ = [ 41 | "NewMessageLink", 42 | "PhotoAttachmentRequestPayload", 43 | "DialogUnmuted", 44 | "DialogMuted", 45 | "DialogCleared", 46 | "BotStopped", 47 | "CommandStart", 48 | "OpenAppButton", 49 | "Message", 50 | "Attachment", 51 | "InputMediaBuffer", 52 | "MessageButton", 53 | "UpdateUnion", 54 | "InputMedia", 55 | "BotCommand", 56 | "CallbackButton", 57 | "ChatButton", 58 | "LinkButton", 59 | "RequestContactButton", 60 | "RequestGeoLocationButton", 61 | "Command", 62 | "PhotoAttachmentPayload", 63 | "OtherAttachmentPayload", 64 | "ContactAttachmentPayload", 65 | "ButtonsPayload", 66 | "StickerAttachmentPayload", 67 | "BotAdded", 68 | "BotRemoved", 69 | "BotStarted", 70 | "ChatTitleChanged", 71 | "MessageCallback", 72 | "MessageChatCreated", 73 | "MessageCreated", 74 | "MessageEdited", 75 | "MessageRemoved", 76 | "UserAdded", 77 | "UserRemoved", 78 | ] 79 | -------------------------------------------------------------------------------- /maxapi/methods/get_messages.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import TYPE_CHECKING, List, Optional, Union, cast 3 | 4 | from ..connection.base import BaseConnection 5 | from ..enums.api_path import ApiPath 6 | from ..enums.http_method import HTTPMethod 7 | from ..types.message import Messages 8 | 9 | if TYPE_CHECKING: 10 | from ..bot import Bot 11 | 12 | 13 | class GetMessages(BaseConnection): 14 | """ 15 | Класс для получения сообщений из чата через API. 16 | 17 | https://dev.max.ru/docs-api/methods/GET/messages 18 | 19 | Attributes: 20 | bot (Bot): Экземпляр бота. 21 | chat_id (int): Идентификатор чата. 22 | message_ids (List[str] | None): Фильтр по идентификаторам сообщений. 23 | from_time (datetime | int | None): Начальная временная метка. 24 | to_time (datetime | int | None): Конечная временная метка. 25 | count (int): Максимальное число сообщений. 26 | """ 27 | 28 | def __init__( 29 | self, 30 | bot: "Bot", 31 | chat_id: Optional[int] = None, 32 | message_ids: Optional[List[str]] = None, 33 | from_time: Optional[Union[datetime, int]] = None, 34 | to_time: Optional[Union[datetime, int]] = None, 35 | count: int = 50, 36 | ): 37 | if count is not None and not (1 <= count <= 100): 38 | raise ValueError("count не должен быть меньше 1 или больше 100") 39 | 40 | self.bot = bot 41 | self.chat_id = chat_id 42 | self.message_ids = message_ids 43 | self.from_time = from_time 44 | self.to_time = to_time 45 | self.count = count 46 | 47 | async def fetch(self) -> Messages: 48 | """ 49 | Выполняет GET-запрос для получения сообщений с учётом параметров фильтрации. 50 | 51 | Преобразует datetime в UNIX timestamp при необходимости. 52 | 53 | Returns: 54 | Messages: Объект с полученными сообщениями. 55 | """ 56 | 57 | bot = self._ensure_bot() 58 | 59 | params = bot.params.copy() 60 | 61 | if self.chat_id: 62 | params["chat_id"] = self.chat_id 63 | 64 | if self.message_ids: 65 | params["message_ids"] = ",".join(self.message_ids) 66 | 67 | if self.from_time: 68 | if isinstance(self.from_time, datetime): 69 | params["from"] = int(self.from_time.timestamp() * 1000) 70 | else: 71 | params["from"] = self.from_time 72 | 73 | if self.to_time: 74 | if isinstance(self.to_time, datetime): 75 | params["to"] = int(self.to_time.timestamp() * 1000) 76 | else: 77 | params["to"] = self.to_time 78 | 79 | params["count"] = self.count 80 | 81 | response = await super().request( 82 | method=HTTPMethod.GET, 83 | path=ApiPath.MESSAGES, 84 | model=Messages, 85 | params=params, 86 | ) 87 | 88 | return cast(Messages, response) 89 | -------------------------------------------------------------------------------- /maxapi/types/attachments/attachment.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Any, List, Optional, Union 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from ...enums.attachment import AttachmentType 6 | from ...types.attachments.buttons import InlineButtonUnion 7 | from ...types.attachments.upload import AttachmentUpload 8 | from ...types.users import User 9 | 10 | if TYPE_CHECKING: 11 | from ...bot import Bot 12 | 13 | 14 | class StickerAttachmentPayload(BaseModel): 15 | """ 16 | Данные для вложения типа стикер. 17 | 18 | Attributes: 19 | url (str): URL стикера. 20 | code (str): Код стикера. 21 | """ 22 | 23 | url: str 24 | code: str 25 | 26 | 27 | class PhotoAttachmentPayload(BaseModel): 28 | """ 29 | Данные для фото-вложения. 30 | 31 | Attributes: 32 | photo_id (int): Идентификатор фотографии. 33 | token (str): Токен для доступа к фото. 34 | url (str): URL фотографии. 35 | """ 36 | 37 | photo_id: int 38 | token: str 39 | url: str 40 | 41 | 42 | class OtherAttachmentPayload(BaseModel): 43 | """ 44 | Данные для общих типов вложений (файлы и т.п.). 45 | 46 | Attributes: 47 | url (str): URL вложения. 48 | token (Optional[str]): Опциональный токен доступа. 49 | """ 50 | 51 | url: str 52 | token: Optional[str] = None 53 | 54 | 55 | class ContactAttachmentPayload(BaseModel): 56 | """ 57 | Данные для контакта. 58 | 59 | Attributes: 60 | vcf_info (Optional[str]): Информация в формате vcf. 61 | max_info (Optional[User]): Дополнительная информация о пользователе. 62 | """ 63 | 64 | vcf_info: str = "" # для корректного определения 65 | max_info: Optional[User] = None 66 | 67 | 68 | class ButtonsPayload(BaseModel): 69 | """ 70 | Данные для вложения с кнопками. 71 | 72 | Attributes: 73 | buttons (List[List[InlineButtonUnion]]): Двумерный список inline-кнопок. 74 | """ 75 | 76 | buttons: List[List[InlineButtonUnion]] 77 | 78 | def pack(self): 79 | return Attachment( 80 | type=AttachmentType.INLINE_KEYBOARD, payload=self 81 | ) # type: ignore 82 | 83 | 84 | class Attachment(BaseModel): 85 | """ 86 | Универсальный класс вложения с типом и полезной нагрузкой. 87 | 88 | Attributes: 89 | type (AttachmentType): Тип вложения. 90 | payload (Optional[Union[...] ]): Полезная нагрузка, зависит от типа вложения. 91 | """ 92 | 93 | type: AttachmentType 94 | payload: Optional[ 95 | Union[ 96 | AttachmentUpload, 97 | PhotoAttachmentPayload, 98 | OtherAttachmentPayload, 99 | ButtonsPayload, 100 | ContactAttachmentPayload, 101 | StickerAttachmentPayload, 102 | ] 103 | ] = None 104 | bot: Optional[Any] = Field( # pyright: ignore[reportRedeclaration] 105 | default=None, exclude=True 106 | ) 107 | 108 | if TYPE_CHECKING: 109 | bot: Optional[Bot] # type: ignore 110 | 111 | class Config: 112 | use_enum_values = True 113 | -------------------------------------------------------------------------------- /maxapi/types/input_media.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import puremagic 4 | 5 | from ..enums.upload_type import UploadType 6 | 7 | 8 | class InputMedia: 9 | """ 10 | Класс для представления медиафайла. 11 | 12 | Attributes: 13 | path (str): Путь к файлу. 14 | type (UploadType): Тип файла, определенный на основе содержимого (MIME-типа). 15 | """ 16 | 17 | def __init__(self, path: str): 18 | """ 19 | Инициализирует объект медиафайла. 20 | 21 | Args: 22 | path (str): Путь к файлу. 23 | """ 24 | 25 | self.path = path 26 | self.type = self.__detect_file_type(path) 27 | 28 | def __detect_file_type(self, path: str) -> UploadType: 29 | """ 30 | Определяет тип файла на основе его содержимого (MIME-типа). 31 | 32 | Args: 33 | path (str): Путь к файлу. 34 | 35 | Returns: 36 | UploadType: Тип файла (VIDEO, IMAGE, AUDIO или FILE). 37 | """ 38 | 39 | with open(path, "rb") as f: 40 | sample = f.read(4096) 41 | 42 | try: 43 | matches = puremagic.magic_string(sample) 44 | if matches: 45 | mime_type = matches[0].mime_type 46 | else: 47 | mime_type = None 48 | except Exception: 49 | mime_type = None 50 | 51 | if mime_type is None: 52 | return UploadType.FILE 53 | 54 | if mime_type.startswith("video/"): 55 | return UploadType.VIDEO 56 | elif mime_type.startswith("image/"): 57 | return UploadType.IMAGE 58 | elif mime_type.startswith("audio/"): 59 | return UploadType.AUDIO 60 | else: 61 | return UploadType.FILE 62 | 63 | 64 | class InputMediaBuffer: 65 | """ 66 | Класс для представления медиафайла из буфера. 67 | 68 | Attributes: 69 | buffer (bytes): Буфер с содержимым файла. 70 | type (UploadType): Тип файла, определенный по содержимому. 71 | """ 72 | 73 | def __init__(self, buffer: bytes, filename: str | None = None): 74 | """ 75 | Инициализирует объект медиафайла из буфера. 76 | 77 | Args: 78 | buffer (IO): Буфер с содержимым файла. 79 | filename (str): Название файла (по умолчанию присваивается uuid4). 80 | """ 81 | 82 | self.filename = filename 83 | self.buffer = buffer 84 | self.type = self.__detect_file_type(buffer) 85 | 86 | def __detect_file_type(self, buffer: bytes) -> UploadType: 87 | try: 88 | matches = puremagic.magic_string(buffer) 89 | if matches: 90 | mime_type = matches[0].mime_type 91 | else: 92 | mime_type = None 93 | except Exception: 94 | mime_type = None 95 | 96 | if mime_type is None: 97 | return UploadType.FILE 98 | if mime_type.startswith("video/"): 99 | return UploadType.VIDEO 100 | elif mime_type.startswith("image/"): 101 | return UploadType.IMAGE 102 | elif mime_type.startswith("audio/"): 103 | return UploadType.AUDIO 104 | else: 105 | return UploadType.FILE 106 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to PyPI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - 'pyproject.toml' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | check-version: 12 | runs-on: ubuntu-latest 13 | outputs: 14 | version: ${{ steps.version.outputs.version }} 15 | changed: ${{ steps.changed.outputs.changed }} 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | fetch-depth: 0 # Нужна полная история для сравнения 21 | 22 | - name: Set up Python 23 | uses: actions/setup-python@v5 24 | with: 25 | python-version: "3.11" 26 | 27 | - name: Get current version 28 | id: version 29 | run: | 30 | python -c "import tomllib; f = open('pyproject.toml', 'rb'); data = tomllib.load(f); print(data['project']['version'])" > /tmp/current_version.txt 31 | VERSION=$(cat /tmp/current_version.txt) 32 | echo "version=$VERSION" >> $GITHUB_OUTPUT 33 | echo "Current version: $VERSION" 34 | 35 | - name: Check if version changed 36 | id: changed 37 | run: | 38 | CURRENT_VERSION=$(cat /tmp/current_version.txt) 39 | 40 | # Получаем версию из предыдущего коммита 41 | PREVIOUS_VERSION=$(git show HEAD~1:pyproject.toml 2>/dev/null | python -c "import sys, tomllib; data = tomllib.load(sys.stdin.buffer); print(data['project']['version'])" 2>/dev/null || echo "") 42 | 43 | if [ -z "$PREVIOUS_VERSION" ]; then 44 | # Если нет предыдущей версии, проверяем изменения в файле 45 | if git diff --quiet HEAD~1 HEAD -- pyproject.toml 2>/dev/null; then 46 | echo "changed=false" >> $GITHUB_OUTPUT 47 | echo "No changes in pyproject.toml" 48 | else 49 | echo "changed=true" >> $GITHUB_OUTPUT 50 | echo "pyproject.toml changed (first publish or version file modified)" 51 | fi 52 | elif [ "$CURRENT_VERSION" != "$PREVIOUS_VERSION" ]; then 53 | echo "changed=true" >> $GITHUB_OUTPUT 54 | echo "Version changed from $PREVIOUS_VERSION to $CURRENT_VERSION" 55 | else 56 | echo "changed=false" >> $GITHUB_OUTPUT 57 | echo "Version not changed: $CURRENT_VERSION" 58 | fi 59 | 60 | publish: 61 | needs: check-version 62 | if: needs.check-version.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' 63 | runs-on: ubuntu-latest 64 | permissions: 65 | contents: read 66 | id-token: write # Для Trusted Publishing на PyPI 67 | 68 | steps: 69 | - name: Checkout repository 70 | uses: actions/checkout@v4 71 | 72 | - name: Install uv 73 | uses: astral-sh/setup-uv@v4 74 | with: 75 | version: "latest" 76 | 77 | - name: Set up Python 78 | uses: actions/setup-python@v5 79 | with: 80 | python-version: "3.11" 81 | 82 | - name: Install build dependencies 83 | run: | 84 | uv sync --all-groups 85 | 86 | - name: Build package 87 | run: | 88 | uv build 89 | 90 | - name: Publish to PyPI 91 | uses: pypa/gh-action-pypi-publish@release/v1 92 | with: 93 | packages-dir: dist/ 94 | print-hash: true 95 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "maxapi" 3 | version = "0.9.11" 4 | description = "Библиотека для разработки чат-ботов с помощью API мессенджера MAX" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | authors = [{ name = "Denis", email = "bestloveapples@gmail.com" }] 8 | keywords = ["max", "api", "bot"] 9 | classifiers = [ 10 | "Programming Language :: Python :: 3.10", 11 | "Programming Language :: Python :: 3.11", 12 | ] 13 | dependencies = [ 14 | "aiohttp>=3.12.14", 15 | "magic_filter>=1.0.0", 16 | "pydantic>=1.8.0", 17 | "aiofiles==24.1.0", 18 | "puremagic==1.30", 19 | ] 20 | 21 | [project.urls] 22 | Homepage = "https://github.com/love-apples/maxapi" 23 | 24 | [tool.setuptools] 25 | license-files = [] 26 | 27 | [build-system] 28 | requires = ["setuptools>=68.0.0", "wheel"] 29 | build-backend = "setuptools.build_meta" 30 | 31 | [tool.setuptools.packages.find] 32 | include = ["maxapi*", "wiki*", "examples*"] 33 | 34 | [project.optional-dependencies] 35 | webhook = ["fastapi>=0.68.0", "uvicorn>=0.15.0"] 36 | 37 | [dependency-groups] 38 | dev = [ 39 | "pytest>=7.0", 40 | "pytest-asyncio>=0.21", 41 | "pytest-cov>=4.1", 42 | "ruff>=0.12", 43 | "black>=24.1", 44 | "isort>=5.12", 45 | "mypy>=1.2", 46 | "pre-commit>=3.0", 47 | "coverage>=6.5", 48 | "mkdocs>=1.5.0", 49 | "mkdocs-material>=9.5.0", 50 | "mkdocstrings[python]>=0.24.0", 51 | "pymdown-extensions>=10.0", 52 | "python-dotenv>=1.0.0", 53 | ] 54 | 55 | [tool.uv] 56 | # Enable uv project management and include `dev` group by default when running/syncing 57 | default-groups = ["dev"] 58 | managed = true 59 | 60 | [tool.black] 61 | line-length = 79 62 | target-version = ["py310", "py311", "py312"] 63 | 64 | [tool.isort] 65 | py_version = 310 66 | profile = "black" 67 | line_length = 79 68 | force_single_line = false 69 | force_sort_within_sections = false 70 | from_first = false 71 | order_by_type = true 72 | no_sections = false 73 | import_heading_firstparty = "Core Stuff" 74 | skip = ["test*.py", "test_all_features.py", "examples", ".venv", "venv", "env"] 75 | skip_glob = ["test*.py", "examples/*", ".venv/**", "venv/**", "env/**", "**/.venv/**", "**/venv/**", "**/env/**"] 76 | 77 | [tool.mypy] 78 | python_version = "3.12" 79 | namespace_packages = true 80 | ignore_missing_imports = true 81 | exclude = ["core/yandex/cloud", "core/google"] 82 | check_untyped_defs = true 83 | 84 | [tool.pytest.ini_options] 85 | # Путь к тестам 86 | testpaths = ["tests"] 87 | 88 | # Автоматический режим для asyncio 89 | asyncio_mode = "auto" 90 | 91 | # Параметры запуска 92 | addopts = "-ra --strict-markers -v -W ignore::pytest.PytestUnraisableExceptionWarning" 93 | 94 | # Маркеры 95 | markers = [ 96 | "integration: интеграционные тесты, требующие реальный токен бота", 97 | "slow: медленные тесты", 98 | ] 99 | 100 | # Фильтры предупреждений 101 | filterwarnings = [ 102 | "ignore::DeprecationWarning", 103 | "ignore::PendingDeprecationWarning", 104 | "ignore::pytest.PytestUnraisableExceptionWarning", 105 | ] 106 | 107 | 108 | [tool.ruff] 109 | exclude = ["core/yandex/cloud", "core/google"] 110 | line-length = 79 111 | indent-width = 4 112 | target-version = "py312" 113 | 114 | [tool.pyright] 115 | root = ['core'] 116 | exclude = ["core/yandex/cloud", "core/google"] 117 | -------------------------------------------------------------------------------- /maxapi/types/updates/message_callback.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, List, Optional, Tuple 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from ...enums.parse_mode import ParseMode 6 | from ...types.attachments import Attachments 7 | from ...types.callback import Callback 8 | from ...types.message import Message, NewMessageLink 9 | from .update import Update 10 | 11 | if TYPE_CHECKING: 12 | from ...methods.types.sended_callback import SendedCallback 13 | 14 | 15 | class MessageForCallback(BaseModel): 16 | """ 17 | Модель сообщения для ответа на callback-запрос. 18 | 19 | Attributes: 20 | text (Optional[str]): Текст сообщения. 21 | attachments (Optional[List[Union[AttachmentButton, Audio, Video, File, Image, Sticker, Share]]]): 22 | Список вложений. 23 | link (Optional[NewMessageLink]): Связь с другим сообщением. 24 | notify (Optional[bool]): Отправлять ли уведомление. 25 | format (Optional[ParseMode]): Режим разбора текста. 26 | """ 27 | 28 | text: Optional[str] = None 29 | attachments: Optional[List[Attachments]] = Field( 30 | default_factory=list 31 | ) # type: ignore 32 | link: Optional[NewMessageLink] = None 33 | notify: Optional[bool] = True 34 | format: Optional[ParseMode] = None 35 | 36 | 37 | class MessageCallback(Update): 38 | """ 39 | Обновление с callback-событием сообщения. 40 | 41 | Attributes: 42 | message (Message): Сообщение, на которое пришёл callback. 43 | user_locale (Optional[str]): Локаль пользователя. 44 | callback (Callback): Объект callback. 45 | """ 46 | 47 | message: Message 48 | user_locale: Optional[str] = None 49 | callback: Callback 50 | 51 | def get_ids(self) -> Tuple[Optional[int], int]: 52 | """ 53 | Возвращает кортеж идентификаторов (chat_id, user_id). 54 | 55 | Returns: 56 | tuple[Optional[int], int]: Идентификаторы чата и пользователя. 57 | """ 58 | 59 | return (self.message.recipient.chat_id, self.callback.user.user_id) 60 | 61 | async def answer( 62 | self, 63 | notification: Optional[str] = None, 64 | new_text: Optional[str] = None, 65 | link: Optional[NewMessageLink] = None, 66 | notify: bool = True, 67 | format: Optional[ParseMode] = None, 68 | ) -> "SendedCallback": 69 | """ 70 | Отправляет ответ на callback с возможностью изменить текст, вложения и параметры уведомления. 71 | 72 | Args: 73 | notification (str): Текст уведомления. 74 | new_text (Optional[str]): Новый текст сообщения. 75 | link (Optional[NewMessageLink]): Связь с другим сообщением. 76 | notify (bool): Отправлять ли уведомление. 77 | format (Optional[ParseMode]): Режим разбора текста. 78 | 79 | Returns: 80 | SendedCallback: Результат вызова send_callback бота. 81 | """ 82 | 83 | message = MessageForCallback() 84 | 85 | message.text = new_text 86 | message.attachments = self.message.body.attachments 87 | message.link = link 88 | message.notify = notify 89 | message.format = format 90 | 91 | return await self._ensure_bot().send_callback( 92 | callback_id=self.callback.callback_id, 93 | message=message, 94 | notification=notification, 95 | ) 96 | --------------------------------------------------------------------------------