mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Add chat member tools (#1527)
* feat: add ChatMemberAdapter * chore: apply formatter * docs: added changelog * docs: rm redundant import * feat: add pre-defined groups
This commit is contained in:
parent
5f05dfc664
commit
46e033e6da
4 changed files with 200 additions and 6 deletions
1
CHANGES/1525.feature.rst
Normal file
1
CHANGES/1525.feature.rst
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Added ChatMember resolution tool and updated 2.x migration guide.
|
||||||
37
aiogram/utils/chat_member.py
Normal file
37
aiogram/utils/chat_member.py
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
from typing import Tuple, Type, Union
|
||||||
|
|
||||||
|
from pydantic import Field, TypeAdapter
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from aiogram.types import (
|
||||||
|
ChatMember,
|
||||||
|
ChatMemberAdministrator,
|
||||||
|
ChatMemberBanned,
|
||||||
|
ChatMemberLeft,
|
||||||
|
ChatMemberMember,
|
||||||
|
ChatMemberOwner,
|
||||||
|
ChatMemberRestricted,
|
||||||
|
)
|
||||||
|
|
||||||
|
ChatMemberUnion = Union[
|
||||||
|
ChatMemberOwner,
|
||||||
|
ChatMemberAdministrator,
|
||||||
|
ChatMemberMember,
|
||||||
|
ChatMemberRestricted,
|
||||||
|
ChatMemberLeft,
|
||||||
|
ChatMemberBanned,
|
||||||
|
]
|
||||||
|
|
||||||
|
ChatMemberCollection = Tuple[Type[ChatMember], ...]
|
||||||
|
|
||||||
|
ChatMemberAdapter = TypeAdapter(
|
||||||
|
Annotated[
|
||||||
|
ChatMemberUnion,
|
||||||
|
Field(discriminator="status"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
ADMINS: ChatMemberCollection = (ChatMemberOwner, ChatMemberAdministrator)
|
||||||
|
USERS: ChatMemberCollection = (ChatMemberMember, ChatMemberRestricted)
|
||||||
|
MEMBERS: ChatMemberCollection = ADMINS + USERS
|
||||||
|
NOT_MEMBERS: ChatMemberCollection = (ChatMemberLeft, ChatMemberBanned)
|
||||||
|
|
@ -26,7 +26,7 @@ On this page, you can read about the changes made in relation to the last stable
|
||||||
Feel free to contribute to this page, if you find something that is not mentioned here.
|
Feel free to contribute to this page, if you find something that is not mentioned here.
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
==========
|
============
|
||||||
|
|
||||||
- The dependencies required for :code:`i18n` are no longer part of the default package.
|
- The dependencies required for :code:`i18n` are no longer part of the default package.
|
||||||
If your application uses translation functionality, be sure to add an optional dependency:
|
If your application uses translation functionality, be sure to add an optional dependency:
|
||||||
|
|
@ -178,7 +178,7 @@ Here are some usage examples:
|
||||||
|
|
||||||
- Creating an object from a dictionary representation of an object
|
- Creating an object from a dictionary representation of an object
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
# Version 2.x
|
# Version 2.x
|
||||||
message_dict = {"id": 42, ...}
|
message_dict = {"id": 42, ...}
|
||||||
|
|
@ -188,6 +188,8 @@ Here are some usage examples:
|
||||||
print(type(message_obj))
|
print(type(message_obj))
|
||||||
# <class 'aiogram.types.message.Message'>
|
# <class 'aiogram.types.message.Message'>
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
# Version 3.x
|
# Version 3.x
|
||||||
message_dict = {"id": 42, ...}
|
message_dict = {"id": 42, ...}
|
||||||
message_obj = Message.model_validate(message_dict)
|
message_obj = Message.model_validate(message_dict)
|
||||||
|
|
@ -198,17 +200,20 @@ Here are some usage examples:
|
||||||
|
|
||||||
- Creating a json representation of an object
|
- Creating a json representation of an object
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 2.x
|
||||||
async def handler(message: Message) -> None:
|
async def handler(message: Message) -> None:
|
||||||
# Version 2.x
|
|
||||||
message_json = message.as_json()
|
message_json = message.as_json()
|
||||||
print(message_json)
|
print(message_json)
|
||||||
# {"id": 42, ...}
|
# {"id": 42, ...}
|
||||||
print(type(message_json))
|
print(type(message_json))
|
||||||
# <class 'str'>
|
# <class 'str'>
|
||||||
|
|
||||||
# Version 3.x
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 3.x
|
||||||
|
async def handler(message: Message) -> None:
|
||||||
message_json = json.dumps(deserialize_telegram_object_to_python(message))
|
message_json = json.dumps(deserialize_telegram_object_to_python(message))
|
||||||
print(message_json)
|
print(message_json)
|
||||||
# {"id": 42, ...}
|
# {"id": 42, ...}
|
||||||
|
|
@ -217,7 +222,7 @@ Here are some usage examples:
|
||||||
|
|
||||||
- Creating a dictionary representation of an object
|
- Creating a dictionary representation of an object
|
||||||
|
|
||||||
.. code-block::
|
.. code-block::
|
||||||
|
|
||||||
async def handler(message: Message) -> None:
|
async def handler(message: Message) -> None:
|
||||||
# Version 2.x
|
# Version 2.x
|
||||||
|
|
@ -227,9 +232,65 @@ Here are some usage examples:
|
||||||
print(type(message_dict))
|
print(type(message_dict))
|
||||||
# <class 'dict'>
|
# <class 'dict'>
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
async def handler(message: Message) -> None:
|
||||||
# Version 3.x
|
# Version 3.x
|
||||||
message_dict = deserialize_telegram_object_to_python(message)
|
message_dict = deserialize_telegram_object_to_python(message)
|
||||||
print(message_dict)
|
print(message_dict)
|
||||||
# {"id": 42, ...}
|
# {"id": 42, ...}
|
||||||
print(type(message_dict))
|
print(type(message_dict))
|
||||||
# <class 'dict'>
|
# <class 'dict'>
|
||||||
|
|
||||||
|
|
||||||
|
ChatMember tools
|
||||||
|
================
|
||||||
|
|
||||||
|
- Now :class:`aiogram.types.chat_member.ChatMember` no longer contains tools to resolve an object with the appropriate status.
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 2.x
|
||||||
|
from aiogram.types import ChatMember
|
||||||
|
|
||||||
|
chat_member = ChatMember.resolve(**dict_data)
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 3.x
|
||||||
|
from aiogram.utils.chat_member import ChatMemberAdapter
|
||||||
|
|
||||||
|
chat_member = ChatMemberAdapter.validate_python(dict_data)
|
||||||
|
|
||||||
|
|
||||||
|
- Now :class:`aiogram.types.chat_member.ChatMember` and all its child classes no longer
|
||||||
|
contain methods for checking for membership in certain logical groups.
|
||||||
|
As a substitute, you can use pre-defined groups or create such groups yourself
|
||||||
|
and check their entry using the :func:`isinstance` function
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 2.x
|
||||||
|
|
||||||
|
if chat_member.is_chat_admin():
|
||||||
|
print("ChatMember is chat admin")
|
||||||
|
|
||||||
|
if chat_member.is_chat_member():
|
||||||
|
print("ChatMember is in the chat")
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# Version 3.x
|
||||||
|
|
||||||
|
from aiogram.utils.chat_member import ADMINS, MEMBERS
|
||||||
|
|
||||||
|
if isinstance(chat_member, ADMINS):
|
||||||
|
print("ChatMember is chat admin")
|
||||||
|
|
||||||
|
if isinstance(chat_member, MEMBERS):
|
||||||
|
print("ChatMember is in the chat")
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
You also can independently create group similar to ADMINS that fits the logic of your application.
|
||||||
|
|
||||||
|
E.g., you can create a PUNISHED group and include banned and restricted members there!
|
||||||
|
|
|
||||||
95
tests/test_utils/test_chat_member.py
Normal file
95
tests/test_utils/test_chat_member.py
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from aiogram.types import (
|
||||||
|
ChatMember,
|
||||||
|
ChatMemberAdministrator,
|
||||||
|
ChatMemberBanned,
|
||||||
|
ChatMemberLeft,
|
||||||
|
ChatMemberMember,
|
||||||
|
ChatMemberOwner,
|
||||||
|
ChatMemberRestricted,
|
||||||
|
User,
|
||||||
|
)
|
||||||
|
from aiogram.utils.chat_member import ChatMemberAdapter
|
||||||
|
|
||||||
|
USER = User(
|
||||||
|
id=42,
|
||||||
|
first_name="John Doe",
|
||||||
|
is_bot=False,
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_ADMINISTRATOR = ChatMemberAdministrator(
|
||||||
|
user=USER,
|
||||||
|
can_be_edited=False,
|
||||||
|
can_manage_chat=True,
|
||||||
|
can_change_info=True,
|
||||||
|
can_delete_messages=True,
|
||||||
|
can_invite_users=True,
|
||||||
|
can_restrict_members=True,
|
||||||
|
can_pin_messages=True,
|
||||||
|
can_manage_topics=False,
|
||||||
|
can_promote_members=False,
|
||||||
|
can_manage_video_chats=True,
|
||||||
|
can_post_stories=True,
|
||||||
|
can_edit_stories=True,
|
||||||
|
can_delete_stories=True,
|
||||||
|
is_anonymous=False,
|
||||||
|
can_manage_voice_chats=False,
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_BANNED = ChatMemberBanned(
|
||||||
|
user=USER,
|
||||||
|
until_date=datetime.now(),
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_LEFT = ChatMemberLeft(
|
||||||
|
user=USER,
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_MEMBER = ChatMemberMember(
|
||||||
|
user=USER,
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_OWNER = ChatMemberOwner(
|
||||||
|
user=USER,
|
||||||
|
is_anonymous=True,
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
CHAT_MEMBER_RESTRICTED = ChatMemberRestricted(
|
||||||
|
user=USER,
|
||||||
|
is_member=True,
|
||||||
|
can_send_messages=False,
|
||||||
|
can_send_audios=False,
|
||||||
|
can_send_documents=False,
|
||||||
|
can_send_photos=False,
|
||||||
|
can_send_videos=False,
|
||||||
|
can_send_video_notes=False,
|
||||||
|
can_send_voice_notes=False,
|
||||||
|
can_send_polls=False,
|
||||||
|
can_send_other_messages=False,
|
||||||
|
can_add_web_page_previews=False,
|
||||||
|
can_change_info=False,
|
||||||
|
can_invite_users=False,
|
||||||
|
can_pin_messages=False,
|
||||||
|
can_manage_topics=False,
|
||||||
|
until_date=datetime.now(),
|
||||||
|
).model_dump()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("data", "resolved_type"),
|
||||||
|
[
|
||||||
|
(CHAT_MEMBER_ADMINISTRATOR, ChatMemberAdministrator),
|
||||||
|
(CHAT_MEMBER_BANNED, ChatMemberBanned),
|
||||||
|
(CHAT_MEMBER_LEFT, ChatMemberLeft),
|
||||||
|
(CHAT_MEMBER_MEMBER, ChatMemberMember),
|
||||||
|
(CHAT_MEMBER_OWNER, ChatMemberOwner),
|
||||||
|
(CHAT_MEMBER_RESTRICTED, ChatMemberRestricted),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_chat_member_resolution(data: dict, resolved_type: Type[ChatMember]) -> None:
|
||||||
|
chat_member = ChatMemberAdapter.validate_python(data)
|
||||||
|
assert isinstance(chat_member, resolved_type)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue