maxapi-python 2.2.0__tar.gz → 2.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/PKG-INFO +2 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/router.rst +6 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/chats.rst +16 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/client.rst +29 -6
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/index.rst +2 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/messages.rst +14 -1
- maxapi_python-2.3.1/docs/release-2-3-0.rst +40 -0
- maxapi_python-2.3.1/docs/release-2-3-1.rst +23 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/router.rst +48 -1
- maxapi_python-2.3.1/docs/types/contact_info.rst +6 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/index.rst +4 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/users.rst +19 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/pyproject.toml +2 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/__init__.py +1 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/payloads.py +6 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/service.py +18 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/payloads.py +21 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/service.py +42 -11
- maxapi_python-2.3.1/src/pymax/api/users/payloads.py +38 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/service.py +14 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/app.py +26 -6
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/base.py +48 -3
- maxapi_python-2.3.1/src/pymax/dispatch/__init__.py +21 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/dispatcher.py +134 -16
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/enums.py +1 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/router.py +86 -4
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/chat.py +21 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/message.py +29 -6
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/user.py +12 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/compression.py +18 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/payload.py +20 -4
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/protocol.py +5 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/store.py +11 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/__init__.py +1 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/chat.py +21 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/message.py +32 -9
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/user.py +14 -4
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_chat_user_self_session_services.py +72 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_message_service.py +43 -3
- maxapi_python-2.3.1/tests/app/test_app_runtime.py +445 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/domain/test_bound_models.py +33 -7
- maxapi_python-2.3.1/tests/domain/test_user_models.py +35 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/protocol/test_protocols.py +45 -1
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/session/test_store.py +20 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/uv.lock +93 -1
- maxapi_python-2.2.0/src/pymax/api/users/payloads.py +0 -16
- maxapi_python-2.2.0/src/pymax/dispatch/__init__.py +0 -10
- maxapi_python-2.2.0/tests/app/test_app_runtime.py +0 -205
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/pull_request_template.md +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/workflows/publish.yml +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/workflows/tests.yml +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.gitignore +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.pre-commit-config.yaml +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/LICENSE +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/README.md +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/_static/.gitkeep +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/account.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/auth.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-client.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-config.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-web.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/files.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/auth.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/conf.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/examples.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/faq.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/files.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/formatting.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/getting-started.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-0.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-1.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-2.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-3.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-2-0.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/troubleshooting.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/audio_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/call_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/chat.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/contact_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/control_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/element.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/enums.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/file_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder_list.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder_update.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/inline_keyboard_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message_delete_event.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message_read_event.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/name.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/photo_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/presence_event.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/profile.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_counter.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_info.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_update_event.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/read_state.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/session.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/share_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sticker_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sync_overrides.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sync_state.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/typing_event.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/user.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/video_attachment.rst +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/types.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/binding.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/facade.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/response.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/email.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/providers.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/qr.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/sms.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/client.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/client_web.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/config.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/connection.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/pending.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/tcp.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/ws.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/mapping.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/resolvers.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/exceptions.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/file.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/photo.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/static.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/video.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/formatting/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/formatting/markdown.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/auth.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/bots.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/protocol.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/self.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/logging.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/framing.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/ws/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/ws/protocol.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/py.typed +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/routers.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/protocol.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/navigation.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/payloads.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/tcp.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/websocket.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/audio.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/call.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/contact.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/control.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/file.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/keyboards/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/keyboards/inline.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/photo.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/share.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/sticker.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/unknown.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/video.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/auth.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/base.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/bots.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/element.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/enums.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/error.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/folder.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/login.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/member.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/name.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/presence.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/profile.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/session.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/sync.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/file.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/mark.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/message.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/presence.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/reaction.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/typing.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/video.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/__init__.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_auth_service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_upload_service.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/auth/test_auth_flows.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/conftest.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/connection/test_connection.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/connection/test_readers_and_transports.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/dispatch/test_dispatcher.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/domain/test_message_models.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/files/test_files_and_formatting.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/telemetry/test_telemetry.py +0 -0
- {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/test_logging.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: maxapi-python
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Python wrapper для API мессенджера Max
|
|
5
5
|
Project-URL: Homepage, https://github.com/MaxApiTeam/PyMax
|
|
6
6
|
Project-URL: Repository, https://github.com/MaxApiTeam/PyMax
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pydantic>=2.10.0
|
|
|
31
31
|
Requires-Dist: python-socks[asyncio]>=2.8.1
|
|
32
32
|
Requires-Dist: qrcode>=8.2
|
|
33
33
|
Requires-Dist: websockets>=16.0
|
|
34
|
+
Requires-Dist: zstandard>=0.25.0
|
|
34
35
|
Description-Content-Type: text/markdown
|
|
35
36
|
|
|
36
37
|
# PyMax
|
|
@@ -142,6 +142,22 @@ login/sync, а также методы для загрузки, создания
|
|
|
142
142
|
``leave()`` зависит от типа чата: для группы вызывает выход из группы, для
|
|
143
143
|
канала - выход из канала. Из личного диалога выйти нельзя.
|
|
144
144
|
|
|
145
|
+
Удалить чат
|
|
146
|
+
-----------
|
|
147
|
+
|
|
148
|
+
.. code-block:: python
|
|
149
|
+
|
|
150
|
+
await client.delete_chat(chat_id=123456)
|
|
151
|
+
|
|
152
|
+
Через объект ``Chat`` PyMax использует ``chat.last_event_time``:
|
|
153
|
+
|
|
154
|
+
.. code-block:: python
|
|
155
|
+
|
|
156
|
+
chat = await client.get_chat(123456)
|
|
157
|
+
await chat.delete(for_all=True)
|
|
158
|
+
|
|
159
|
+
После успешного удаления чат убирается из локального кеша ``client.chats``.
|
|
160
|
+
|
|
145
161
|
Invite-ссылки
|
|
146
162
|
-------------
|
|
147
163
|
|
|
@@ -231,6 +231,21 @@ PyMax потеряет token и попросит авторизацию снов
|
|
|
231
231
|
сессии можно удалить файл ``work_dir/session_name``; тогда потребуется новая
|
|
232
232
|
авторизация.
|
|
233
233
|
|
|
234
|
+
Повторная авторизация
|
|
235
|
+
---------------------
|
|
236
|
+
|
|
237
|
+
Если нужно сбросить текущую локальную сессию и пройти авторизацию заново,
|
|
238
|
+
используйте ``relogin()``:
|
|
239
|
+
|
|
240
|
+
.. code-block:: python
|
|
241
|
+
|
|
242
|
+
await client.relogin()
|
|
243
|
+
|
|
244
|
+
``relogin()`` удаляет загруженную сессию из store, закрывает текущий runtime и
|
|
245
|
+
по умолчанию сразу запускает клиента снова. Если token был передан через
|
|
246
|
+
``ExtraConfig(token=...)``, он тоже сбрасывается; это можно отключить через
|
|
247
|
+
``drop_config_token=False``.
|
|
248
|
+
|
|
234
249
|
Reconnect
|
|
235
250
|
---------
|
|
236
251
|
|
|
@@ -242,6 +257,14 @@ Reconnect
|
|
|
242
257
|
а новый ``App`` снова получает тот же root router. ``on_start`` вызывается
|
|
243
258
|
после каждого успешного reconnect.
|
|
244
259
|
|
|
260
|
+
Перед повторным подключением можно зарегистрировать ``on_disconnect``:
|
|
261
|
+
|
|
262
|
+
.. code-block:: python
|
|
263
|
+
|
|
264
|
+
@client.on_disconnect()
|
|
265
|
+
async def disconnected(exc: Exception, reconnect: bool, delay: float) -> None:
|
|
266
|
+
print("connection lost:", exc, reconnect, delay)
|
|
267
|
+
|
|
245
268
|
Отключить reconnect:
|
|
246
269
|
|
|
247
270
|
.. code-block:: python
|
|
@@ -277,18 +300,18 @@ Debug-логи показывают handshake, login, входящие собы
|
|
|
277
300
|
Клиент собирает несколько API-направлений:
|
|
278
301
|
|
|
279
302
|
Сообщения
|
|
280
|
-
``send_message()``, ``
|
|
281
|
-
``pin_message()``, ``read_message()``, реакции и
|
|
282
|
-
файлов/видео.
|
|
303
|
+
``send_message()``, ``forward_message()``, ``fetch_history()``,
|
|
304
|
+
``delete_message()``, ``pin_message()``, ``read_message()``, реакции и
|
|
305
|
+
получение URL для входящих файлов/видео.
|
|
283
306
|
|
|
284
307
|
Чаты
|
|
285
308
|
``get_chat()``, ``fetch_chats()``, создание групп, invite-ссылки,
|
|
286
|
-
участники, настройки
|
|
309
|
+
участники, настройки групп, удаление чатов и выход из групп/каналов.
|
|
287
310
|
|
|
288
311
|
Пользователи
|
|
289
312
|
``get_user()``, ``get_users()``, ``fetch_users()``, ``search_by_phone()``,
|
|
290
|
-
``add_contact()``, ``remove_contact()
|
|
291
|
-
:doc:`users`.
|
|
313
|
+
``add_contact()``, ``remove_contact()``, ``import_contacts()`` и
|
|
314
|
+
``get_chat_id()``. Подробнее: :doc:`users`.
|
|
292
315
|
|
|
293
316
|
Аккаунт
|
|
294
317
|
``change_profile()``, папки чатов, активные сессии, ``logout()`` и
|
|
@@ -51,6 +51,7 @@ Messages
|
|
|
51
51
|
|
|
52
52
|
Через клиент то же редактирование доступно как
|
|
53
53
|
``client.edit_message(chat_id, message_id, text, ...)``.
|
|
54
|
+
Новые вложения передаются через ``attachments``.
|
|
54
55
|
|
|
55
56
|
Отправлять сообщения
|
|
56
57
|
--------------------
|
|
@@ -69,6 +70,18 @@ Messages
|
|
|
69
70
|
async def on_message(message: Message, client: Client) -> None:
|
|
70
71
|
await message.answer("Ответ в тот же чат")
|
|
71
72
|
await message.reply("Ответ реплаем")
|
|
73
|
+
await message.forward(chat_id=654321)
|
|
74
|
+
|
|
75
|
+
Переслать сообщение напрямую через клиент можно с указанием исходного и
|
|
76
|
+
целевого чатов:
|
|
77
|
+
|
|
78
|
+
.. code-block:: python
|
|
79
|
+
|
|
80
|
+
await client.forward_message(
|
|
81
|
+
chat_id=654321,
|
|
82
|
+
message_id=987654,
|
|
83
|
+
source_chat_id=123456,
|
|
84
|
+
)
|
|
72
85
|
|
|
73
86
|
Ответ, реакции, удаление и прочтение
|
|
74
87
|
----------------------------------------
|
|
@@ -99,7 +112,7 @@ Messages
|
|
|
99
112
|
Служебные события
|
|
100
113
|
-----------------
|
|
101
114
|
|
|
102
|
-
|
|
115
|
+
Начиная с ``2.2.0`` доступны отдельные обработчики набора текста, присутствия,
|
|
103
116
|
прочтения и реакций:
|
|
104
117
|
|
|
105
118
|
.. code-block:: python
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
PyMax 2.3.0
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Изменения относительно ``2.2.0``.
|
|
5
|
+
|
|
6
|
+
Добавлено
|
|
7
|
+
---------
|
|
8
|
+
|
|
9
|
+
* Несколько ``on_start``-обработчиков на одном клиенте или роутере. Все
|
|
10
|
+
зарегистрированные callbacks запускаются после успешного login.
|
|
11
|
+
* ``on_error()`` для централизованной обработки ошибок из handler-ов,
|
|
12
|
+
фильтров, ``on_start`` и login на этапе запуска.
|
|
13
|
+
* ``ErrorScope.GLOBAL`` и ``ErrorScope.LOCAL`` для выбора области действия
|
|
14
|
+
error-handler-а.
|
|
15
|
+
* ``on_disconnect()`` для реакции на сетевое отключение перед reconnect. В
|
|
16
|
+
callback передаются исходная ошибка, флаг reconnect и задержка.
|
|
17
|
+
* ``relogin()`` для удаления текущей локальной сессии и повторной авторизации.
|
|
18
|
+
* ``delete_chat()`` на клиенте и ``Chat.delete()`` на bound-объекте чата.
|
|
19
|
+
* ``import_contacts()`` и ``ContactInfo`` для импорта контактов из телефонной
|
|
20
|
+
книги.
|
|
21
|
+
* ``SessionStore.delete_all_sessions()`` для очистки встроенного SQLite-store.
|
|
22
|
+
|
|
23
|
+
Изменилось
|
|
24
|
+
----------
|
|
25
|
+
|
|
26
|
+
* ``edit_message()`` и ``Message.edit()`` принимают новые вложения только через
|
|
27
|
+
``attachments=[...]``.
|
|
28
|
+
* Тип ``attachments`` для отправки и редактирования сообщений теперь принимает
|
|
29
|
+
любую ``Sequence`` из ``Photo``, ``File`` и ``Video``.
|
|
30
|
+
* Обработанные login-ошибки на этапе ``start`` больше не приводят к запуску
|
|
31
|
+
``on_start``.
|
|
32
|
+
|
|
33
|
+
Миграция
|
|
34
|
+
--------
|
|
35
|
+
|
|
36
|
+
* В ``edit_message()`` и ``Message.edit()`` используйте ``attachments=[...]``.
|
|
37
|
+
Параметр ``attachment`` удален.
|
|
38
|
+
* Если error-handler зарегистрирован и успешно отработал, исходная ошибка
|
|
39
|
+
считается обработанной. Если сам error-handler падает, исходная ошибка
|
|
40
|
+
продолжает распространяться.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
PyMax 2.3.1
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Изменения относительно ``2.3.0``.
|
|
5
|
+
|
|
6
|
+
Добавлено
|
|
7
|
+
---------
|
|
8
|
+
|
|
9
|
+
* ``forward_message()`` на клиенте и ``Message.forward()`` на bound-объекте
|
|
10
|
+
сообщения. Для пересылки между разными чатами укажите ``source_chat_id``.
|
|
11
|
+
|
|
12
|
+
Исправлено
|
|
13
|
+
----------
|
|
14
|
+
|
|
15
|
+
* Декодирование сжатых TCP payload-ов: коэффициенты LZ4 теперь обрабатываются
|
|
16
|
+
корректно, а payload-ы с флагом ``0xFF`` декодируются через Zstandard.
|
|
17
|
+
* Разбор профилей bot-аккаунтов, в которых ``gender`` приходит числом, а
|
|
18
|
+
``web_app`` — URL-строкой.
|
|
19
|
+
|
|
20
|
+
Зависимости
|
|
21
|
+
-----------
|
|
22
|
+
|
|
23
|
+
* Добавлена runtime-зависимость ``zstandard`` для декодирования TCP payload-ов.
|
|
@@ -158,7 +158,54 @@ Handler всегда вызывается как ``handler(event, client)``. Э
|
|
|
158
158
|
print(client.me)
|
|
159
159
|
|
|
160
160
|
Если включен reconnect, ``on_start`` будет вызван после каждого успешного
|
|
161
|
-
переподключения.
|
|
161
|
+
переподключения. На одном клиенте или роутере можно зарегистрировать несколько
|
|
162
|
+
``on_start``-обработчиков; PyMax запустит каждый из них.
|
|
163
|
+
|
|
164
|
+
Ошибки handler-ов
|
|
165
|
+
-----------------
|
|
166
|
+
|
|
167
|
+
``on_error`` перехватывает ошибки из фильтров, handler-ов, ``on_start`` и login
|
|
168
|
+
на этапе запуска.
|
|
169
|
+
|
|
170
|
+
.. code-block:: python
|
|
171
|
+
|
|
172
|
+
from pymax import ApiError, Client, ClientRouter
|
|
173
|
+
from pymax.dispatch import ErrorContext, ErrorScope
|
|
174
|
+
|
|
175
|
+
client = Client(phone="+79990000000", work_dir="cache")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@client.on_error(scope=ErrorScope.GLOBAL)
|
|
179
|
+
async def on_err(e: Exception, ctx: ErrorContext[Client]) -> None:
|
|
180
|
+
if isinstance(e, ApiError) and e.message == "FAIL_LOGIN_TOKEN":
|
|
181
|
+
await ctx.client.relogin()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
router = ClientRouter()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@router.on_error(scope=ErrorScope.LOCAL)
|
|
188
|
+
async def router_error(e: Exception, ctx: ErrorContext[Client]) -> None:
|
|
189
|
+
print("router failed:", e)
|
|
190
|
+
|
|
191
|
+
``ErrorScope.GLOBAL`` получает ошибки из всего дерева подключенных роутеров.
|
|
192
|
+
``ErrorScope.LOCAL`` получает только ошибки того router-а, на котором
|
|
193
|
+
зарегистрирован error-handler.
|
|
194
|
+
|
|
195
|
+
Если error-handler успешно отработал, исходная ошибка считается обработанной.
|
|
196
|
+
Если упал сам error-handler, исходная ошибка продолжит распространяться.
|
|
197
|
+
|
|
198
|
+
Отключение
|
|
199
|
+
----------
|
|
200
|
+
|
|
201
|
+
``on_disconnect`` вызывается при сетевой ошибке перед reconnect или перед
|
|
202
|
+
пробросом ошибки, если reconnect отключен.
|
|
203
|
+
|
|
204
|
+
.. code-block:: python
|
|
205
|
+
|
|
206
|
+
@client.on_disconnect()
|
|
207
|
+
async def disconnected(exc: Exception, reconnect: bool, delay: float) -> None:
|
|
208
|
+
print(exc, reconnect, delay)
|
|
162
209
|
|
|
163
210
|
Raw events
|
|
164
211
|
----------
|
|
@@ -37,6 +37,9 @@ Types
|
|
|
37
37
|
``User`` и ``Profile``
|
|
38
38
|
Пользователи и профиль текущего аккаунта.
|
|
39
39
|
|
|
40
|
+
``ContactInfo``
|
|
41
|
+
Контакт телефонной книги для ``import_contacts()``.
|
|
42
|
+
|
|
40
43
|
``PhotoAttachment``, ``VideoAttachment``, ``FileAttachment`` и другие
|
|
41
44
|
Входящие вложения в ``message.attaches``.
|
|
42
45
|
|
|
@@ -95,6 +98,7 @@ API reference
|
|
|
95
98
|
element
|
|
96
99
|
name
|
|
97
100
|
user
|
|
101
|
+
contact_info
|
|
98
102
|
profile
|
|
99
103
|
session
|
|
100
104
|
folder
|
|
@@ -71,6 +71,25 @@ PyMax хранит контакты, которые Max вернул на login/
|
|
|
71
71
|
await user.add_contact()
|
|
72
72
|
await user.remove_contact()
|
|
73
73
|
|
|
74
|
+
Импортировать контакты из телефонной книги:
|
|
75
|
+
|
|
76
|
+
.. code-block:: python
|
|
77
|
+
|
|
78
|
+
from pymax.types import ContactInfo
|
|
79
|
+
|
|
80
|
+
contacts = await client.import_contacts(
|
|
81
|
+
[
|
|
82
|
+
ContactInfo(
|
|
83
|
+
phone="+79990000000",
|
|
84
|
+
first_name="Ada",
|
|
85
|
+
last_name="Lovelace",
|
|
86
|
+
)
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
``last_name`` хранится в ``ContactInfo``, но текущий payload импорта Max
|
|
91
|
+
использует только телефон и имя.
|
|
92
|
+
|
|
74
93
|
Личный чат
|
|
75
94
|
----------
|
|
76
95
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "maxapi-python"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.1"
|
|
4
4
|
description = "Python wrapper для API мессенджера Max"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -31,6 +31,7 @@ dependencies = [
|
|
|
31
31
|
"python-socks[asyncio]>=2.8.1",
|
|
32
32
|
"qrcode>=8.2",
|
|
33
33
|
"websockets>=16.0",
|
|
34
|
+
"zstandard>=0.25.0",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.urls]
|
|
@@ -115,3 +115,9 @@ class JoinRequestActionPayload(CamelModel):
|
|
|
115
115
|
type: str = "JOIN_REQUEST" # TODO: ENUMM!!!
|
|
116
116
|
show_history: bool | None = True
|
|
117
117
|
operation: ChatMemberOperation
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class DeleteChatPayload(CamelModel):
|
|
121
|
+
chat_id: int
|
|
122
|
+
last_event_time: int
|
|
123
|
+
for_all: bool = True
|
|
@@ -23,6 +23,7 @@ from .payloads import (
|
|
|
23
23
|
CreateGroupAttach,
|
|
24
24
|
CreateGroupMessage,
|
|
25
25
|
CreateGroupPayload,
|
|
26
|
+
DeleteChatPayload,
|
|
26
27
|
FetchChatsPayload,
|
|
27
28
|
FetchJoinRequests,
|
|
28
29
|
GetChatInfoPayload,
|
|
@@ -362,3 +363,20 @@ class ChatService:
|
|
|
362
363
|
chat_id=chat_id,
|
|
363
364
|
user_ids=[user_id],
|
|
364
365
|
)
|
|
366
|
+
|
|
367
|
+
async def delete_chat(
|
|
368
|
+
self,
|
|
369
|
+
chat_id: int,
|
|
370
|
+
last_event_time: int | None = None,
|
|
371
|
+
for_all: bool = True,
|
|
372
|
+
) -> None:
|
|
373
|
+
frame = DeleteChatPayload(
|
|
374
|
+
chat_id=chat_id,
|
|
375
|
+
last_event_time=(
|
|
376
|
+
last_event_time if last_event_time is not None else int(time.time() * 1000)
|
|
377
|
+
),
|
|
378
|
+
for_all=for_all,
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
await self.app.invoke(Opcode.CHAT_DELETE, frame.to_payload())
|
|
382
|
+
self._remove_cached_chat(chat_id)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any
|
|
1
|
+
from typing import Any, Literal
|
|
2
2
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
|
|
@@ -46,6 +46,26 @@ class SendMessagePayload(CamelModel):
|
|
|
46
46
|
notify: bool = False
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
class ForwardLink(CamelModel):
|
|
50
|
+
type: Literal["FORWARD"] = "FORWARD"
|
|
51
|
+
message_id: str
|
|
52
|
+
chat_id: int
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ForwardMessagePayloadMessage(CamelModel):
|
|
56
|
+
cid: int
|
|
57
|
+
link: ForwardLink
|
|
58
|
+
attaches: list[AttachPhotoPayload | VideoAttachPayload | AttachFilePayload] = Field(
|
|
59
|
+
default_factory=list
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ForwardMessagePayload(CamelModel):
|
|
64
|
+
chat_id: int
|
|
65
|
+
message: ForwardMessagePayloadMessage
|
|
66
|
+
notify: bool = True
|
|
67
|
+
|
|
68
|
+
|
|
49
69
|
class ChatHistoryPayload(CamelModel):
|
|
50
70
|
chat_id: int
|
|
51
71
|
forward: int
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import time
|
|
4
|
+
from collections.abc import Sequence
|
|
4
5
|
from typing import TYPE_CHECKING, TypeAlias
|
|
5
6
|
|
|
6
7
|
from pymax.api.binding import bind_api_model, bind_api_models
|
|
@@ -35,6 +36,9 @@ from .payloads import (
|
|
|
35
36
|
ChatHistoryPayload,
|
|
36
37
|
DeleteMessagePayload,
|
|
37
38
|
EditMessagePayload,
|
|
39
|
+
ForwardLink,
|
|
40
|
+
ForwardMessagePayload,
|
|
41
|
+
ForwardMessagePayloadMessage,
|
|
38
42
|
GetFilePayload,
|
|
39
43
|
GetMessagesPayload,
|
|
40
44
|
GetReactionsPayload,
|
|
@@ -52,7 +56,7 @@ if TYPE_CHECKING:
|
|
|
52
56
|
from pymax.app import App
|
|
53
57
|
|
|
54
58
|
SendAttachment: TypeAlias = Photo | File | Video
|
|
55
|
-
SendAttachments: TypeAlias =
|
|
59
|
+
SendAttachments: TypeAlias = Sequence[SendAttachment] | None
|
|
56
60
|
|
|
57
61
|
logger = get_logger(__name__)
|
|
58
62
|
|
|
@@ -138,6 +142,42 @@ class MessageService:
|
|
|
138
142
|
logger.info("message sent chat_id=%s", chat_id)
|
|
139
143
|
return message
|
|
140
144
|
|
|
145
|
+
async def forward_message(
|
|
146
|
+
self,
|
|
147
|
+
chat_id: int,
|
|
148
|
+
message_id: int | str,
|
|
149
|
+
source_chat_id: int | None = None,
|
|
150
|
+
*,
|
|
151
|
+
notify: bool = True,
|
|
152
|
+
) -> Message | None:
|
|
153
|
+
source_chat_id = chat_id if source_chat_id is None else source_chat_id
|
|
154
|
+
logger.info(
|
|
155
|
+
"forwarding message source_chat_id=%s chat_id=%s message_id=%s",
|
|
156
|
+
source_chat_id,
|
|
157
|
+
chat_id,
|
|
158
|
+
message_id,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
frame = ForwardMessagePayload(
|
|
162
|
+
chat_id=chat_id,
|
|
163
|
+
message=ForwardMessagePayloadMessage(
|
|
164
|
+
cid=-self._next_cid(),
|
|
165
|
+
link=ForwardLink(
|
|
166
|
+
message_id=str(message_id),
|
|
167
|
+
chat_id=source_chat_id,
|
|
168
|
+
),
|
|
169
|
+
),
|
|
170
|
+
notify=notify,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
response = await self.app.invoke(Opcode.MSG_SEND, frame.to_payload())
|
|
174
|
+
message = bind_api_model(
|
|
175
|
+
self.app,
|
|
176
|
+
require_payload_model(response, Message),
|
|
177
|
+
)
|
|
178
|
+
logger.info("message forwarded source_chat_id=%s chat_id=%s", source_chat_id, chat_id)
|
|
179
|
+
return message
|
|
180
|
+
|
|
141
181
|
async def get_messages(
|
|
142
182
|
self,
|
|
143
183
|
chat_id: int,
|
|
@@ -169,24 +209,15 @@ class MessageService:
|
|
|
169
209
|
chat_id: int,
|
|
170
210
|
message_id: int,
|
|
171
211
|
text: str,
|
|
172
|
-
attachment: SendAttachment | None = None,
|
|
173
212
|
attachments: SendAttachments = None,
|
|
174
213
|
) -> Message:
|
|
175
|
-
if attachment is not None and attachments:
|
|
176
|
-
logger.warning("both attachment and attachments provided; using attachments")
|
|
177
|
-
attachment = None
|
|
178
|
-
|
|
179
|
-
edit_attachments = attachments
|
|
180
|
-
if attachment is not None:
|
|
181
|
-
edit_attachments = [attachment]
|
|
182
|
-
|
|
183
214
|
clean_text, elements = Formatter.format_markdown(text)
|
|
184
215
|
frame = EditMessagePayload(
|
|
185
216
|
chat_id=chat_id,
|
|
186
217
|
message_id=message_id,
|
|
187
218
|
text=clean_text,
|
|
188
219
|
elements=elements,
|
|
189
|
-
attachments=await self._upload_attachments(
|
|
220
|
+
attachments=await self._upload_attachments(attachments),
|
|
190
221
|
)
|
|
191
222
|
|
|
192
223
|
response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
|
|
3
|
+
from pymax.api.models import CamelModel
|
|
4
|
+
from pymax.types.domain import ContactInfo
|
|
5
|
+
|
|
6
|
+
from .enums import ContactAction
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FetchContactsPayload(CamelModel):
|
|
10
|
+
contact_ids: list[int]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SearchByPhonePayload(CamelModel):
|
|
14
|
+
phone: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ContactActionPayload(CamelModel):
|
|
18
|
+
contact_id: int
|
|
19
|
+
action: ContactAction
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _ContactPayload(CamelModel):
|
|
23
|
+
first_name: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ImportContactsPayload(CamelModel):
|
|
27
|
+
contact_list: dict[str, _ContactPayload] # phone -> contact payload
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_contacts(cls, contacts: Iterable[ContactInfo]) -> "ImportContactsPayload":
|
|
31
|
+
return cls(
|
|
32
|
+
contact_list={
|
|
33
|
+
contact.phone: _ContactPayload(
|
|
34
|
+
first_name=contact.first_name,
|
|
35
|
+
)
|
|
36
|
+
for contact in contacts
|
|
37
|
+
}
|
|
38
|
+
)
|
|
@@ -10,12 +10,13 @@ from pymax.api.response import (
|
|
|
10
10
|
)
|
|
11
11
|
from pymax.logging import get_logger
|
|
12
12
|
from pymax.protocol import InboundFrame, Opcode
|
|
13
|
-
from pymax.types.domain import Session, User
|
|
13
|
+
from pymax.types.domain import ContactInfo, Session, User
|
|
14
14
|
|
|
15
15
|
from .enums import ContactAction, UserPayloadKey
|
|
16
16
|
from .payloads import (
|
|
17
17
|
ContactActionPayload,
|
|
18
18
|
FetchContactsPayload,
|
|
19
|
+
ImportContactsPayload,
|
|
19
20
|
SearchByPhonePayload,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -122,5 +123,17 @@ class UserService:
|
|
|
122
123
|
self.app.users.pop(contact_id, None)
|
|
123
124
|
return True
|
|
124
125
|
|
|
126
|
+
async def import_contacts(self, contacts: list[ContactInfo]) -> list[User]:
|
|
127
|
+
frame = ImportContactsPayload.from_contacts(contacts)
|
|
128
|
+
|
|
129
|
+
response = await self.app.invoke(Opcode.SYNC, frame.to_payload())
|
|
130
|
+
|
|
131
|
+
users = parse_payload_list(
|
|
132
|
+
response, UserPayloadKey.CONTACTS, User
|
|
133
|
+
) # TODO: maybe also return phone mapping?
|
|
134
|
+
|
|
135
|
+
# {contacts: [...], phones: {data[0]: server_phone}}
|
|
136
|
+
return [self._cache_user(user) for user in users]
|
|
137
|
+
|
|
125
138
|
def get_chat_id(self, first_user_id: int, second_user_id: int) -> int:
|
|
126
139
|
return first_user_id ^ second_user_id
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
from typing import Any, Generic, TypeVar
|
|
2
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
3
3
|
|
|
4
4
|
from pymax.api import ApiFacade
|
|
5
5
|
from pymax.auth import AuthFlow
|
|
6
6
|
from pymax.config import ClientConfig
|
|
7
7
|
from pymax.connection import ConnectionManager
|
|
8
8
|
from pymax.dispatch import Dispatcher
|
|
9
|
-
from pymax.dispatch.router import Router
|
|
9
|
+
from pymax.dispatch.router import EventType, Router
|
|
10
10
|
from pymax.exceptions import ApiError
|
|
11
11
|
from pymax.logging import get_logger
|
|
12
12
|
from pymax.protocol import Command, InboundFrame, OutboundFrame
|
|
@@ -17,8 +17,11 @@ from pymax.telemetry import TelemetryService
|
|
|
17
17
|
from pymax.types import MaxApiError, Message
|
|
18
18
|
from pymax.types.domain import Chat, Profile, User
|
|
19
19
|
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from pymax.base import BaseClient
|
|
22
|
+
|
|
20
23
|
logger = get_logger(__name__)
|
|
21
|
-
ClientT = TypeVar("ClientT")
|
|
24
|
+
ClientT = TypeVar("ClientT", bound="BaseClient")
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
class App(Generic[ClientT]):
|
|
@@ -124,9 +127,26 @@ class App(Generic[ClientT]):
|
|
|
124
127
|
self.session = session_data
|
|
125
128
|
|
|
126
129
|
logger.debug("logging in")
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
+
|
|
131
|
+
try:
|
|
132
|
+
response = await self.api.auth.login(
|
|
133
|
+
self.config.device.user_agent,
|
|
134
|
+
)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
handled = False
|
|
137
|
+
if self.dispatcher.client is not None:
|
|
138
|
+
handled = await self.dispatcher.emit_error(
|
|
139
|
+
e,
|
|
140
|
+
EventType.ON_START,
|
|
141
|
+
None,
|
|
142
|
+
self.dispatcher.root_router,
|
|
143
|
+
None,
|
|
144
|
+
)
|
|
145
|
+
if not handled:
|
|
146
|
+
raise
|
|
147
|
+
|
|
148
|
+
await self.close()
|
|
149
|
+
return
|
|
130
150
|
|
|
131
151
|
if response.token is not None and response.token != self.session.token:
|
|
132
152
|
await self.store.update_token(self.session.token, response.token)
|