Fix getting and using participants

This commit is contained in:
Lonami Exo 2023-11-02 14:39:06 +01:00
parent cea9fc6c77
commit 48bf78a855
8 changed files with 232 additions and 10 deletions

View File

@ -10,10 +10,6 @@ if TYPE_CHECKING:
class InlineResults(metaclass=NoPublicConstructor):
"""
:final:
"""
def __init__(
self,
client: Client,

View File

@ -1,6 +1,6 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from typing import TYPE_CHECKING, Optional, Set
from ...tl import abcs, functions, types
from ..types import AsyncList, ChatLike, File, Participant, RecentAction, build_chat_map
@ -21,6 +21,7 @@ class ParticipantList(AsyncList[Participant]):
self._chat = chat
self._peer: Optional[abcs.InputPeer] = None
self._offset = 0
self._seen: Set[int] = set()
async def _fetch_next(self) -> None:
if self._peer is None:
@ -45,10 +46,17 @@ class ParticipantList(AsyncList[Participant]):
chat_map = build_chat_map(result.users, result.chats)
self._buffer.extend(
Participant._from_raw_channel(p, chat_map) for p in result.participants
)
seen_count = len(self._seen)
for p in result.participants:
part = Participant._from_raw_channel(p, chat_map)
pid = part._peer_id()
if pid not in self._seen:
self._seen.add(pid)
self._buffer.append(part)
self._total = result.count
self._offset += len(result.participants)
self._done = len(self._seen) == seen_count
elif isinstance(self._peer, types.InputPeerChat):
result = await self._client(

View File

@ -1,3 +1,4 @@
from .admin_right import AdminRight
from .async_list import AsyncList
from .callback_answer import CallbackAnswer
from .chat import (
@ -22,6 +23,7 @@ from .password_token import PasswordToken
from .recent_action import RecentAction
__all__ = [
"AdminRight",
"AsyncList",
"CallbackAnswer",
"Channel",

View File

@ -0,0 +1,104 @@
from __future__ import annotations
from enum import Enum
from typing import Set
from ...tl import abcs, types
class AdminRight(Enum):
"""
A right that can be granted to a chat's administrator.
.. note::
The specific values of the enumeration are not covered by `semver <https://semver.org/>`_.
They also may do nothing in future updates if Telegram decides to change them.
"""
CHANGE_INFO = "change_info"
"""Allows editing the description in a group or channel."""
POST_MESSAGES = "post_messages"
"""Allows sending messages in a broadcast channel."""
EDIT_MESSAGES = "edit_messages"
"""Allows editing messages in a group or channel."""
DELETE_MESSAGES = "delete_messages"
"""Allows deleting messages in a group or channel."""
BAN_USERS = "ban_users"
"""Allows setting the banned rights of other users in a group or channel."""
INVITE_USERS = "invite_users"
"""Allows inviting other users to the group or channel."""
PIN_MESSAGES = "pin_messages"
"""Allows pinning a message to the group or channel."""
MANAGE_ADMINS = "add_admins"
"""Allows setting the same or less administrator rights to other users in the group or channel."""
REMAIN_ANONYMOUS = "anonymous"
"""Allows the administrator to remain anonymous."""
MANAGE_CALLS = "manage_call"
"""Allows managing group or channel calls."""
OTHER = "other"
"""Unspecified."""
MANAGE_TOPICS = "manage_topics"
"""Allows managing the topics in a group."""
POST_STORIES = "post_stories"
"""Allows posting stories in a channel."""
EDIT_STORIES = "edit_stories"
"""Allows editing stories in a channel."""
DELETE_STORIES = "delete_stories"
"""Allows deleting stories in a channel."""
@classmethod
def _from_raw(cls, rights: abcs.ChatAdminRights) -> Set[AdminRight]:
assert isinstance(rights, types.ChatAdminRights)
all_rights = (
cls.CHANGE_INFO if rights.change_info else None,
cls.POST_MESSAGES if rights.post_messages else None,
cls.EDIT_MESSAGES if rights.edit_messages else None,
cls.DELETE_MESSAGES if rights.delete_messages else None,
cls.BAN_USERS if rights.ban_users else None,
cls.INVITE_USERS if rights.invite_users else None,
cls.PIN_MESSAGES if rights.pin_messages else None,
cls.MANAGE_ADMINS if rights.add_admins else None,
cls.REMAIN_ANONYMOUS if rights.anonymous else None,
cls.MANAGE_CALLS if rights.manage_call else None,
cls.OTHER if rights.other else None,
cls.MANAGE_TOPICS if rights.manage_topics else None,
cls.POST_STORIES if rights.post_stories else None,
cls.EDIT_STORIES if rights.edit_stories else None,
cls.DELETE_STORIES if rights.delete_stories else None,
)
return set(filter(None, iter(all_rights)))
@classmethod
def _chat_rights(cls) -> Set[AdminRight]:
return {
cls.CHANGE_INFO,
cls.POST_MESSAGES,
cls.EDIT_MESSAGES,
cls.DELETE_MESSAGES,
cls.BAN_USERS,
cls.INVITE_USERS,
cls.PIN_MESSAGES,
cls.MANAGE_ADMINS,
cls.REMAIN_ANONYMOUS,
cls.MANAGE_CALLS,
cls.OTHER,
cls.MANAGE_TOPICS,
cls.POST_STORIES,
cls.EDIT_STORIES,
cls.DELETE_STORIES,
}

View File

@ -1,7 +1,8 @@
from typing import Dict, Self, Union
from typing import Dict, Optional, Self, Set, Union
from ...tl import abcs, types
from .chat import Chat
from .admin_right import AdminRight
from .chat import Chat, User, peer_id
from .meta import NoPublicConstructor
@ -66,3 +67,98 @@ class Participant(metaclass=NoPublicConstructor):
return cls._create(participant, chat_map)
else:
raise RuntimeError("unexpected case")
def _peer_id(self) -> int:
if isinstance(
self._raw,
(
types.ChannelParticipant,
types.ChannelParticipantSelf,
types.ChannelParticipantCreator,
types.ChannelParticipantAdmin,
types.ChatParticipant,
types.ChatParticipantCreator,
types.ChatParticipantAdmin,
),
):
return self._raw.user_id
elif isinstance(
self._raw, (types.ChannelParticipantBanned, types.ChannelParticipantLeft)
):
return peer_id(self._raw.peer)
else:
raise RuntimeError("unexpected case")
@property
def user(self) -> Optional[User]:
"""
The user participant that is currently present in the chat.
This will be :data:`None` if the participant was instead :attr:`banned` or has :attr:`left`.
"""
if isinstance(
self._raw,
(
types.ChannelParticipant,
types.ChannelParticipantSelf,
types.ChannelParticipantCreator,
types.ChannelParticipantAdmin,
types.ChatParticipant,
types.ChatParticipantCreator,
types.ChatParticipantAdmin,
),
):
user = self._chat_map[self._raw.user_id]
assert isinstance(user, User)
return user
else:
return None
@property
def banned(self) -> Optional[Chat]:
"""
The banned participant.
This will usually be a :class:`User`.
"""
if isinstance(self._raw, types.ChannelParticipantBanned):
return self._chat_map[peer_id(self._raw.peer)]
else:
return None
@property
def left(self) -> Optional[Chat]:
"""
The participant that has left the group.
This will usually be a :class:`User`.
"""
if isinstance(self._raw, types.ChannelParticipantLeft):
return self._chat_map[peer_id(self._raw.peer)]
else:
return None
@property
def creator(self) -> bool:
"""
:data:`True` if the participant is the creator of the chat.
"""
return isinstance(
self._raw, (types.ChannelParticipantCreator, types.ChatParticipantCreator)
)
@property
def admin_rights(self) -> Optional[Set[AdminRight]]:
"""
The set of administrator rights this participant has been granted, if they are an administrator.
"""
if isinstance(
self._raw, (types.ChannelParticipantCreator, types.ChannelParticipantAdmin)
):
return AdminRight._from_raw(self._raw.admin_rights)
elif isinstance(
self._raw, (types.ChatParticipantCreator, types.ChatParticipantAdmin)
):
return AdminRight._chat_rights()
else:
return None

View File

@ -2,6 +2,7 @@
Classes for the various objects the library returns.
"""
from .._impl.client.types import (
AdminRight,
AsyncList,
CallbackAnswer,
Channel,
@ -22,6 +23,7 @@ from .._impl.client.types.buttons import Button, InlineButton
from .._impl.session import PackedChat, PackedType
__all__ = [
"AdminRight",
"AsyncList",
"CallbackAnswer",
"Channel",

View File

@ -0,0 +1,14 @@
from pytest import mark
from telethon._impl.client.types import AdminRight
from telethon._impl.tl import types
@mark.parametrize("slot", types.ChatAdminRights.__slots__)
def test_admin_right_covers_all(slot: str) -> None:
kwargs = {slot: False for slot in types.ChatAdminRights.__slots__}
kwargs[slot] = True
rights = types.ChatAdminRights(**kwargs)
rights_set = AdminRight._from_raw(rights)
assert len(rights_set) == 1
assert next(iter(rights_set)).value == slot