From 04806a2a4acab497644de8b76e08200fece2e78e Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Mon, 16 Oct 2023 20:11:08 +0200 Subject: [PATCH] Create a base class for Chat --- .../telethon/_impl/client/types/__init__.py | 3 +- .../_impl/client/types/chat/__init__.py | 6 +- .../_impl/client/types/chat/channel.py | 30 +++++---- .../telethon/_impl/client/types/chat/chat.py | 60 +++++++++++++++++ .../telethon/_impl/client/types/chat/group.py | 35 ++++++---- .../telethon/_impl/client/types/chat/user.py | 66 +++++++------------ client/src/telethon/_impl/client/utils.py | 5 +- client/src/telethon/types.py | 8 --- 8 files changed, 128 insertions(+), 85 deletions(-) create mode 100644 client/src/telethon/_impl/client/types/chat/chat.py diff --git a/client/src/telethon/_impl/client/types/__init__.py b/client/src/telethon/_impl/client/types/__init__.py index 4f781d18..d6b2a2c3 100644 --- a/client/src/telethon/_impl/client/types/__init__.py +++ b/client/src/telethon/_impl/client/types/__init__.py @@ -1,5 +1,5 @@ from .async_list import AsyncList -from .chat import Channel, Chat, ChatLike, Group, RestrictionReason, User +from .chat import Channel, Chat, ChatLike, Group, User from .dialog import Dialog from .draft import Draft from .file import File, InFileLike, OutFileLike, OutWrapper @@ -17,7 +17,6 @@ __all__ = [ "Chat", "ChatLike", "Group", - "RestrictionReason", "User", "Dialog", "Draft", diff --git a/client/src/telethon/_impl/client/types/chat/__init__.py b/client/src/telethon/_impl/client/types/chat/__init__.py index 92795878..14460b5d 100644 --- a/client/src/telethon/_impl/client/types/chat/__init__.py +++ b/client/src/telethon/_impl/client/types/chat/__init__.py @@ -2,10 +2,10 @@ from typing import Union from ....session import PackedChat from .channel import Channel +from .chat import Chat from .group import Group -from .user import RestrictionReason, User +from .user import User -Chat = Union[Channel, Group, User] ChatLike = Union[Chat, PackedChat, int, str] -__all__ = ["Chat", "ChatLike", "Channel", "Group", "RestrictionReason", "User"] +__all__ = ["Chat", "ChatLike", "Channel", "Group", "User"] diff --git a/client/src/telethon/_impl/client/types/chat/channel.py b/client/src/telethon/_impl/client/types/chat/channel.py index 3228368e..f056fd92 100644 --- a/client/src/telethon/_impl/client/types/chat/channel.py +++ b/client/src/telethon/_impl/client/types/chat/channel.py @@ -3,9 +3,10 @@ from typing import Optional, Self, Union from ....session import PackedChat, PackedType from ....tl import abcs, types from ..meta import NoPublicConstructor +from .chat import Chat -class Channel(metaclass=NoPublicConstructor): +class Channel(Chat, metaclass=NoPublicConstructor): """ A broadcast channel. @@ -32,10 +33,25 @@ class Channel(metaclass=NoPublicConstructor): else: raise RuntimeError("unexpected case") + # region Overrides + @property def id(self) -> int: return self._raw.id + @property + def name(self) -> str: + """ + The channel's title. + + This property is always present, but may be the empty string. + """ + return self._raw.title + + @property + def username(self) -> Optional[str]: + return getattr(self._raw, "username", None) + def pack(self) -> Optional[PackedChat]: if self._raw.access_hash is None: return None @@ -48,14 +64,4 @@ class Channel(metaclass=NoPublicConstructor): access_hash=None, ) - @property - def title(self) -> str: - return getattr(self._raw, "title", None) or "" - - @property - def full_name(self) -> str: - return self.title - - @property - def username(self) -> Optional[str]: - return getattr(self._raw, "username", None) + # endregion Overrides diff --git a/client/src/telethon/_impl/client/types/chat/chat.py b/client/src/telethon/_impl/client/types/chat/chat.py new file mode 100644 index 00000000..35fcaed7 --- /dev/null +++ b/client/src/telethon/_impl/client/types/chat/chat.py @@ -0,0 +1,60 @@ +import abc +from typing import Optional + +from ....session import PackedChat + + +class Chat(abc.ABC): + """ + The base class for all chat types. + + This will either be a :class:`User`, :class:`Group` or :class:`Channel`. + """ + + __slots__ = () + + @property + @abc.abstractmethod + def id(self) -> int: + """ + The chat's integer identifier. + + This identifier is always a positive number. + + This property is always present. + """ + + @property + @abc.abstractmethod + def name(self) -> str: + """ + The full name of the user, group or channel. + + For users, this will be the :attr:`User.first_name` concatenated with the :attr:`User.last_name`. + + For groups and channels, this will be their title. + + If there is no name (such as for deleted accounts), an empty string ``''`` will be returned. + """ + + @property + @abc.abstractmethod + def username(self) -> Optional[str]: + """ + The primary *@username* of the chat. + + The returned string will *not* contain the at-sign ``@``. + """ + + @abc.abstractmethod + def pack(self) -> Optional[PackedChat]: + """ + Pack the chat into a compact and reusable object. + + This object can be easily serialized and saved to persistent storage. + Unlike resolving usernames, packed chats can be reused without costly calls. + + .. seealso:: + + :doc:`/concepts/chats` + """ diff --git a/client/src/telethon/_impl/client/types/chat/group.py b/client/src/telethon/_impl/client/types/chat/group.py index 6eba2602..719fc220 100644 --- a/client/src/telethon/_impl/client/types/chat/group.py +++ b/client/src/telethon/_impl/client/types/chat/group.py @@ -3,9 +3,10 @@ from typing import Optional, Self, Union from ....session import PackedChat, PackedType from ....tl import abcs, types from ..meta import NoPublicConstructor +from .chat import Chat -class Group(metaclass=NoPublicConstructor): +class Group(Chat, metaclass=NoPublicConstructor): """ A small group or supergroup. @@ -38,10 +39,25 @@ class Group(metaclass=NoPublicConstructor): else: raise RuntimeError("unexpected case") + # region Overrides + @property def id(self) -> int: return self._raw.id + @property + def name(self) -> str: + """ + The group's title. + + This property is always present, but may be the empty string. + """ + return self._raw.title + + @property + def username(self) -> Optional[str]: + return getattr(self._raw, "username", None) + def pack(self) -> Optional[PackedChat]: if isinstance(self._raw, (types.ChatEmpty, types.Chat, types.ChatForbidden)): return PackedChat(ty=PackedType.CHAT, id=self._raw.id, access_hash=None) @@ -52,18 +68,13 @@ class Group(metaclass=NoPublicConstructor): ty=PackedType.MEGAGROUP, id=self._raw.id, access_hash=None ) - @property - def title(self) -> str: - return getattr(self._raw, "title", None) or "" - - @property - def full_name(self) -> str: - return self.title - - @property - def username(self) -> Optional[str]: - return getattr(self._raw, "username", None) + # endregion Overrides @property def is_megagroup(self) -> bool: + """ + Whether the group is a supergroup. + + These are known as "megagroups" in Telegram's API, and are different from "gigagroups". + """ return isinstance(self._raw, (types.Channel, types.ChannelForbidden)) diff --git a/client/src/telethon/_impl/client/types/chat/user.py b/client/src/telethon/_impl/client/types/chat/user.py index 1b76aa12..4a9de6b2 100644 --- a/client/src/telethon/_impl/client/types/chat/user.py +++ b/client/src/telethon/_impl/client/types/chat/user.py @@ -1,39 +1,12 @@ -from typing import List, Optional, Self +from typing import Optional, Self from ....session import PackedChat, PackedType from ....tl import abcs, types from ..meta import NoPublicConstructor +from .chat import Chat -class RestrictionReason(metaclass=NoPublicConstructor): - """ - A restriction reason for :class:`telethon.types.User`. - """ - - __slots__ = ("_raw",) - - def __init__(self, raw: types.RestrictionReason) -> None: - self._raw = raw - - @classmethod - def _from_raw(cls, reason: abcs.RestrictionReason) -> Self: - assert isinstance(reason, types.RestrictionReason) - return cls._create(reason) - - @property - def platforms(self) -> List[str]: - return self._raw.platform.split("-") - - @property - def reason(self) -> str: - return self._raw.reason - - @property - def text(self) -> str: - return self._raw.text - - -class User(metaclass=NoPublicConstructor): +class User(Chat, metaclass=NoPublicConstructor): """ A user, representing either a bot account or an account created with a phone number. @@ -95,10 +68,27 @@ class User(metaclass=NoPublicConstructor): else: raise RuntimeError("unexpected case") + # region Overrides + @property def id(self) -> int: return self._raw.id + @property + def name(self) -> str: + """ + The user's full name. + + This property joins both the :attr:`first_name` and :attr:`last_name` into a single string. + + This property is always present, but may be the empty string. + """ + return f"{self.first_name} {self.last_name}".strip() + + @property + def username(self) -> Optional[str]: + return self._raw.username + def pack(self) -> Optional[PackedChat]: if self._raw.access_hash is not None: return PackedChat( @@ -109,6 +99,8 @@ class User(metaclass=NoPublicConstructor): else: return None + # endregion Overrides + @property def first_name(self) -> str: return self._raw.first_name or "" @@ -117,14 +109,6 @@ class User(metaclass=NoPublicConstructor): def last_name(self) -> str: return self._raw.last_name or "" - @property - def full_name(self) -> str: - return f"{self.first_name} {self.last_name}".strip() - - @property - def username(self) -> Optional[str]: - return self._raw.username - @property def phone(self) -> Optional[str]: return self._raw.phone @@ -132,9 +116,3 @@ class User(metaclass=NoPublicConstructor): @property def bot(self) -> bool: return self._raw.bot - - @property - def restriction_reasons(self) -> List[RestrictionReason]: - return [ - RestrictionReason._from_raw(r) for r in (self._raw.restriction_reason or []) - ] diff --git a/client/src/telethon/_impl/client/utils.py b/client/src/telethon/_impl/client/utils.py index bb44df3d..673c54b2 100644 --- a/client/src/telethon/_impl/client/utils.py +++ b/client/src/telethon/_impl/client/utils.py @@ -27,10 +27,7 @@ def build_chat_map(users: List[abcs.User], chats: List[abcs.Chat]) -> Dict[int, for c in chats ) - # https://github.com/python/mypy/issues/2115 - result: Dict[int, Chat] = { - c.id: c for c in itertools.chain(users_iter, chats_iter) # type: ignore [attr-defined, misc] - } + result: Dict[int, Chat] = {c.id: c for c in itertools.chain(users_iter, chats_iter)} if len(result) != len(users) + len(chats): # The fabled ID collision between different chat types. diff --git a/client/src/telethon/types.py b/client/src/telethon/types.py index 17a271c0..89aac915 100644 --- a/client/src/telethon/types.py +++ b/client/src/telethon/types.py @@ -5,20 +5,16 @@ from ._impl.client.types import ( AsyncList, Channel, Chat, - ChatLike, Dialog, Draft, File, Group, - InFileLike, InlineResult, LoginToken, Message, - OutFileLike, Participant, PasswordToken, RecentAction, - RestrictionReason, User, ) from ._impl.session import PackedChat, PackedType @@ -28,19 +24,15 @@ __all__ = [ "AsyncList", "Channel", "Chat", - "ChatLike", "Dialog", "Draft", "File", "Group", - "InFileLike", "LoginToken", "Message", - "OutFileLike", "Participant", "PasswordToken", "RecentAction", - "RestrictionReason", "User", "PackedChat", "PackedType",