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.
Files changed (247) hide show
  1. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/PKG-INFO +2 -1
  2. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/router.rst +6 -0
  3. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/chats.rst +16 -0
  4. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/client.rst +29 -6
  5. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/index.rst +2 -0
  6. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/messages.rst +14 -1
  7. maxapi_python-2.3.1/docs/release-2-3-0.rst +40 -0
  8. maxapi_python-2.3.1/docs/release-2-3-1.rst +23 -0
  9. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/router.rst +48 -1
  10. maxapi_python-2.3.1/docs/types/contact_info.rst +6 -0
  11. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/index.rst +4 -0
  12. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/users.rst +19 -0
  13. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/pyproject.toml +2 -1
  14. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/__init__.py +1 -1
  15. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/payloads.py +6 -0
  16. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/service.py +18 -0
  17. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/payloads.py +21 -1
  18. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/service.py +42 -11
  19. maxapi_python-2.3.1/src/pymax/api/users/payloads.py +38 -0
  20. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/service.py +14 -1
  21. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/app.py +26 -6
  22. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/base.py +48 -3
  23. maxapi_python-2.3.1/src/pymax/dispatch/__init__.py +21 -0
  24. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/dispatcher.py +134 -16
  25. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/enums.py +1 -0
  26. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/router.py +86 -4
  27. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/chat.py +21 -0
  28. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/message.py +29 -6
  29. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/user.py +12 -1
  30. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/compression.py +18 -0
  31. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/payload.py +20 -4
  32. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/protocol.py +5 -1
  33. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/store.py +11 -0
  34. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/__init__.py +1 -1
  35. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/chat.py +21 -1
  36. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/message.py +32 -9
  37. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/user.py +14 -4
  38. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_chat_user_self_session_services.py +72 -0
  39. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_message_service.py +43 -3
  40. maxapi_python-2.3.1/tests/app/test_app_runtime.py +445 -0
  41. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/domain/test_bound_models.py +33 -7
  42. maxapi_python-2.3.1/tests/domain/test_user_models.py +35 -0
  43. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/protocol/test_protocols.py +45 -1
  44. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/session/test_store.py +20 -0
  45. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/uv.lock +93 -1
  46. maxapi_python-2.2.0/src/pymax/api/users/payloads.py +0 -16
  47. maxapi_python-2.2.0/src/pymax/dispatch/__init__.py +0 -10
  48. maxapi_python-2.2.0/tests/app/test_app_runtime.py +0 -205
  49. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  50. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  51. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/ISSUE_TEMPLATE/refactor.md +0 -0
  52. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/pull_request_template.md +0 -0
  53. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/workflows/publish.yml +0 -0
  54. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.github/workflows/tests.yml +0 -0
  55. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.gitignore +0 -0
  56. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/.pre-commit-config.yaml +0 -0
  57. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/LICENSE +0 -0
  58. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/README.md +0 -0
  59. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/_static/.gitkeep +0 -0
  60. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/account.rst +0 -0
  61. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/auth.rst +0 -0
  62. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-client.rst +0 -0
  63. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-config.rst +0 -0
  64. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client-web.rst +0 -0
  65. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/client.rst +0 -0
  66. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/api/files.rst +0 -0
  67. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/auth.rst +0 -0
  68. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/conf.py +0 -0
  69. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/examples.rst +0 -0
  70. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/faq.rst +0 -0
  71. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/files.rst +0 -0
  72. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/formatting.rst +0 -0
  73. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/getting-started.rst +0 -0
  74. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-0.rst +0 -0
  75. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-1.rst +0 -0
  76. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-2.rst +0 -0
  77. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-1-3.rst +0 -0
  78. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/release-2-2-0.rst +0 -0
  79. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/troubleshooting.rst +0 -0
  80. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/audio_attachment.rst +0 -0
  81. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/call_attachment.rst +0 -0
  82. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/chat.rst +0 -0
  83. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/contact_attachment.rst +0 -0
  84. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/control_attachment.rst +0 -0
  85. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/element.rst +0 -0
  86. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/enums.rst +0 -0
  87. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/file_attachment.rst +0 -0
  88. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder.rst +0 -0
  89. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder_list.rst +0 -0
  90. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/folder_update.rst +0 -0
  91. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/inline_keyboard_attachment.rst +0 -0
  92. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message.rst +0 -0
  93. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message_delete_event.rst +0 -0
  94. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/message_read_event.rst +0 -0
  95. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/name.rst +0 -0
  96. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/photo_attachment.rst +0 -0
  97. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/presence_event.rst +0 -0
  98. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/profile.rst +0 -0
  99. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_counter.rst +0 -0
  100. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_info.rst +0 -0
  101. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/reaction_update_event.rst +0 -0
  102. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/read_state.rst +0 -0
  103. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/session.rst +0 -0
  104. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/share_attachment.rst +0 -0
  105. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sticker_attachment.rst +0 -0
  106. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sync_overrides.rst +0 -0
  107. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/sync_state.rst +0 -0
  108. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/typing_event.rst +0 -0
  109. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/user.rst +0 -0
  110. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/docs/types/video_attachment.rst +0 -0
  111. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/__init__.py +0 -0
  112. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/__init__.py +0 -0
  113. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/enums.py +0 -0
  114. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/payloads.py +0 -0
  115. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/service.py +0 -0
  116. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/auth/types.py +0 -0
  117. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/binding.py +0 -0
  118. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/__init__.py +0 -0
  119. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/payloads.py +0 -0
  120. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/bots/service.py +0 -0
  121. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/__init__.py +0 -0
  122. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/chats/enums.py +0 -0
  123. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/facade.py +0 -0
  124. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/__init__.py +0 -0
  125. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/messages/enums.py +0 -0
  126. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/models.py +0 -0
  127. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/response.py +0 -0
  128. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/__init__.py +0 -0
  129. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/enums.py +0 -0
  130. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/payloads.py +0 -0
  131. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/self/service.py +0 -0
  132. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/__init__.py +0 -0
  133. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/enums.py +0 -0
  134. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/payloads.py +0 -0
  135. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/session/service.py +0 -0
  136. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/__init__.py +0 -0
  137. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/models.py +0 -0
  138. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/payloads.py +0 -0
  139. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/uploads/service.py +0 -0
  140. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/__init__.py +0 -0
  141. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/api/users/enums.py +0 -0
  142. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/__init__.py +0 -0
  143. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/base.py +0 -0
  144. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/email.py +0 -0
  145. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/models.py +0 -0
  146. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/providers.py +0 -0
  147. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/qr.py +0 -0
  148. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/service.py +0 -0
  149. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/auth/sms.py +0 -0
  150. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/client.py +0 -0
  151. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/client_web.py +0 -0
  152. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/config.py +0 -0
  153. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/__init__.py +0 -0
  154. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/connection.py +0 -0
  155. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/pending.py +0 -0
  156. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/__init__.py +0 -0
  157. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/base.py +0 -0
  158. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/tcp.py +0 -0
  159. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/connection/readers/ws.py +0 -0
  160. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/mapping.py +0 -0
  161. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/dispatch/resolvers.py +0 -0
  162. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/exceptions.py +0 -0
  163. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/__init__.py +0 -0
  164. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/base.py +0 -0
  165. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/file.py +0 -0
  166. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/photo.py +0 -0
  167. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/static.py +0 -0
  168. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/files/video.py +0 -0
  169. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/formatting/__init__.py +0 -0
  170. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/formatting/markdown.py +0 -0
  171. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/__init__.py +0 -0
  172. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/auth.py +0 -0
  173. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/base.py +0 -0
  174. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/bots.py +0 -0
  175. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/protocol.py +0 -0
  176. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/infra/self.py +0 -0
  177. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/logging.py +0 -0
  178. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/__init__.py +0 -0
  179. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/base.py +0 -0
  180. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/enums.py +0 -0
  181. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/models.py +0 -0
  182. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/__init__.py +0 -0
  183. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/tcp/framing.py +0 -0
  184. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/ws/__init__.py +0 -0
  185. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/protocol/ws/protocol.py +0 -0
  186. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/py.typed +0 -0
  187. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/routers.py +0 -0
  188. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/__init__.py +0 -0
  189. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/models.py +0 -0
  190. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/session/protocol.py +0 -0
  191. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/__init__.py +0 -0
  192. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/navigation.py +0 -0
  193. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/payloads.py +0 -0
  194. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/telemetry/service.py +0 -0
  195. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/__init__.py +0 -0
  196. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/base.py +0 -0
  197. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/tcp.py +0 -0
  198. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/transport/websocket.py +0 -0
  199. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/__init__.py +0 -0
  200. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/__init__.py +0 -0
  201. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/audio.py +0 -0
  202. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/call.py +0 -0
  203. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/contact.py +0 -0
  204. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/control.py +0 -0
  205. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/enums.py +0 -0
  206. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/file.py +0 -0
  207. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/keyboards/__init__.py +0 -0
  208. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/keyboards/inline.py +0 -0
  209. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/photo.py +0 -0
  210. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/share.py +0 -0
  211. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/sticker.py +0 -0
  212. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/unknown.py +0 -0
  213. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/attachments/video.py +0 -0
  214. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/auth.py +0 -0
  215. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/base.py +0 -0
  216. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/bots.py +0 -0
  217. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/element.py +0 -0
  218. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/enums.py +0 -0
  219. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/error.py +0 -0
  220. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/folder.py +0 -0
  221. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/login.py +0 -0
  222. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/member.py +0 -0
  223. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/name.py +0 -0
  224. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/presence.py +0 -0
  225. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/profile.py +0 -0
  226. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/session.py +0 -0
  227. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/domain/sync.py +0 -0
  228. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/__init__.py +0 -0
  229. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/file.py +0 -0
  230. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/mark.py +0 -0
  231. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/message.py +0 -0
  232. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/presence.py +0 -0
  233. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/reaction.py +0 -0
  234. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/typing.py +0 -0
  235. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/src/pymax/types/events/video.py +0 -0
  236. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/__init__.py +0 -0
  237. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_auth_service.py +0 -0
  238. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/api/test_upload_service.py +0 -0
  239. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/auth/test_auth_flows.py +0 -0
  240. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/conftest.py +0 -0
  241. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/connection/test_connection.py +0 -0
  242. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/connection/test_readers_and_transports.py +0 -0
  243. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/dispatch/test_dispatcher.py +0 -0
  244. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/domain/test_message_models.py +0 -0
  245. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/files/test_files_and_formatting.py +0 -0
  246. {maxapi_python-2.2.0 → maxapi_python-2.3.1}/tests/telemetry/test_telemetry.py +0 -0
  247. {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.2.0
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
@@ -5,6 +5,12 @@ Router API
5
5
  :members:
6
6
  :show-inheritance:
7
7
 
8
+ .. autoclass:: pymax.dispatch.ErrorScope
9
+ :members:
10
+
11
+ .. autoclass:: pymax.dispatch.ErrorContext
12
+ :members:
13
+
8
14
  .. autodata:: pymax.ClientRouter
9
15
 
10
16
  .. autodata:: pymax.WebRouter
@@ -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()``, ``fetch_history()``, ``delete_message()``,
281
- ``pin_message()``, ``read_message()``, реакции и получение URL для входящих
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()`` и ``get_chat_id()``. Подробнее:
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()`` и
@@ -22,6 +22,8 @@ PyMax - асинхронная Python-библиотека для Max API. Он
22
22
  :maxdepth: 1
23
23
  :caption: Новости
24
24
 
25
+ release-2-3-1
26
+ release-2-3-0
25
27
  release-2-2-0
26
28
  release-2-1-3
27
29
  release-2-1-2
@@ -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
- В ``2.2.0`` доступны отдельные обработчики набора текста, присутствия,
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
  ----------
@@ -0,0 +1,6 @@
1
+ ContactInfo
2
+ ===========
3
+
4
+ .. autoclass:: pymax.types.domain.user.ContactInfo
5
+ :members:
6
+ :show-inheritance:
@@ -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.2.0"
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]
@@ -1,4 +1,4 @@
1
- __version__ = "2.2.0"
1
+ __version__ = "2.3.1"
2
2
 
3
3
 
4
4
  from .auth import (
@@ -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 = list[SendAttachment] | None
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(edit_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
- response = await self.api.auth.login(
128
- self.config.device.user_agent,
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)