Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/api/router.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ Router API
:members:
:show-inheritance:

.. autoclass:: pymax.dispatch.ErrorScope
:members:

.. autoclass:: pymax.dispatch.ErrorContext
:members:

.. autodata:: pymax.ClientRouter

.. autodata:: pymax.WebRouter
16 changes: 16 additions & 0 deletions docs/chats.rst
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,22 @@ login/sync, а также методы для загрузки, создания
``leave()`` зависит от типа чата: для группы вызывает выход из группы, для
канала - выход из канала. Из личного диалога выйти нельзя.

Удалить чат
-----------

.. code-block:: python

await client.delete_chat(chat_id=123456)

Через объект ``Chat`` PyMax использует ``chat.last_event_time``:

.. code-block:: python

chat = await client.get_chat(123456)
await chat.delete(for_all=True)

После успешного удаления чат убирается из локального кеша ``client.chats``.

Invite-ссылки
-------------

Expand Down
29 changes: 26 additions & 3 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,21 @@ PyMax потеряет token и попросит авторизацию снов
сессии можно удалить файл ``work_dir/session_name``; тогда потребуется новая
авторизация.

Повторная авторизация
---------------------

Если нужно сбросить текущую локальную сессию и пройти авторизацию заново,
используйте ``relogin()``:

.. code-block:: python

await client.relogin()

``relogin()`` удаляет загруженную сессию из store, закрывает текущий runtime и
по умолчанию сразу запускает клиента снова. Если token был передан через
``ExtraConfig(token=...)``, он тоже сбрасывается; это можно отключить через
``drop_config_token=False``.

Reconnect
---------

Expand All @@ -242,6 +257,14 @@ Reconnect
а новый ``App`` снова получает тот же root router. ``on_start`` вызывается
после каждого успешного reconnect.

Перед повторным подключением можно зарегистрировать ``on_disconnect``:

.. code-block:: python

@client.on_disconnect()
async def disconnected(exc: Exception, reconnect: bool, delay: float) -> None:
print("connection lost:", exc, reconnect, delay)

Отключить reconnect:

.. code-block:: python
Expand Down Expand Up @@ -283,12 +306,12 @@ Debug-логи показывают handshake, login, входящие собы

Чаты
``get_chat()``, ``fetch_chats()``, создание групп, invite-ссылки,
участники, настройки групп и выход из групп/каналов.
участники, настройки групп, удаление чатов и выход из групп/каналов.

Пользователи
``get_user()``, ``get_users()``, ``fetch_users()``, ``search_by_phone()``,
``add_contact()``, ``remove_contact()`` и ``get_chat_id()``. Подробнее:
:doc:`users`.
``add_contact()``, ``remove_contact()``, ``import_contacts()`` и
``get_chat_id()``. Подробнее: :doc:`users`.

Аккаунт
``change_profile()``, папки чатов, активные сессии, ``logout()`` и
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ PyMax - асинхронная Python-библиотека для Max API. Он
:maxdepth: 1
:caption: Новости

release-2-3-0
release-2-2-0
release-2-1-3
release-2-1-2
Expand Down
3 changes: 2 additions & 1 deletion docs/messages.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Messages

Через клиент то же редактирование доступно как
``client.edit_message(chat_id, message_id, text, ...)``.
Новые вложения передаются через ``attachments``.

Отправлять сообщения
--------------------
Expand Down Expand Up @@ -99,7 +100,7 @@ Messages
Служебные события
-----------------

В ``2.2.0`` доступны отдельные обработчики набора текста, присутствия,
Начиная с ``2.2.0`` доступны отдельные обработчики набора текста, присутствия,
прочтения и реакций:

.. code-block:: python
Expand Down
40 changes: 40 additions & 0 deletions docs/release-2-3-0.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
PyMax 2.3.0
===========

Изменения относительно ``2.2.0``.

Добавлено
---------

* Несколько ``on_start``-обработчиков на одном клиенте или роутере. Все
зарегистрированные callbacks запускаются после успешного login.
* ``on_error()`` для централизованной обработки ошибок из handler-ов,
фильтров, ``on_start`` и login на этапе запуска.
* ``ErrorScope.GLOBAL`` и ``ErrorScope.LOCAL`` для выбора области действия
error-handler-а.
* ``on_disconnect()`` для реакции на сетевое отключение перед reconnect. В
callback передаются исходная ошибка, флаг reconnect и задержка.
* ``relogin()`` для удаления текущей локальной сессии и повторной авторизации.
* ``delete_chat()`` на клиенте и ``Chat.delete()`` на bound-объекте чата.
* ``import_contacts()`` и ``ContactInfo`` для импорта контактов из телефонной
книги.
* ``SessionStore.delete_all_sessions()`` для очистки встроенного SQLite-store.

Изменилось
----------

* ``edit_message()`` и ``Message.edit()`` принимают новые вложения только через
``attachments=[...]``.
* Тип ``attachments`` для отправки и редактирования сообщений теперь принимает
любую ``Sequence`` из ``Photo``, ``File`` и ``Video``.
* Обработанные login-ошибки на этапе ``start`` больше не приводят к запуску
``on_start``.

Миграция
--------

* В ``edit_message()`` и ``Message.edit()`` используйте ``attachments=[...]``.
Параметр ``attachment`` удален.
* Если error-handler зарегистрирован и успешно отработал, исходная ошибка
считается обработанной. Если сам error-handler падает, исходная ошибка
продолжает распространяться.
49 changes: 48 additions & 1 deletion docs/router.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,54 @@ Handler всегда вызывается как ``handler(event, client)``. Э
print(client.me)

Если включен reconnect, ``on_start`` будет вызван после каждого успешного
переподключения.
переподключения. На одном клиенте или роутере можно зарегистрировать несколько
``on_start``-обработчиков; PyMax запустит каждый из них.

Ошибки handler-ов
-----------------

``on_error`` перехватывает ошибки из фильтров, handler-ов, ``on_start`` и login
на этапе запуска.

.. code-block:: python

from pymax import ApiError, Client, ClientRouter
from pymax.dispatch import ErrorContext, ErrorScope

client = Client(phone="+79990000000", work_dir="cache")


@client.on_error(scope=ErrorScope.GLOBAL)
async def on_err(e: Exception, ctx: ErrorContext[Client]) -> None:
if isinstance(e, ApiError) and e.message == "FAIL_LOGIN_TOKEN":
await ctx.client.relogin()


router = ClientRouter()


@router.on_error(scope=ErrorScope.LOCAL)
async def router_error(e: Exception, ctx: ErrorContext[Client]) -> None:
print("router failed:", e)

``ErrorScope.GLOBAL`` получает ошибки из всего дерева подключенных роутеров.
``ErrorScope.LOCAL`` получает только ошибки того router-а, на котором
зарегистрирован error-handler.

Если error-handler успешно отработал, исходная ошибка считается обработанной.
Если упал сам error-handler, исходная ошибка продолжит распространяться.

Отключение
----------

``on_disconnect`` вызывается при сетевой ошибке перед reconnect или перед
пробросом ошибки, если reconnect отключен.

.. code-block:: python

@client.on_disconnect()
async def disconnected(exc: Exception, reconnect: bool, delay: float) -> None:
print(exc, reconnect, delay)

Raw events
----------
Expand Down
6 changes: 6 additions & 0 deletions docs/types/contact_info.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
ContactInfo
===========

.. autoclass:: pymax.types.domain.user.ContactInfo
:members:
:show-inheritance:
4 changes: 4 additions & 0 deletions docs/types/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ Types
``User`` и ``Profile``
Пользователи и профиль текущего аккаунта.

``ContactInfo``
Контакт телефонной книги для ``import_contacts()``.

``PhotoAttachment``, ``VideoAttachment``, ``FileAttachment`` и другие
Входящие вложения в ``message.attaches``.

Expand Down Expand Up @@ -95,6 +98,7 @@ API reference
element
name
user
contact_info
profile
session
folder
Expand Down
19 changes: 19 additions & 0 deletions docs/users.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,25 @@ PyMax хранит контакты, которые Max вернул на login/
await user.add_contact()
await user.remove_contact()

Импортировать контакты из телефонной книги:

.. code-block:: python

from pymax.types import ContactInfo

contacts = await client.import_contacts(
[
ContactInfo(
phone="+79990000000",
first_name="Ada",
last_name="Lovelace",
)
]
)

``last_name`` хранится в ``ContactInfo``, но текущий payload импорта Max
использует только телефон и имя.

Личный чат
----------

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "maxapi-python"
version = "2.2.0"
version = "2.3.0"
description = "Python wrapper для API мессенджера Max"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
2 changes: 1 addition & 1 deletion src/pymax/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "2.2.0"
__version__ = "2.3.0"


from .auth import (
Expand Down
6 changes: 6 additions & 0 deletions src/pymax/api/chats/payloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,9 @@ class JoinRequestActionPayload(CamelModel):
type: str = "JOIN_REQUEST" # TODO: ENUMM!!!
show_history: bool | None = True
operation: ChatMemberOperation


class DeleteChatPayload(CamelModel):
chat_id: int
last_event_time: int
for_all: bool = True
18 changes: 18 additions & 0 deletions src/pymax/api/chats/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
CreateGroupAttach,
CreateGroupMessage,
CreateGroupPayload,
DeleteChatPayload,
FetchChatsPayload,
FetchJoinRequests,
GetChatInfoPayload,
Expand Down Expand Up @@ -362,3 +363,20 @@ async def decline_join_request(
chat_id=chat_id,
user_ids=[user_id],
)

async def delete_chat(
self,
chat_id: int,
last_event_time: int | None = None,
for_all: bool = True,
) -> None:
frame = DeleteChatPayload(
chat_id=chat_id,
last_event_time=(
last_event_time if last_event_time is not None else int(time.time() * 1000)
),
for_all=for_all,
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.

await self.app.invoke(Opcode.CHAT_DELETE, frame.to_payload())
self._remove_cached_chat(chat_id)
14 changes: 3 additions & 11 deletions src/pymax/api/messages/service.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import time
from collections.abc import Sequence
from typing import TYPE_CHECKING, TypeAlias

from pymax.api.binding import bind_api_model, bind_api_models
Expand Down Expand Up @@ -52,7 +53,7 @@
from pymax.app import App

SendAttachment: TypeAlias = Photo | File | Video
SendAttachments: TypeAlias = list[SendAttachment] | None
SendAttachments: TypeAlias = Sequence[SendAttachment] | None

logger = get_logger(__name__)

Expand Down Expand Up @@ -169,24 +170,15 @@ async def edit_message(
chat_id: int,
message_id: int,
text: str,
attachment: SendAttachment | None = None,
attachments: SendAttachments = None,
) -> Message:
if attachment is not None and attachments:
logger.warning("both attachment and attachments provided; using attachments")
attachment = None

edit_attachments = attachments
if attachment is not None:
edit_attachments = [attachment]

clean_text, elements = Formatter.format_markdown(text)
frame = EditMessagePayload(
chat_id=chat_id,
message_id=message_id,
text=clean_text,
elements=elements,
attachments=await self._upload_attachments(edit_attachments),
attachments=await self._upload_attachments(attachments),
)

response = await self.app.invoke(Opcode.MSG_EDIT, frame.to_payload())
Expand Down
22 changes: 22 additions & 0 deletions src/pymax/api/users/payloads.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from collections.abc import Iterable

from pymax.api.models import CamelModel
from pymax.types.domain import ContactInfo

from .enums import ContactAction

Expand All @@ -14,3 +17,22 @@ class SearchByPhonePayload(CamelModel):
class ContactActionPayload(CamelModel):
contact_id: int
action: ContactAction


class _ContactPayload(CamelModel):
first_name: str


class ImportContactsPayload(CamelModel):
contact_list: dict[str, _ContactPayload] # phone -> contact payload

@classmethod
def from_contacts(cls, contacts: Iterable[ContactInfo]) -> "ImportContactsPayload":
return cls(
contact_list={
contact.phone: _ContactPayload(
first_name=contact.first_name,
)
for contact in contacts
}
)
Loading