diff --git a/client/src/telethon/__init__.py b/client/src/telethon/__init__.py index 2e8c599c..17333f8e 100644 --- a/client/src/telethon/__init__.py +++ b/client/src/telethon/__init__.py @@ -1,6 +1,7 @@ from ._impl import tl as _tl from ._impl.client import Client, Config +from ._impl.mtproto import RpcError from ._impl.session import Session from .version import __version__ -__all__ = ["_tl", "Client", "Config", "Session"] +__all__ = ["_tl", "Client", "Config", "RpcError", "Session"] diff --git a/client/src/telethon/_impl/client/client/client.py b/client/src/telethon/_impl/client/client/client.py index 11b74d1c..e707116b 100644 --- a/client/src/telethon/_impl/client/client/client.py +++ b/client/src/telethon/_impl/client/client/client.py @@ -33,6 +33,7 @@ from ..events import Event from ..events.filters import Filter from ..types import ( AsyncList, + Chat, ChatLike, File, InFileLike, @@ -46,6 +47,7 @@ from ..types import ( from .auth import ( bot_sign_in, check_password, + interactive_login, is_authorized, request_login_code, sign_in, @@ -92,7 +94,13 @@ from .updates import ( remove_event_handler, set_handler_filter, ) -from .users import get_me, input_to_peer, resolve_to_packed +from .users import ( + get_contacts, + get_me, + input_to_peer, + resolve_to_packed, + resolve_username, +) Return = TypeVar("Return") T = TypeVar("T") @@ -169,6 +177,12 @@ class Client: await disconnect(self) async def download(self, media: MediaLike, file: OutFileLike) -> None: + """ + Download a file. + + This is simply a more convenient method to `iter_download`, + as it will handle dealing with the file chunks and writes by itself. + """ await download(self, media, file) async def edit_message( @@ -196,6 +210,9 @@ class Client: ) -> List[Message]: return await forward_messages(self, target, message_ids, source) + async def get_contacts(self) -> AsyncList[User]: + return await get_contacts(self) + def get_dialogs(self) -> None: get_dialogs(self) @@ -232,6 +249,9 @@ class Client: ) -> AsyncIterator[InlineResult]: return await inline_query(self, bot, query, chat=chat) + async def interactive_login(self) -> User: + return await interactive_login(self) + async def is_authorized(self) -> bool: return await is_authorized(self) @@ -257,6 +277,9 @@ class Client: async def resolve_to_packed(self, chat: ChatLike) -> PackedChat: return await resolve_to_packed(self, chat) + async def resolve_username(self) -> Chat: + return await resolve_username(self) + async def run_until_disconnected(self) -> None: await run_until_disconnected(self) @@ -299,6 +322,12 @@ class Client: title: Optional[str] = None, performer: Optional[str] = None, ) -> Message: + """ + Send an audio file. + + Unlike `send_file`, this method will attempt to guess the values for + duration, title and performer if they are not provided. + """ return await send_audio( self, chat, @@ -340,6 +369,19 @@ class Client: caption_markdown: Optional[str] = None, caption_html: Optional[str] = None, ) -> Message: + """ + Send any type of file with any amount of attributes. + + This method will not attempt to guess any of the file metadata such as + width, duration, title, etc. If you want to let the library attempt to + guess the file metadata, use the type-specific methods to send media: + `send_photo`, `send_audio` or `send_file`. + + Unlike `send_photo`, image files will be sent as documents by default. + + The parameters are used to construct a `File`. See the documentation + for `File.new` to learn what they do and when they are in effect. + """ return await send_file( self, chat, @@ -398,6 +440,20 @@ class Client: width: Optional[int] = None, height: Optional[int] = None, ) -> Message: + """ + Send a photo file. + + Exactly one of path, url or file must be specified. + A `File` can also be used as the second parameter. + + By default, the server will be allowed to `compress` the image. + Only compressed images can be displayed as photos in applications. + Images that cannot be compressed will be sent as file documents, + with a thumbnail if possible. + + Unlike `send_file`, this method will attempt to guess the values for + width and height if they are not provided and the can't be compressed. + """ return await send_photo( self, chat, @@ -426,6 +482,12 @@ class Client: round: bool = False, supports_streaming: bool = False, ) -> Message: + """ + Send a video file. + + Unlike `send_file`, this method will attempt to guess the values for + duration, width and height if they are not provided. + """ return await send_video( self, chat, diff --git a/client/src/telethon/_impl/client/client/users.py b/client/src/telethon/_impl/client/client/users.py index 271e0409..0cb4288b 100644 --- a/client/src/telethon/_impl/client/client/users.py +++ b/client/src/telethon/_impl/client/client/users.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional from ...session import PackedChat, PackedType from ...tl import abcs, types -from ..types import Channel, ChatLike, Group, User +from ..types import AsyncList, Channel, Chat, ChatLike, Group, User if TYPE_CHECKING: from .client import Client @@ -15,6 +15,16 @@ async def get_me(self: Client) -> User: raise NotImplementedError +async def get_contacts(self: Client) -> AsyncList[User]: + self + raise NotImplementedError + + +async def resolve_username(self: Client) -> Chat: + self + raise NotImplementedError + + async def resolve_to_packed(self: Client, chat: ChatLike) -> PackedChat: if isinstance(chat, (User, Group, Channel)): packed = chat.pack() diff --git a/client/src/telethon/_impl/client/types/chat/__init__.py b/client/src/telethon/_impl/client/types/chat/__init__.py index 4be77fe0..92795878 100644 --- a/client/src/telethon/_impl/client/types/chat/__init__.py +++ b/client/src/telethon/_impl/client/types/chat/__init__.py @@ -1,12 +1,11 @@ from typing import Union from ....session import PackedChat -from ....tl import abcs from .channel import Channel from .group import Group from .user import RestrictionReason, User Chat = Union[Channel, Group, User] -ChatLike = Union[Chat, PackedChat, int, str, abcs.InputPeer] +ChatLike = Union[Chat, PackedChat, int, str] __all__ = ["Chat", "ChatLike", "Channel", "Group", "RestrictionReason", "User"] diff --git a/client/src/telethon/_impl/client/types/message.py b/client/src/telethon/_impl/client/types/message.py index 11983d6b..c70ba05c 100644 --- a/client/src/telethon/_impl/client/types/message.py +++ b/client/src/telethon/_impl/client/types/message.py @@ -41,6 +41,10 @@ class Message(metaclass=NoPublicConstructor): def chat(self) -> Chat: raise NotImplementedError + @property + def sender(self) -> Chat: + raise NotImplementedError + def _file(self) -> Optional[File]: return ( File._try_from_raw(self._raw.media) diff --git a/client/src/telethon/_impl/mtproto/mtp/types.py b/client/src/telethon/_impl/mtproto/mtp/types.py index 8c2f6a2e..536aa44d 100644 --- a/client/src/telethon/_impl/mtproto/mtp/types.py +++ b/client/src/telethon/_impl/mtproto/mtp/types.py @@ -20,10 +20,22 @@ class RpcError(ValueError): append_value = f" ({value})" if value else None super().__init__(f"rpc error {code}: {name}{append_value}") - self.code = code - self.name = name - self.value = value - self.caused_by = caused_by + self._code = code + self._name = name + self._value = value + self._caused_by = caused_by + + @property + def code(self) -> int: + return self._code + + @property + def name(self) -> str: + return self._name + + @property + def value(self) -> Optional[int]: + return self._value @classmethod def from_mtproto_error(cls, error: GeneratedRpcError) -> Self: @@ -49,9 +61,9 @@ class RpcError(ValueError): if not isinstance(other, self.__class__): return NotImplemented return ( - self.code == other.code - and self.name == other.name - and self.value == other.value + self._code == other._code + and self._name == other._name + and self._value == other._value ) @@ -64,13 +76,17 @@ class BadMessage(ValueError): ) -> None: super().__init__(f"bad msg: {code}") - self.code = code - self.caused_by = caused_by + self._code = code + self._caused_by = caused_by + + @property + def code(self) -> int: + return self._code def __eq__(self, other: object) -> bool: if not isinstance(other, self.__class__): return NotImplemented - return self.code == other.code + return self._code == other._code RpcResult = bytes | RpcError | BadMessage diff --git a/client/src/telethon/session.py b/client/src/telethon/session.py new file mode 100644 index 00000000..eda49dd2 --- /dev/null +++ b/client/src/telethon/session.py @@ -0,0 +1,21 @@ +from ._impl.session import ( + ChannelState, + DataCenter, + MemorySession, + Session, + SqliteSession, + Storage, + UpdateState, + User, +) + +__all__ = [ + "ChannelState", + "DataCenter", + "MemorySession", + "Session", + "SqliteSession", + "Storage", + "UpdateState", + "User", +] diff --git a/client/src/telethon/types.py b/client/src/telethon/types.py new file mode 100644 index 00000000..add55ea2 --- /dev/null +++ b/client/src/telethon/types.py @@ -0,0 +1,37 @@ +from ._impl.client.types import ( + AsyncList, + Channel, + Chat, + ChatLike, + File, + Group, + InFileLike, + LoginToken, + MediaLike, + Message, + NoPublicConstructor, + OutFileLike, + PasswordToken, + RestrictionReason, + User, +) +from ._impl.session import PackedChat + +__all__ = [ + "AsyncList", + "Channel", + "Chat", + "ChatLike", + "File", + "Group", + "InFileLike", + "LoginToken", + "MediaLike", + "Message", + "NoPublicConstructor", + "OutFileLike", + "PasswordToken", + "RestrictionReason", + "User", + "PackedChat", +]