mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-31 07:57:38 +03:00 
			
		
		
		
	Replace weird mixin Client classes with free-standing defs
This should take care of the extremely precarious subclassing order. It should also make IDEs go a lot less crazy. Documentation and code can be kept separated.
This commit is contained in:
		
							parent
							
								
									af201c12ba
								
							
						
					
					
						commit
						f639992baa
					
				|  | @ -13,12 +13,27 @@ from Telethon version 1.x to 2.0 onwards. | |||
| User, chat and channel identifiers are now 64-bit numbers | ||||
| --------------------------------------------------------- | ||||
| 
 | ||||
| `Layer 133 <https://diff.telethon.dev/?from=132&to=133>`__ changed *a lot* of | ||||
| identifiers from ``int`` to ``long``, meaning they will no longer fit in 32 | ||||
| bits, and instead require 64 bits. | ||||
| `Layer 133 <https://diff.telethon.dev/?from=132&to=133>`__ changed *a lot* of identifiers from | ||||
| ``int`` to ``long``, meaning they will no longer fit in 32 bits, and instead require 64 bits. | ||||
| 
 | ||||
| If you were storing these identifiers somewhere size did matter (for example, | ||||
| a database), you will need to migrate that to support the new size requirement | ||||
| of 8 bytes. | ||||
| If you were storing these identifiers somewhere size did matter (for example, a database), you | ||||
| will need to migrate that to support the new size requirement of 8 bytes. | ||||
| 
 | ||||
| For the full list of types changed, please review the above link. | ||||
| 
 | ||||
| 
 | ||||
| Many modules are now private | ||||
| ---------------------------- | ||||
| 
 | ||||
| There were a lot of things which were public but should not have been. From now on, you should | ||||
| only rely on things that are either publicly re-exported or defined. That is, as soon as anything | ||||
| starts with an underscore (``_``) on its name, you're acknowledging that the functionality may | ||||
| change even across minor version changes, and thus have your code break. | ||||
| 
 | ||||
| * The ``telethon.client`` module is now ``telethon._client``, meaning you should stop relying on | ||||
|   anything inside of it. This includes all of the subclasses that used to exist (like ``UserMethods``). | ||||
| 
 | ||||
|   TODO REVIEW self\._\w+\( | ||||
|   and __signature__ | ||||
|   and property | ||||
|   abs abc abstract | ||||
|  | @ -1,4 +1,4 @@ | |||
| from .client.telegramclient import TelegramClient | ||||
| from ._client.telegramclient import TelegramClient | ||||
| from .network import connection | ||||
| from .tl import types, functions, custom | ||||
| from .tl.custom import Button | ||||
|  |  | |||
|  | @ -107,8 +107,7 @@ class _TakeoutClient: | |||
|         return setattr(self.__client, name, value) | ||||
| 
 | ||||
| 
 | ||||
| class AccountMethods: | ||||
|     def takeout( | ||||
| def takeout( | ||||
|         self: 'TelegramClient', | ||||
|         finalize: bool = True, | ||||
|         *, | ||||
|  | @ -119,87 +118,6 @@ class AccountMethods: | |||
|         channels: bool = None, | ||||
|         files: bool = None, | ||||
|         max_file_size: bool = None) -> 'TelegramClient': | ||||
|         """ | ||||
|         Returns a :ref:`telethon-client` which calls methods behind a takeout session. | ||||
| 
 | ||||
|         It does so by creating a proxy object over the current client through | ||||
|         which making requests will use :tl:`InvokeWithTakeoutRequest` to wrap | ||||
|         them. In other words, returns the current client modified so that | ||||
|         requests are done as a takeout: | ||||
| 
 | ||||
|         Some of the calls made through the takeout session will have lower | ||||
|         flood limits. This is useful if you want to export the data from | ||||
|         conversations or mass-download media, since the rate limits will | ||||
|         be lower. Only some requests will be affected, and you will need | ||||
|         to adjust the `wait_time` of methods like `client.iter_messages | ||||
|         <telethon.client.messages.MessageMethods.iter_messages>`. | ||||
| 
 | ||||
|         By default, all parameters are `None`, and you need to enable those | ||||
|         you plan to use by setting them to either `True` or `False`. | ||||
| 
 | ||||
|         You should ``except errors.TakeoutInitDelayError as e``, since this | ||||
|         exception will raise depending on the condition of the session. You | ||||
|         can then access ``e.seconds`` to know how long you should wait for | ||||
|         before calling the method again. | ||||
| 
 | ||||
|         There's also a `success` property available in the takeout proxy | ||||
|         object, so from the `with` body you can set the boolean result that | ||||
|         will be sent back to Telegram. But if it's left `None` as by | ||||
|         default, then the action is based on the `finalize` parameter. If | ||||
|         it's `True` then the takeout will be finished, and if no exception | ||||
|         occurred during it, then `True` will be considered as a result. | ||||
|         Otherwise, the takeout will not be finished and its ID will be | ||||
|         preserved for future usage as `client.session.takeout_id | ||||
|         <telethon.sessions.abstract.Session.takeout_id>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             finalize (`bool`): | ||||
|                 Whether the takeout session should be finalized upon | ||||
|                 exit or not. | ||||
| 
 | ||||
|             contacts (`bool`): | ||||
|                 Set to `True` if you plan on downloading contacts. | ||||
| 
 | ||||
|             users (`bool`): | ||||
|                 Set to `True` if you plan on downloading information | ||||
|                 from users and their private conversations with you. | ||||
| 
 | ||||
|             chats (`bool`): | ||||
|                 Set to `True` if you plan on downloading information | ||||
|                 from small group chats, such as messages and media. | ||||
| 
 | ||||
|             megagroups (`bool`): | ||||
|                 Set to `True` if you plan on downloading information | ||||
|                 from megagroups (channels), such as messages and media. | ||||
| 
 | ||||
|             channels (`bool`): | ||||
|                 Set to `True` if you plan on downloading information | ||||
|                 from broadcast channels, such as messages and media. | ||||
| 
 | ||||
|             files (`bool`): | ||||
|                 Set to `True` if you plan on downloading media and | ||||
|                 you don't only wish to export messages. | ||||
| 
 | ||||
|             max_file_size (`int`): | ||||
|                 The maximum file size, in bytes, that you plan | ||||
|                 to download for each message with media. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 from telethon import errors | ||||
| 
 | ||||
|                 try: | ||||
|                     async with client.takeout() as takeout: | ||||
|                         await client.get_messages('me')  # normal call | ||||
|                         await takeout.get_messages('me')  # wrapped through takeout (less limits) | ||||
| 
 | ||||
|                         async for message in takeout.iter_messages(chat, wait_time=0): | ||||
|                             ...  # Do something with the message | ||||
| 
 | ||||
|                 except errors.TakeoutInitDelayError as e: | ||||
|                     print('Must wait', e.seconds, 'before takeout') | ||||
|         """ | ||||
|     request_kwargs = dict( | ||||
|         contacts=contacts, | ||||
|         message_users=users, | ||||
|  | @ -219,22 +137,7 @@ class AccountMethods: | |||
| 
 | ||||
|     return _TakeoutClient(finalize, self, request) | ||||
| 
 | ||||
|     async def end_takeout(self: 'TelegramClient', success: bool) -> bool: | ||||
|         """ | ||||
|         Finishes the current takeout session. | ||||
| 
 | ||||
|         Arguments | ||||
|             success (`bool`): | ||||
|                 Whether the takeout completed successfully or not. | ||||
| 
 | ||||
|         Returns | ||||
|             `True` if the operation was successful, `False` otherwise. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 await client.end_takeout(success=False) | ||||
|         """ | ||||
| async def end_takeout(self: 'TelegramClient', success: bool) -> bool: | ||||
|     try: | ||||
|         async with _TakeoutClient(True, self, None) as takeout: | ||||
|             takeout.success = success | ||||
|  |  | |||
|  | @ -12,11 +12,7 @@ if typing.TYPE_CHECKING: | |||
|     from .telegramclient import TelegramClient | ||||
| 
 | ||||
| 
 | ||||
| class AuthMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     def start( | ||||
| def start( | ||||
|         self: 'TelegramClient', | ||||
|         phone: typing.Callable[[], str] = lambda: input('Please enter your phone (or bot token): '), | ||||
|         password: typing.Callable[[], str] = lambda: getpass.getpass('Please enter your password: '), | ||||
|  | @ -27,81 +23,6 @@ class AuthMethods: | |||
|         first_name: str = 'New User', | ||||
|         last_name: str = '', | ||||
|         max_attempts: int = 3) -> 'TelegramClient': | ||||
|         """ | ||||
|         Starts the client (connects and logs in if necessary). | ||||
| 
 | ||||
|         By default, this method will be interactive (asking for | ||||
|         user input if needed), and will handle 2FA if enabled too. | ||||
| 
 | ||||
|         If the phone doesn't belong to an existing account (and will hence | ||||
|         `sign_up` for a new one),  **you are agreeing to Telegram's | ||||
|         Terms of Service. This is required and your account | ||||
|         will be banned otherwise.** See https://telegram.org/tos | ||||
|         and https://core.telegram.org/api/terms. | ||||
| 
 | ||||
|         If the event loop is already running, this method returns a | ||||
|         coroutine that you should await on your own code; otherwise | ||||
|         the loop is ran until said coroutine completes. | ||||
| 
 | ||||
|         Arguments | ||||
|             phone (`str` | `int` | `callable`): | ||||
|                 The phone (or callable without arguments to get it) | ||||
|                 to which the code will be sent. If a bot-token-like | ||||
|                 string is given, it will be used as such instead. | ||||
|                 The argument may be a coroutine. | ||||
| 
 | ||||
|             password (`str`, `callable`, optional): | ||||
|                 The password for 2 Factor Authentication (2FA). | ||||
|                 This is only required if it is enabled in your account. | ||||
|                 The argument may be a coroutine. | ||||
| 
 | ||||
|             bot_token (`str`): | ||||
|                 Bot Token obtained by `@BotFather <https://t.me/BotFather>`_ | ||||
|                 to log in as a bot. Cannot be specified with ``phone`` (only | ||||
|                 one of either allowed). | ||||
| 
 | ||||
|             force_sms (`bool`, optional): | ||||
|                 Whether to force sending the code request as SMS. | ||||
|                 This only makes sense when signing in with a `phone`. | ||||
| 
 | ||||
|             code_callback (`callable`, optional): | ||||
|                 A callable that will be used to retrieve the Telegram | ||||
|                 login code. Defaults to `input()`. | ||||
|                 The argument may be a coroutine. | ||||
| 
 | ||||
|             first_name (`str`, optional): | ||||
|                 The first name to be used if signing up. This has no | ||||
|                 effect if the account already exists and you sign in. | ||||
| 
 | ||||
|             last_name (`str`, optional): | ||||
|                 Similar to the first name, but for the last. Optional. | ||||
| 
 | ||||
|             max_attempts (`int`, optional): | ||||
|                 How many times the code/password callback should be | ||||
|                 retried or switching between signing in and signing up. | ||||
| 
 | ||||
|         Returns | ||||
|             This `TelegramClient`, so initialization | ||||
|             can be chained with ``.start()``. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 client = TelegramClient('anon', api_id, api_hash) | ||||
| 
 | ||||
|                 # Starting as a bot account | ||||
|                 await client.start(bot_token=bot_token) | ||||
| 
 | ||||
|                 # Starting as a user account | ||||
|                 await client.start(phone) | ||||
|                 # Please enter the code you received: 12345 | ||||
|                 # Please enter your password: ******* | ||||
|                 # (You are now logged in) | ||||
| 
 | ||||
|                 # Starting using a context manager (this calls start()): | ||||
|                 with client: | ||||
|                     pass | ||||
|         """ | ||||
|     if code_callback is None: | ||||
|         def code_callback(): | ||||
|             return input('Please enter the code you received: ') | ||||
|  | @ -133,7 +54,7 @@ class AuthMethods: | |||
|         else self.loop.run_until_complete(coro) | ||||
|     ) | ||||
| 
 | ||||
|     async def _start( | ||||
| async def _start( | ||||
|         self: 'TelegramClient', phone, password, bot_token, force_sms, | ||||
|         code_callback, first_name, last_name, max_attempts): | ||||
|     if not self.is_connected(): | ||||
|  | @ -261,7 +182,7 @@ class AuthMethods: | |||
| 
 | ||||
|     return self | ||||
| 
 | ||||
|     def _parse_phone_and_hash(self, phone, phone_hash): | ||||
| def _parse_phone_and_hash(self, phone, phone_hash): | ||||
|     """ | ||||
|     Helper method to both parse and validate phone and its hash. | ||||
|     """ | ||||
|  | @ -277,7 +198,7 @@ class AuthMethods: | |||
| 
 | ||||
|     return phone, phone_hash | ||||
| 
 | ||||
|     async def sign_in( | ||||
| async def sign_in( | ||||
|         self: 'TelegramClient', | ||||
|         phone: str = None, | ||||
|         code: typing.Union[str, int] = None, | ||||
|  | @ -285,55 +206,6 @@ class AuthMethods: | |||
|         password: str = None, | ||||
|         bot_token: str = None, | ||||
|         phone_code_hash: str = None) -> 'typing.Union[types.User, types.auth.SentCode]': | ||||
|         """ | ||||
|         Logs in to Telegram to an existing user or bot account. | ||||
| 
 | ||||
|         You should only use this if you are not authorized yet. | ||||
| 
 | ||||
|         This method will send the code if it's not provided. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             In most cases, you should simply use `start()` and not this method. | ||||
| 
 | ||||
|         Arguments | ||||
|             phone (`str` | `int`): | ||||
|                 The phone to send the code to if no code was provided, | ||||
|                 or to override the phone that was previously used with | ||||
|                 these requests. | ||||
| 
 | ||||
|             code (`str` | `int`): | ||||
|                 The code that Telegram sent. Note that if you have sent this | ||||
|                 code through the application itself it will immediately | ||||
|                 expire. If you want to send the code, obfuscate it somehow. | ||||
|                 If you're not doing any of this you can ignore this note. | ||||
| 
 | ||||
|             password (`str`): | ||||
|                 2FA password, should be used if a previous call raised | ||||
|                 ``SessionPasswordNeededError``. | ||||
| 
 | ||||
|             bot_token (`str`): | ||||
|                 Used to sign in as a bot. Not all requests will be available. | ||||
|                 This should be the hash the `@BotFather <https://t.me/BotFather>`_ | ||||
|                 gave you. | ||||
| 
 | ||||
|             phone_code_hash (`str`, optional): | ||||
|                 The hash returned by `send_code_request`. This can be left as | ||||
|                 `None` to use the last hash known for the phone to be used. | ||||
| 
 | ||||
|         Returns | ||||
|             The signed in user, or the information about | ||||
|             :meth:`send_code_request`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 phone = '+34 123 123 123' | ||||
|                 await client.sign_in(phone)  # send code | ||||
| 
 | ||||
|                 code = input('enter code: ') | ||||
|                 await client.sign_in(phone, code) | ||||
|         """ | ||||
|     me = await self.get_me() | ||||
|     if me: | ||||
|         return me | ||||
|  | @ -373,7 +245,7 @@ class AuthMethods: | |||
| 
 | ||||
|     return self._on_login(result.user) | ||||
| 
 | ||||
|     async def sign_up( | ||||
| async def sign_up( | ||||
|         self: 'TelegramClient', | ||||
|         code: typing.Union[str, int], | ||||
|         first_name: str, | ||||
|  | @ -381,48 +253,6 @@ class AuthMethods: | |||
|         *, | ||||
|         phone: str = None, | ||||
|         phone_code_hash: str = None) -> 'types.User': | ||||
|         """ | ||||
|         Signs up to Telegram as a new user account. | ||||
| 
 | ||||
|         Use this if you don't have an account yet. | ||||
| 
 | ||||
|         You must call `send_code_request` first. | ||||
| 
 | ||||
|         **By using this method you're agreeing to Telegram's | ||||
|         Terms of Service. This is required and your account | ||||
|         will be banned otherwise.** See https://telegram.org/tos | ||||
|         and https://core.telegram.org/api/terms. | ||||
| 
 | ||||
|         Arguments | ||||
|             code (`str` | `int`): | ||||
|                 The code sent by Telegram | ||||
| 
 | ||||
|             first_name (`str`): | ||||
|                 The first name to be used by the new account. | ||||
| 
 | ||||
|             last_name (`str`, optional) | ||||
|                 Optional last name. | ||||
| 
 | ||||
|             phone (`str` | `int`, optional): | ||||
|                 The phone to sign up. This will be the last phone used by | ||||
|                 default (you normally don't need to set this). | ||||
| 
 | ||||
|             phone_code_hash (`str`, optional): | ||||
|                 The hash returned by `send_code_request`. This can be left as | ||||
|                 `None` to use the last hash known for the phone to be used. | ||||
| 
 | ||||
|         Returns | ||||
|             The new created :tl:`User`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 phone = '+34 123 123 123' | ||||
|                 await client.send_code_request(phone) | ||||
| 
 | ||||
|                 code = input('enter code: ') | ||||
|                 await client.sign_up(code, first_name='Anna', last_name='Banana') | ||||
|         """ | ||||
|     me = await self.get_me() | ||||
|     if me: | ||||
|         return me | ||||
|  | @ -468,10 +298,9 @@ class AuthMethods: | |||
| 
 | ||||
|     return self._on_login(result.user) | ||||
| 
 | ||||
|     def _on_login(self, user): | ||||
| def _on_login(self, user): | ||||
|     """ | ||||
|     Callback called whenever the login or sign up process completes. | ||||
| 
 | ||||
|     Returns the input user parameter. | ||||
|     """ | ||||
|     self._bot = bool(user.bot) | ||||
|  | @ -480,31 +309,11 @@ class AuthMethods: | |||
| 
 | ||||
|     return user | ||||
| 
 | ||||
|     async def send_code_request( | ||||
| async def send_code_request( | ||||
|         self: 'TelegramClient', | ||||
|         phone: str, | ||||
|         *, | ||||
|         force_sms: bool = False) -> 'types.auth.SentCode': | ||||
|         """ | ||||
|         Sends the Telegram code needed to login to the given phone number. | ||||
| 
 | ||||
|         Arguments | ||||
|             phone (`str` | `int`): | ||||
|                 The phone to which the code will be sent. | ||||
| 
 | ||||
|             force_sms (`bool`, optional): | ||||
|                 Whether to force sending as SMS. | ||||
| 
 | ||||
|         Returns | ||||
|             An instance of :tl:`SentCode`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 phone = '+34 123 123 123' | ||||
|                 sent = await client.send_code_request(phone) | ||||
|                 print(sent) | ||||
|         """ | ||||
|     result = None | ||||
|     phone = utils.parse_phone(phone) or self._phone | ||||
|     phone_hash = self._phone_code_hash.get(phone) | ||||
|  | @ -536,56 +345,12 @@ class AuthMethods: | |||
| 
 | ||||
|     return result | ||||
| 
 | ||||
|     async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> custom.QRLogin: | ||||
|         """ | ||||
|         Initiates the QR login procedure. | ||||
| 
 | ||||
|         Note that you must be connected before invoking this, as with any | ||||
|         other request. | ||||
| 
 | ||||
|         It is up to the caller to decide how to present the code to the user, | ||||
|         whether it's the URL, using the token bytes directly, or generating | ||||
|         a QR code and displaying it by other means. | ||||
| 
 | ||||
|         See the documentation for `QRLogin` to see how to proceed after this. | ||||
| 
 | ||||
|         Arguments | ||||
|             ignored_ids (List[`int`]): | ||||
|                 List of already logged-in user IDs, to prevent logging in | ||||
|                 twice with the same user. | ||||
| 
 | ||||
|         Returns | ||||
|             An instance of `QRLogin`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 def display_url_as_qr(url): | ||||
|                     pass  # do whatever to show url as a qr to the user | ||||
| 
 | ||||
|                 qr_login = await client.qr_login() | ||||
|                 display_url_as_qr(qr_login.url) | ||||
| 
 | ||||
|                 # Important! You need to wait for the login to complete! | ||||
|                 await qr_login.wait() | ||||
|         """ | ||||
| async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> custom.QRLogin: | ||||
|     qr_login = custom.QRLogin(self, ignored_ids or []) | ||||
|     await qr_login.recreate() | ||||
|     return qr_login | ||||
| 
 | ||||
|     async def log_out(self: 'TelegramClient') -> bool: | ||||
|         """ | ||||
|         Logs out Telegram and deletes the current ``*.session`` file. | ||||
| 
 | ||||
|         Returns | ||||
|             `True` if the operation was successful. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Note: you will need to login again! | ||||
|                 await client.log_out() | ||||
|         """ | ||||
| async def log_out(self: 'TelegramClient') -> bool: | ||||
|     try: | ||||
|         await self(functions.auth.LogOutRequest()) | ||||
|     except errors.RPCError: | ||||
|  | @ -600,7 +365,7 @@ class AuthMethods: | |||
|     self.session.delete() | ||||
|     return True | ||||
| 
 | ||||
|     async def edit_2fa( | ||||
| async def edit_2fa( | ||||
|         self: 'TelegramClient', | ||||
|         current_password: str = None, | ||||
|         new_password: str = None, | ||||
|  | @ -608,59 +373,6 @@ class AuthMethods: | |||
|         hint: str = '', | ||||
|         email: str = None, | ||||
|         email_code_callback: typing.Callable[[int], str] = None) -> bool: | ||||
|         """ | ||||
|         Changes the 2FA settings of the logged in user. | ||||
| 
 | ||||
|         Review carefully the parameter explanations before using this method. | ||||
| 
 | ||||
|         Note that this method may be *incredibly* slow depending on the | ||||
|         prime numbers that must be used during the process to make sure | ||||
|         that everything is safe. | ||||
| 
 | ||||
|         Has no effect if both current and new password are omitted. | ||||
| 
 | ||||
|         Arguments | ||||
|             current_password (`str`, optional): | ||||
|                 The current password, to authorize changing to ``new_password``. | ||||
|                 Must be set if changing existing 2FA settings. | ||||
|                 Must **not** be set if 2FA is currently disabled. | ||||
|                 Passing this by itself will remove 2FA (if correct). | ||||
| 
 | ||||
|             new_password (`str`, optional): | ||||
|                 The password to set as 2FA. | ||||
|                 If 2FA was already enabled, ``current_password`` **must** be set. | ||||
|                 Leaving this blank or `None` will remove the password. | ||||
| 
 | ||||
|             hint (`str`, optional): | ||||
|                 Hint to be displayed by Telegram when it asks for 2FA. | ||||
|                 Leaving unspecified is highly discouraged. | ||||
|                 Has no effect if ``new_password`` is not set. | ||||
| 
 | ||||
|             email (`str`, optional): | ||||
|                 Recovery and verification email. If present, you must also | ||||
|                 set `email_code_callback`, else it raises ``ValueError``. | ||||
| 
 | ||||
|             email_code_callback (`callable`, optional): | ||||
|                 If an email is provided, a callback that returns the code sent | ||||
|                 to it must also be set. This callback may be asynchronous. | ||||
|                 It should return a string with the code. The length of the | ||||
|                 code will be passed to the callback as an input parameter. | ||||
| 
 | ||||
|                 If the callback returns an invalid code, it will raise | ||||
|                 ``CodeInvalidError``. | ||||
| 
 | ||||
|         Returns | ||||
|             `True` if successful, `False` otherwise. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Setting a password for your account which didn't have | ||||
|                 await client.edit_2fa(new_password='I_<3_Telethon') | ||||
| 
 | ||||
|                 # Removing the password | ||||
|                 await client.edit_2fa(current_password='I_<3_Telethon') | ||||
|         """ | ||||
|     if new_password is None and current_password is None: | ||||
|         return False | ||||
| 
 | ||||
|  | @ -704,18 +416,3 @@ class AuthMethods: | |||
|         await self(functions.account.ConfirmPasswordEmailRequest(code)) | ||||
| 
 | ||||
|     return True | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region with blocks | ||||
| 
 | ||||
|     async def __aenter__(self): | ||||
|         return await self.start() | ||||
| 
 | ||||
|     async def __aexit__(self, *args): | ||||
|         await self.disconnect() | ||||
| 
 | ||||
|     __enter__ = helpers._sync_enter | ||||
|     __exit__ = helpers._sync_exit | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -7,8 +7,7 @@ if typing.TYPE_CHECKING: | |||
|     from .telegramclient import TelegramClient | ||||
| 
 | ||||
| 
 | ||||
| class BotMethods: | ||||
|     async def inline_query( | ||||
| async def inline_query( | ||||
|         self: 'TelegramClient', | ||||
|         bot: 'hints.EntityLike', | ||||
|         query: str, | ||||
|  | @ -16,45 +15,6 @@ class BotMethods: | |||
|         entity: 'hints.EntityLike' = None, | ||||
|         offset: str = None, | ||||
|         geo_point: 'types.GeoPoint' = None) -> custom.InlineResults: | ||||
|         """ | ||||
|         Makes an inline query to the specified bot (``@vote New Poll``). | ||||
| 
 | ||||
|         Arguments | ||||
|             bot (`entity`): | ||||
|                 The bot entity to which the inline query should be made. | ||||
| 
 | ||||
|             query (`str`): | ||||
|                 The query that should be made to the bot. | ||||
| 
 | ||||
|             entity (`entity`, optional): | ||||
|                 The entity where the inline query is being made from. Certain | ||||
|                 bots use this to display different results depending on where | ||||
|                 it's used, such as private chats, groups or channels. | ||||
| 
 | ||||
|                 If specified, it will also be the default entity where the | ||||
|                 message will be sent after clicked. Otherwise, the "empty | ||||
|                 peer" will be used, which some bots may not handle correctly. | ||||
| 
 | ||||
|             offset (`str`, optional): | ||||
|                 The string offset to use for the bot. | ||||
| 
 | ||||
|             geo_point (:tl:`GeoPoint`, optional) | ||||
|                 The geo point location information to send to the bot | ||||
|                 for localised results. Available under some bots. | ||||
| 
 | ||||
|         Returns | ||||
|             A list of `custom.InlineResult | ||||
|             <telethon.tl.custom.inlineresult.InlineResult>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Make an inline query to @like | ||||
|                 results = await client.inline_query('like', 'Do you like Telethon?') | ||||
| 
 | ||||
|                 # Send the first result to some chat | ||||
|                 message = await results[0].click('TelethonOffTopic') | ||||
|         """ | ||||
|     bot = await self.get_input_entity(bot) | ||||
|     if entity: | ||||
|         peer = await self.get_input_entity(entity) | ||||
|  |  | |||
|  | @ -4,40 +4,9 @@ from .. import utils, hints | |||
| from ..tl import types, custom | ||||
| 
 | ||||
| 
 | ||||
| class ButtonMethods: | ||||
|     @staticmethod | ||||
|     def build_reply_markup( | ||||
| def build_reply_markup( | ||||
|         buttons: 'typing.Optional[hints.MarkupLike]', | ||||
|         inline_only: bool = False) -> 'typing.Optional[types.TypeReplyMarkup]': | ||||
|         """ | ||||
|         Builds a :tl:`ReplyInlineMarkup` or :tl:`ReplyKeyboardMarkup` for | ||||
|         the given buttons. | ||||
| 
 | ||||
|         Does nothing if either no buttons are provided or the provided | ||||
|         argument is already a reply markup. | ||||
| 
 | ||||
|         You should consider using this method if you are going to reuse | ||||
|         the markup very often. Otherwise, it is not necessary. | ||||
| 
 | ||||
|         This method is **not** asynchronous (don't use ``await`` on it). | ||||
| 
 | ||||
|         Arguments | ||||
|             buttons (`hints.MarkupLike`): | ||||
|                 The button, list of buttons, array of buttons or markup | ||||
|                 to convert into a markup. | ||||
| 
 | ||||
|             inline_only (`bool`, optional): | ||||
|                 Whether the buttons **must** be inline buttons only or not. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 from telethon import Button | ||||
| 
 | ||||
|                 markup = client.build_reply_markup(Button.inline('hi')) | ||||
|                 # later | ||||
|                 await client.send_message(chat, 'click me', buttons=markup) | ||||
|         """ | ||||
|     if buttons is None: | ||||
|         return None | ||||
| 
 | ||||
|  |  | |||
|  | @ -404,11 +404,7 @@ class _ProfilePhotoIter(RequestIter): | |||
|                 self.request.offset_id = result.messages[-1].id | ||||
| 
 | ||||
| 
 | ||||
| class ChatMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     def iter_participants( | ||||
| def iter_participants( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         limit: float = None, | ||||
|  | @ -416,67 +412,6 @@ class ChatMethods: | |||
|         search: str = '', | ||||
|         filter: 'types.TypeChannelParticipantsFilter' = None, | ||||
|         aggressive: bool = False) -> _ParticipantsIter: | ||||
|         """ | ||||
|         Iterator over the participants belonging to the specified chat. | ||||
| 
 | ||||
|         The order is unspecified. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The entity from which to retrieve the participants list. | ||||
| 
 | ||||
|             limit (`int`): | ||||
|                 Limits amount of participants fetched. | ||||
| 
 | ||||
|             search (`str`, optional): | ||||
|                 Look for participants with this string in name/username. | ||||
| 
 | ||||
|                 If ``aggressive is True``, the symbols from this string will | ||||
|                 be used. | ||||
| 
 | ||||
|             filter (:tl:`ChannelParticipantsFilter`, optional): | ||||
|                 The filter to be used, if you want e.g. only admins | ||||
|                 Note that you might not have permissions for some filter. | ||||
|                 This has no effect for normal chats or users. | ||||
| 
 | ||||
|                 .. note:: | ||||
| 
 | ||||
|                     The filter :tl:`ChannelParticipantsBanned` will return | ||||
|                     *restricted* users. If you want *banned* users you should | ||||
|                     use :tl:`ChannelParticipantsKicked` instead. | ||||
| 
 | ||||
|             aggressive (`bool`, optional): | ||||
|                 Aggressively looks for all participants in the chat. | ||||
| 
 | ||||
|                 This is useful for channels since 20 July 2018, | ||||
|                 Telegram added a server-side limit where only the | ||||
|                 first 200 members can be retrieved. With this flag | ||||
|                 set, more than 200 will be often be retrieved. | ||||
| 
 | ||||
|                 This has no effect if a ``filter`` is given. | ||||
| 
 | ||||
|         Yields | ||||
|             The :tl:`User` objects returned by :tl:`GetParticipantsRequest` | ||||
|             with an additional ``.participant`` attribute which is the | ||||
|             matched :tl:`ChannelParticipant` type for channels/megagroups | ||||
|             or :tl:`ChatParticipants` for normal chats. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Show all user IDs in a chat | ||||
|                 async for user in client.iter_participants(chat): | ||||
|                     print(user.id) | ||||
| 
 | ||||
|                 # Search by name | ||||
|                 async for user in client.iter_participants(chat, search='name'): | ||||
|                     print(user.username) | ||||
| 
 | ||||
|                 # Filter by admins | ||||
|                 from telethon.tl.types import ChannelParticipantsAdmins | ||||
|                 async for user in client.iter_participants(chat, filter=ChannelParticipantsAdmins): | ||||
|                     print(user.first_name) | ||||
|         """ | ||||
|     return _ParticipantsIter( | ||||
|         self, | ||||
|         limit, | ||||
|  | @ -486,30 +421,14 @@ class ChatMethods: | |||
|         aggressive=aggressive | ||||
|     ) | ||||
| 
 | ||||
|     async def get_participants( | ||||
| async def get_participants( | ||||
|         self: 'TelegramClient', | ||||
|         *args, | ||||
|         **kwargs) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_participants()`, but returns a | ||||
|         `TotalList <telethon.helpers.TotalList>` instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 users = await client.get_participants(chat) | ||||
|                 print(users[0].first_name) | ||||
| 
 | ||||
|                 for user in users: | ||||
|                     if user.username is not None: | ||||
|                         print(user.username) | ||||
|         """ | ||||
|     return await self.iter_participants(*args, **kwargs).collect() | ||||
| 
 | ||||
|     get_participants.__signature__ = inspect.signature(iter_participants) | ||||
| 
 | ||||
| 
 | ||||
|     def iter_admin_log( | ||||
| def iter_admin_log( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         limit: float = None, | ||||
|  | @ -533,105 +452,6 @@ class ChatMethods: | |||
|         edit: bool = None, | ||||
|         delete: bool = None, | ||||
|         group_call: bool = None) -> _AdminLogIter: | ||||
|         """ | ||||
|         Iterator over the admin log for the specified channel. | ||||
| 
 | ||||
|         The default order is from the most recent event to to the oldest. | ||||
| 
 | ||||
|         Note that you must be an administrator of it to use this method. | ||||
| 
 | ||||
|         If none of the filters are present (i.e. they all are `None`), | ||||
|         *all* event types will be returned. If at least one of them is | ||||
|         `True`, only those that are true will be returned. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel entity from which to get its admin log. | ||||
| 
 | ||||
|             limit (`int` | `None`, optional): | ||||
|                 Number of events to be retrieved. | ||||
| 
 | ||||
|                 The limit may also be `None`, which would eventually return | ||||
|                 the whole history. | ||||
| 
 | ||||
|             max_id (`int`): | ||||
|                 All the events with a higher (newer) ID or equal to this will | ||||
|                 be excluded. | ||||
| 
 | ||||
|             min_id (`int`): | ||||
|                 All the events with a lower (older) ID or equal to this will | ||||
|                 be excluded. | ||||
| 
 | ||||
|             search (`str`): | ||||
|                 The string to be used as a search query. | ||||
| 
 | ||||
|             admins (`entity` | `list`): | ||||
|                 If present, the events will be filtered by these admins | ||||
|                 (or single admin) and only those caused by them will be | ||||
|                 returned. | ||||
| 
 | ||||
|             join (`bool`): | ||||
|                 If `True`, events for when a user joined will be returned. | ||||
| 
 | ||||
|             leave (`bool`): | ||||
|                 If `True`, events for when a user leaves will be returned. | ||||
| 
 | ||||
|             invite (`bool`): | ||||
|                 If `True`, events for when a user joins through an invite | ||||
|                 link will be returned. | ||||
| 
 | ||||
|             restrict (`bool`): | ||||
|                 If `True`, events with partial restrictions will be | ||||
|                 returned. This is what the API calls "ban". | ||||
| 
 | ||||
|             unrestrict (`bool`): | ||||
|                 If `True`, events removing restrictions will be returned. | ||||
|                 This is what the API calls "unban". | ||||
| 
 | ||||
|             ban (`bool`): | ||||
|                 If `True`, events applying or removing all restrictions will | ||||
|                 be returned. This is what the API calls "kick" (restricting | ||||
|                 all permissions removed is a ban, which kicks the user). | ||||
| 
 | ||||
|             unban (`bool`): | ||||
|                 If `True`, events removing all restrictions will be | ||||
|                 returned. This is what the API calls "unkick". | ||||
| 
 | ||||
|             promote (`bool`): | ||||
|                 If `True`, events with admin promotions will be returned. | ||||
| 
 | ||||
|             demote (`bool`): | ||||
|                 If `True`, events with admin demotions will be returned. | ||||
| 
 | ||||
|             info (`bool`): | ||||
|                 If `True`, events changing the group info will be returned. | ||||
| 
 | ||||
|             settings (`bool`): | ||||
|                 If `True`, events changing the group settings will be | ||||
|                 returned. | ||||
| 
 | ||||
|             pinned (`bool`): | ||||
|                 If `True`, events of new pinned messages will be returned. | ||||
| 
 | ||||
|             edit (`bool`): | ||||
|                 If `True`, events of message edits will be returned. | ||||
| 
 | ||||
|             delete (`bool`): | ||||
|                 If `True`, events of message deletions will be returned. | ||||
| 
 | ||||
|             group_call (`bool`): | ||||
|                 If `True`, events related to group calls will be returned. | ||||
| 
 | ||||
|         Yields | ||||
|             Instances of `AdminLogEvent <telethon.tl.custom.adminlogevent.AdminLogEvent>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 async for event in client.iter_admin_log(channel): | ||||
|                     if event.changed_title: | ||||
|                         print('The title changed from', event.old, 'to', event.new) | ||||
|         """ | ||||
|     return _AdminLogIter( | ||||
|         self, | ||||
|         limit, | ||||
|  | @ -657,64 +477,20 @@ class ChatMethods: | |||
|         group_call=group_call | ||||
|     ) | ||||
| 
 | ||||
|     async def get_admin_log( | ||||
| async def get_admin_log( | ||||
|         self: 'TelegramClient', | ||||
|         *args, | ||||
|         **kwargs) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_admin_log()`, but returns a ``list`` instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Get a list of deleted message events which said "heck" | ||||
|                 events = await client.get_admin_log(channel, search='heck', delete=True) | ||||
| 
 | ||||
|                 # Print the old message before it was deleted | ||||
|                 print(events[0].old) | ||||
|         """ | ||||
|     return await self.iter_admin_log(*args, **kwargs).collect() | ||||
| 
 | ||||
|     get_admin_log.__signature__ = inspect.signature(iter_admin_log) | ||||
| 
 | ||||
|     def iter_profile_photos( | ||||
| def iter_profile_photos( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         limit: int = None, | ||||
|         *, | ||||
|         offset: int = 0, | ||||
|         max_id: int = 0) -> _ProfilePhotoIter: | ||||
|         """ | ||||
|         Iterator over a user's profile photos or a chat's photos. | ||||
| 
 | ||||
|         The order is from the most recent photo to the oldest. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The entity from which to get the profile or chat photos. | ||||
| 
 | ||||
|             limit (`int` | `None`, optional): | ||||
|                 Number of photos to be retrieved. | ||||
| 
 | ||||
|                 The limit may also be `None`, which would eventually all | ||||
|                 the photos that are still available. | ||||
| 
 | ||||
|             offset (`int`): | ||||
|                 How many photos should be skipped before returning the first one. | ||||
| 
 | ||||
|             max_id (`int`): | ||||
|                 The maximum ID allowed when fetching photos. | ||||
| 
 | ||||
|         Yields | ||||
|             Instances of :tl:`Photo`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Download all the profile photos of some user | ||||
|                 async for photo in client.iter_profile_photos(user): | ||||
|                     await client.download_media(photo) | ||||
|         """ | ||||
|     return _ProfilePhotoIter( | ||||
|         self, | ||||
|         limit, | ||||
|  | @ -723,103 +499,20 @@ class ChatMethods: | |||
|         max_id=max_id | ||||
|     ) | ||||
| 
 | ||||
|     async def get_profile_photos( | ||||
| async def get_profile_photos( | ||||
|         self: 'TelegramClient', | ||||
|         *args, | ||||
|         **kwargs) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_profile_photos()`, but returns a | ||||
|         `TotalList <telethon.helpers.TotalList>` instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Get the photos of a channel | ||||
|                 photos = await client.get_profile_photos(channel) | ||||
| 
 | ||||
|                 # Download the oldest photo | ||||
|                 await client.download_media(photos[-1]) | ||||
|         """ | ||||
|     return await self.iter_profile_photos(*args, **kwargs).collect() | ||||
| 
 | ||||
|     get_profile_photos.__signature__ = inspect.signature(iter_profile_photos) | ||||
| 
 | ||||
|     def action( | ||||
| def action( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         action: 'typing.Union[str, types.TypeSendMessageAction]', | ||||
|         *, | ||||
|         delay: float = 4, | ||||
|         auto_cancel: bool = True) -> 'typing.Union[_ChatAction, typing.Coroutine]': | ||||
|         """ | ||||
|         Returns a context-manager object to represent a "chat action". | ||||
| 
 | ||||
|         Chat actions indicate things like "user is typing", "user is | ||||
|         uploading a photo", etc. | ||||
| 
 | ||||
|         If the action is ``'cancel'``, you should just ``await`` the result, | ||||
|         since it makes no sense to use a context-manager for it. | ||||
| 
 | ||||
|         See the example below for intended usage. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The entity where the action should be showed in. | ||||
| 
 | ||||
|             action (`str` | :tl:`SendMessageAction`): | ||||
|                 The action to show. You can either pass a instance of | ||||
|                 :tl:`SendMessageAction` or better, a string used while: | ||||
| 
 | ||||
|                 * ``'typing'``: typing a text message. | ||||
|                 * ``'contact'``: choosing a contact. | ||||
|                 * ``'game'``: playing a game. | ||||
|                 * ``'location'``: choosing a geo location. | ||||
|                 * ``'sticker'``: choosing a sticker. | ||||
|                 * ``'record-audio'``: recording a voice note. | ||||
|                   You may use ``'record-voice'`` as alias. | ||||
|                 * ``'record-round'``: recording a round video. | ||||
|                 * ``'record-video'``: recording a normal video. | ||||
|                 * ``'audio'``: sending an audio file (voice note or song). | ||||
|                   You may use ``'voice'`` and ``'song'`` as aliases. | ||||
|                 * ``'round'``: uploading a round video. | ||||
|                 * ``'video'``: uploading a video file. | ||||
|                 * ``'photo'``: uploading a photo. | ||||
|                 * ``'document'``: uploading a document file. | ||||
|                   You may use ``'file'`` as alias. | ||||
|                 * ``'cancel'``: cancel any pending action in this chat. | ||||
| 
 | ||||
|                 Invalid strings will raise a ``ValueError``. | ||||
| 
 | ||||
|             delay (`int` | `float`): | ||||
|                 The delay, in seconds, to wait between sending actions. | ||||
|                 For example, if the delay is 5 and it takes 7 seconds to | ||||
|                 do something, three requests will be made at 0s, 5s, and | ||||
|                 7s to cancel the action. | ||||
| 
 | ||||
|             auto_cancel (`bool`): | ||||
|                 Whether the action should be cancelled once the context | ||||
|                 manager exists or not. The default is `True`, since | ||||
|                 you don't want progress to be shown when it has already | ||||
|                 completed. | ||||
| 
 | ||||
|         Returns | ||||
|             Either a context-manager object or a coroutine. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Type for 2 seconds, then send a message | ||||
|                 async with client.action(chat, 'typing'): | ||||
|                     await asyncio.sleep(2) | ||||
|                     await client.send_message(chat, 'Hello world! I type slow ^^') | ||||
| 
 | ||||
|                 # Cancel any previous action | ||||
|                 await client.action(chat, 'cancel') | ||||
| 
 | ||||
|                 # Upload a document, showing its progress (most clients ignore this) | ||||
|                 async with client.action(chat, 'document') as action: | ||||
|                     await client.send_file(chat, zip_file, progress_callback=action.progress) | ||||
|         """ | ||||
|     if isinstance(action, str): | ||||
|         try: | ||||
|             action = _ChatAction._str_mapping[action.lower()] | ||||
|  | @ -841,7 +534,7 @@ class ChatMethods: | |||
|     return _ChatAction( | ||||
|         self, entity, action, delay=delay, auto_cancel=auto_cancel) | ||||
| 
 | ||||
|     async def edit_admin( | ||||
| async def edit_admin( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         user: 'hints.EntityLike', | ||||
|  | @ -858,93 +551,6 @@ class ChatMethods: | |||
|         anonymous: bool = None, | ||||
|         is_admin: bool = None, | ||||
|         title: str = None) -> types.Updates: | ||||
|         """ | ||||
|         Edits admin permissions for someone in a chat. | ||||
| 
 | ||||
|         Raises an error if a wrong combination of rights are given | ||||
|         (e.g. you don't have enough permissions to grant one). | ||||
| 
 | ||||
|         Unless otherwise stated, permissions will work in channels and megagroups. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel, megagroup or chat where the promotion should happen. | ||||
| 
 | ||||
|             user (`entity`): | ||||
|                 The user to be promoted. | ||||
| 
 | ||||
|             change_info (`bool`, optional): | ||||
|                 Whether the user will be able to change info. | ||||
| 
 | ||||
|             post_messages (`bool`, optional): | ||||
|                 Whether the user will be able to post in the channel. | ||||
|                 This will only work in broadcast channels. | ||||
| 
 | ||||
|             edit_messages (`bool`, optional): | ||||
|                 Whether the user will be able to edit messages in the channel. | ||||
|                 This will only work in broadcast channels. | ||||
| 
 | ||||
|             delete_messages (`bool`, optional): | ||||
|                 Whether the user will be able to delete messages. | ||||
| 
 | ||||
|             ban_users (`bool`, optional): | ||||
|                 Whether the user will be able to ban users. | ||||
| 
 | ||||
|             invite_users (`bool`, optional): | ||||
|                 Whether the user will be able to invite users. Needs some testing. | ||||
| 
 | ||||
|             pin_messages (`bool`, optional): | ||||
|                 Whether the user will be able to pin messages. | ||||
| 
 | ||||
|             add_admins (`bool`, optional): | ||||
|                 Whether the user will be able to add admins. | ||||
| 
 | ||||
|             manage_call (`bool`, optional): | ||||
|                 Whether the user will be able to manage group calls. | ||||
| 
 | ||||
|             anonymous (`bool`, optional): | ||||
|                 Whether the user will remain anonymous when sending messages. | ||||
|                 The sender of the anonymous messages becomes the group itself. | ||||
| 
 | ||||
|                 .. note:: | ||||
| 
 | ||||
|                     Users may be able to identify the anonymous admin by its | ||||
|                     custom title, so additional care is needed when using both | ||||
|                     ``anonymous`` and custom titles. For example, if multiple | ||||
|                     anonymous admins share the same title, users won't be able | ||||
|                     to distinguish them. | ||||
| 
 | ||||
|             is_admin (`bool`, optional): | ||||
|                 Whether the user will be an admin in the chat. | ||||
|                 This will only work in small group chats. | ||||
|                 Whether the user will be an admin in the chat. This is the | ||||
|                 only permission available in small group chats, and when | ||||
|                 used in megagroups, all non-explicitly set permissions will | ||||
|                 have this value. | ||||
| 
 | ||||
|                 Essentially, only passing ``is_admin=True`` will grant all | ||||
|                 permissions, but you can still disable those you need. | ||||
| 
 | ||||
|             title (`str`, optional): | ||||
|                 The custom title (also known as "rank") to show for this admin. | ||||
|                 This text will be shown instead of the "admin" badge. | ||||
|                 This will only work in channels and megagroups. | ||||
| 
 | ||||
|                 When left unspecified or empty, the default localized "admin" | ||||
|                 badge will be shown. | ||||
| 
 | ||||
|         Returns | ||||
|             The resulting :tl:`Updates` object. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Allowing `user` to pin messages in `chat` | ||||
|                 await client.edit_admin(chat, user, pin_messages=True) | ||||
| 
 | ||||
|                 # Granting all permissions except for `add_admins` | ||||
|                 await client.edit_admin(chat, user, is_admin=True, add_admins=False) | ||||
|         """ | ||||
|     entity = await self.get_input_entity(entity) | ||||
|     user = await self.get_input_entity(user) | ||||
|     ty = helpers._entity_type(user) | ||||
|  | @ -993,7 +599,7 @@ class ChatMethods: | |||
|         raise ValueError( | ||||
|             'You can only edit permissions in groups and channels') | ||||
| 
 | ||||
|     async def edit_permissions( | ||||
| async def edit_permissions( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         user: 'typing.Optional[hints.EntityLike]' = None, | ||||
|  | @ -1011,103 +617,6 @@ class ChatMethods: | |||
|         change_info: bool = True, | ||||
|         invite_users: bool = True, | ||||
|         pin_messages: bool = True) -> types.Updates: | ||||
|         """ | ||||
|         Edits user restrictions in a chat. | ||||
| 
 | ||||
|         Set an argument to `False` to apply a restriction (i.e. remove | ||||
|         the permission), or omit them to use the default `True` (i.e. | ||||
|         don't apply a restriction). | ||||
| 
 | ||||
|         Raises an error if a wrong combination of rights are given | ||||
|         (e.g. you don't have enough permissions to revoke one). | ||||
| 
 | ||||
|         By default, each boolean argument is `True`, meaning that it | ||||
|         is true that the user has access to the default permission | ||||
|         and may be able to make use of it. | ||||
| 
 | ||||
|         If you set an argument to `False`, then a restriction is applied | ||||
|         regardless of the default permissions. | ||||
| 
 | ||||
|         It is important to note that `True` does *not* mean grant, only | ||||
|         "don't restrict", and this is where the default permissions come | ||||
|         in. A user may have not been revoked the ``pin_messages`` permission | ||||
|         (it is `True`) but they won't be able to use it if the default | ||||
|         permissions don't allow it either. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel or megagroup where the restriction should happen. | ||||
| 
 | ||||
|             user (`entity`, optional): | ||||
|                 If specified, the permission will be changed for the specific user. | ||||
|                 If left as `None`, the default chat permissions will be updated. | ||||
| 
 | ||||
|             until_date (`DateLike`, optional): | ||||
|                 When the user will be unbanned. | ||||
| 
 | ||||
|                 If the due date or duration is longer than 366 days or shorter than | ||||
|                 30 seconds, the ban will be forever. Defaults to ``0`` (ban forever). | ||||
| 
 | ||||
|             view_messages (`bool`, optional): | ||||
|                 Whether the user is able to view messages or not. | ||||
|                 Forbidding someone from viewing messages equals to banning them. | ||||
|                 This will only work if ``user`` is set. | ||||
| 
 | ||||
|             send_messages (`bool`, optional): | ||||
|                 Whether the user is able to send messages or not. | ||||
| 
 | ||||
|             send_media (`bool`, optional): | ||||
|                 Whether the user is able to send media or not. | ||||
| 
 | ||||
|             send_stickers (`bool`, optional): | ||||
|                 Whether the user is able to send stickers or not. | ||||
| 
 | ||||
|             send_gifs (`bool`, optional): | ||||
|                 Whether the user is able to send animated gifs or not. | ||||
| 
 | ||||
|             send_games (`bool`, optional): | ||||
|                 Whether the user is able to send games or not. | ||||
| 
 | ||||
|             send_inline (`bool`, optional): | ||||
|                 Whether the user is able to use inline bots or not. | ||||
| 
 | ||||
|             embed_link_previews (`bool`, optional): | ||||
|                 Whether the user is able to enable the link preview in the | ||||
|                 messages they send. Note that the user will still be able to | ||||
|                 send messages with links if this permission is removed, but | ||||
|                 these links won't display a link preview. | ||||
| 
 | ||||
|             send_polls (`bool`, optional): | ||||
|                 Whether the user is able to send polls or not. | ||||
| 
 | ||||
|             change_info (`bool`, optional): | ||||
|                 Whether the user is able to change info or not. | ||||
| 
 | ||||
|             invite_users (`bool`, optional): | ||||
|                 Whether the user is able to invite other users or not. | ||||
| 
 | ||||
|             pin_messages (`bool`, optional): | ||||
|                 Whether the user is able to pin messages or not. | ||||
| 
 | ||||
|         Returns | ||||
|             The resulting :tl:`Updates` object. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 from datetime import timedelta | ||||
| 
 | ||||
|                 # Banning `user` from `chat` for 1 minute | ||||
|                 await client.edit_permissions(chat, user, timedelta(minutes=1), | ||||
|                                               view_messages=False) | ||||
| 
 | ||||
|                 # Banning `user` from `chat` forever | ||||
|                 await client.edit_permissions(chat, user, view_messages=False) | ||||
| 
 | ||||
|                 # Kicking someone (ban + un-ban) | ||||
|                 await client.edit_permissions(chat, user, view_messages=False) | ||||
|                 await client.edit_permissions(chat, user) | ||||
|         """ | ||||
|     entity = await self.get_input_entity(entity) | ||||
|     ty = helpers._entity_type(entity) | ||||
|     if ty != helpers._EntityType.CHANNEL: | ||||
|  | @ -1149,43 +658,11 @@ class ChatMethods: | |||
|         banned_rights=rights | ||||
|     )) | ||||
| 
 | ||||
|     async def kick_participant( | ||||
| async def kick_participant( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         user: 'typing.Optional[hints.EntityLike]' | ||||
|     ): | ||||
|         """ | ||||
|         Kicks a user from a chat. | ||||
| 
 | ||||
|         Kicking yourself (``'me'``) will result in leaving the chat. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             Attempting to kick someone who was banned will remove their | ||||
|             restrictions (and thus unbanning them), since kicking is just | ||||
|             ban + unban. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel or chat where the user should be kicked from. | ||||
| 
 | ||||
|             user (`entity`, optional): | ||||
|                 The user to kick. | ||||
| 
 | ||||
|         Returns | ||||
|             Returns the service `Message <telethon.tl.custom.message.Message>` | ||||
|             produced about a user being kicked, if any. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Kick some user from some chat, and deleting the service message | ||||
|                 msg = await client.kick_participant(chat, user) | ||||
|                 await msg.delete() | ||||
| 
 | ||||
|                 # Leaving chat | ||||
|                 await client.kick_participant(chat, 'me') | ||||
|         """ | ||||
| ): | ||||
|     entity = await self.get_input_entity(entity) | ||||
|     user = await self.get_input_entity(user) | ||||
|     if helpers._entity_type(user) != helpers._EntityType.USER: | ||||
|  | @ -1217,42 +694,11 @@ class ChatMethods: | |||
| 
 | ||||
|     return self._get_response_message(None, resp, entity) | ||||
| 
 | ||||
|     async def get_permissions( | ||||
| async def get_permissions( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         user: 'hints.EntityLike' = None | ||||
|     ) -> 'typing.Optional[custom.ParticipantPermissions]': | ||||
|         """ | ||||
|         Fetches the permissions of a user in a specific chat or channel or | ||||
|         get Default Restricted Rights of Chat or Channel. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             This request has to fetch the entire chat for small group chats, | ||||
|             which can get somewhat expensive, so use of a cache is advised. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel or chat the user is participant of. | ||||
| 
 | ||||
|             user (`entity`, optional): | ||||
|                 Target user. | ||||
| 
 | ||||
|         Returns | ||||
|             A `ParticipantPermissions <telethon.tl.custom.participantpermissions.ParticipantPermissions>` | ||||
|             instance. Refer to its documentation to see what properties are | ||||
|             available. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 permissions = await client.get_permissions(chat, user) | ||||
|                 if permissions.is_admin: | ||||
|                     # do something | ||||
| 
 | ||||
|                 # Get Banned Permissions of Chat | ||||
|                 await client.get_permissions(chat) | ||||
|         """ | ||||
| ) -> 'typing.Optional[custom.ParticipantPermissions]': | ||||
|     entity = await self.get_entity(entity) | ||||
| 
 | ||||
|     if not user: | ||||
|  | @ -1287,50 +733,11 @@ class ChatMethods: | |||
| 
 | ||||
|     raise ValueError('You must pass either a channel or a chat') | ||||
| 
 | ||||
|     async def get_stats( | ||||
| async def get_stats( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'typing.Union[int, types.Message]' = None, | ||||
|     ): | ||||
|         """ | ||||
|         Retrieves statistics from the given megagroup or broadcast channel. | ||||
| 
 | ||||
|         Note that some restrictions apply before being able to fetch statistics, | ||||
|         in particular the channel must have enough members (for megagroups, this | ||||
|         requires `at least 500 members`_). | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The channel from which to get statistics. | ||||
| 
 | ||||
|             message (`int` | ``Message``, optional): | ||||
|                 The message ID from which to get statistics, if your goal is | ||||
|                 to obtain the statistics of a single message. | ||||
| 
 | ||||
|         Raises | ||||
|             If the given entity is not a channel (broadcast or megagroup), | ||||
|             a `TypeError` is raised. | ||||
| 
 | ||||
|             If there are not enough members (poorly named) errors such as | ||||
|             ``telethon.errors.ChatAdminRequiredError`` will appear. | ||||
| 
 | ||||
|         Returns | ||||
|             If both ``entity`` and ``message`` were provided, returns | ||||
|             :tl:`MessageStats`. Otherwise, either :tl:`BroadcastStats` or | ||||
|             :tl:`MegagroupStats`, depending on whether the input belonged to a | ||||
|             broadcast channel or megagroup. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Some megagroup or channel username or ID to fetch | ||||
|                 channel = -100123 | ||||
|                 stats = await client.get_stats(channel) | ||||
|                 print('Stats from', stats.period.min_date, 'to', stats.period.max_date, ':') | ||||
|                 print(stats.stringify()) | ||||
| 
 | ||||
|         .. _`at least 500 members`: https://telegram.org/blog/profile-videos-people-nearby-and-more | ||||
|         """ | ||||
| ): | ||||
|     entity = await self.get_input_entity(entity) | ||||
|     if helpers._entity_type(entity) != helpers._EntityType.CHANNEL: | ||||
|         raise TypeError('You must pass a channel entity') | ||||
|  | @ -1364,5 +771,3 @@ class ChatMethods: | |||
|         return await sender.send(req) | ||||
|     finally: | ||||
|         await self._return_exported_sender(sender) | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -136,11 +136,7 @@ class _DraftsIter(RequestIter): | |||
|         return [] | ||||
| 
 | ||||
| 
 | ||||
| class DialogMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     def iter_dialogs( | ||||
| def iter_dialogs( | ||||
|         self: 'TelegramClient', | ||||
|         limit: float = None, | ||||
|         *, | ||||
|  | @ -151,70 +147,7 @@ class DialogMethods: | |||
|         ignore_migrated: bool = False, | ||||
|         folder: int = None, | ||||
|         archived: bool = None | ||||
|     ) -> _DialogsIter: | ||||
|         """ | ||||
|         Iterator over the dialogs (open conversations/subscribed channels). | ||||
| 
 | ||||
|         The order is the same as the one seen in official applications | ||||
|         (first pinned, them from those with the most recent message to | ||||
|         those with the oldest message). | ||||
| 
 | ||||
|         Arguments | ||||
|             limit (`int` | `None`): | ||||
|                 How many dialogs to be retrieved as maximum. Can be set to | ||||
|                 `None` to retrieve all dialogs. Note that this may take | ||||
|                 whole minutes if you have hundreds of dialogs, as Telegram | ||||
|                 will tell the library to slow down through a | ||||
|                 ``FloodWaitError``. | ||||
| 
 | ||||
|             offset_date (`datetime`, optional): | ||||
|                 The offset date to be used. | ||||
| 
 | ||||
|             offset_id (`int`, optional): | ||||
|                 The message ID to be used as an offset. | ||||
| 
 | ||||
|             offset_peer (:tl:`InputPeer`, optional): | ||||
|                 The peer to be used as an offset. | ||||
| 
 | ||||
|             ignore_pinned (`bool`, optional): | ||||
|                 Whether pinned dialogs should be ignored or not. | ||||
|                 When set to `True`, these won't be yielded at all. | ||||
| 
 | ||||
|             ignore_migrated (`bool`, optional): | ||||
|                 Whether :tl:`Chat` that have ``migrated_to`` a :tl:`Channel` | ||||
|                 should be included or not. By default all the chats in your | ||||
|                 dialogs are returned, but setting this to `True` will ignore | ||||
|                 (i.e. skip) them in the same way official applications do. | ||||
| 
 | ||||
|             folder (`int`, optional): | ||||
|                 The folder from which the dialogs should be retrieved. | ||||
| 
 | ||||
|                 If left unspecified, all dialogs (including those from | ||||
|                 folders) will be returned. | ||||
| 
 | ||||
|                 If set to ``0``, all dialogs that don't belong to any | ||||
|                 folder will be returned. | ||||
| 
 | ||||
|                 If set to a folder number like ``1``, only those from | ||||
|                 said folder will be returned. | ||||
| 
 | ||||
|                 By default Telegram assigns the folder ID ``1`` to | ||||
|                 archived chats, so you should use that if you need | ||||
|                 to fetch the archived dialogs. | ||||
| 
 | ||||
|             archived (`bool`, optional): | ||||
|                 Alias for `folder`. If unspecified, all will be returned, | ||||
|                 `False` implies ``folder=0`` and `True` implies ``folder=1``. | ||||
|         Yields | ||||
|             Instances of `Dialog <telethon.tl.custom.dialog.Dialog>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Print all dialog IDs and the title, nicely formatted | ||||
|                 async for dialog in client.iter_dialogs(): | ||||
|                     print('{:>14}: {}'.format(dialog.id, dialog.title)) | ||||
|         """ | ||||
| ) -> _DialogsIter: | ||||
|     if archived is not None: | ||||
|         folder = 1 if archived else 0 | ||||
| 
 | ||||
|  | @ -229,149 +162,37 @@ class DialogMethods: | |||
|         folder=folder | ||||
|     ) | ||||
| 
 | ||||
|     async def get_dialogs(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_dialogs()`, but returns a | ||||
|         `TotalList <telethon.helpers.TotalList>` instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Get all open conversation, print the title of the first | ||||
|                 dialogs = await client.get_dialogs() | ||||
|                 first = dialogs[0] | ||||
|                 print(first.title) | ||||
| 
 | ||||
|                 # Use the dialog somewhere else | ||||
|                 await client.send_message(first, 'hi') | ||||
| 
 | ||||
|                 # Getting only non-archived dialogs (both equivalent) | ||||
|                 non_archived = await client.get_dialogs(folder=0) | ||||
|                 non_archived = await client.get_dialogs(archived=False) | ||||
| 
 | ||||
|                 # Getting only archived dialogs (both equivalent) | ||||
|                 archived = await client.get_dialogs(folder=1) | ||||
|                 archived = await client.get_dialogs(archived=True) | ||||
|         """ | ||||
| async def get_dialogs(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | ||||
|     return await self.iter_dialogs(*args, **kwargs).collect() | ||||
| 
 | ||||
|     get_dialogs.__signature__ = inspect.signature(iter_dialogs) | ||||
| 
 | ||||
|     def iter_drafts( | ||||
| def iter_drafts( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntitiesLike' = None | ||||
|     ) -> _DraftsIter: | ||||
|         """ | ||||
|         Iterator over draft messages. | ||||
| 
 | ||||
|         The order is unspecified. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`hints.EntitiesLike`, optional): | ||||
|                 The entity or entities for which to fetch the draft messages. | ||||
|                 If left unspecified, all draft messages will be returned. | ||||
| 
 | ||||
|         Yields | ||||
|             Instances of `Draft <telethon.tl.custom.draft.Draft>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Clear all drafts | ||||
|                 async for draft in client.get_drafts(): | ||||
|                     await draft.delete() | ||||
| 
 | ||||
|                 # Getting the drafts with 'bot1' and 'bot2' | ||||
|                 async for draft in client.iter_drafts(['bot1', 'bot2']): | ||||
|                     print(draft.text) | ||||
|         """ | ||||
| ) -> _DraftsIter: | ||||
|     if entity and not utils.is_list_like(entity): | ||||
|         entity = (entity,) | ||||
| 
 | ||||
|     # TODO Passing a limit here makes no sense | ||||
|     return _DraftsIter(self, None, entities=entity) | ||||
| 
 | ||||
|     async def get_drafts( | ||||
| async def get_drafts( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntitiesLike' = None | ||||
|     ) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_drafts()`, but returns a list instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Get drafts, print the text of the first | ||||
|                 drafts = await client.get_drafts() | ||||
|                 print(drafts[0].text) | ||||
| 
 | ||||
|                 # Get the draft in your chat | ||||
|                 draft = await client.get_drafts('me') | ||||
|                 print(drafts.text) | ||||
|         """ | ||||
| ) -> 'hints.TotalList': | ||||
|     items = await self.iter_drafts(entity).collect() | ||||
|     if not entity or utils.is_list_like(entity): | ||||
|         return items | ||||
|     else: | ||||
|         return items[0] | ||||
| 
 | ||||
|     async def edit_folder( | ||||
| async def edit_folder( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntitiesLike' = None, | ||||
|         folder: typing.Union[int, typing.Sequence[int]] = None, | ||||
|         *, | ||||
|         unpack=None | ||||
|     ) -> types.Updates: | ||||
|         """ | ||||
|         Edits the folder used by one or more dialogs to archive them. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (entities): | ||||
|                 The entity or list of entities to move to the desired | ||||
|                 archive folder. | ||||
| 
 | ||||
|             folder (`int`): | ||||
|                 The folder to which the dialog should be archived to. | ||||
| 
 | ||||
|                 If you want to "archive" a dialog, use ``folder=1``. | ||||
| 
 | ||||
|                 If you want to "un-archive" it, use ``folder=0``. | ||||
| 
 | ||||
|                 You may also pass a list with the same length as | ||||
|                 `entities` if you want to control where each entity | ||||
|                 will go. | ||||
| 
 | ||||
|             unpack (`int`, optional): | ||||
|                 If you want to unpack an archived folder, set this | ||||
|                 parameter to the folder number that you want to | ||||
|                 delete. | ||||
| 
 | ||||
|                 When you unpack a folder, all the dialogs inside are | ||||
|                 moved to the folder number 0. | ||||
| 
 | ||||
|                 You can only use this parameter if the other two | ||||
|                 are not set. | ||||
| 
 | ||||
|         Returns | ||||
|             The :tl:`Updates` object that the request produces. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Archiving the first 5 dialogs | ||||
|                 dialogs = await client.get_dialogs(5) | ||||
|                 await client.edit_folder(dialogs, 1) | ||||
| 
 | ||||
|                 # Un-archiving the third dialog (archiving to folder 0) | ||||
|                 await client.edit_folder(dialog[2], 0) | ||||
| 
 | ||||
|                 # Moving the first dialog to folder 0 and the second to 1 | ||||
|                 dialogs = await client.get_dialogs(2) | ||||
|                 await client.edit_folder(dialogs, [0, 1]) | ||||
| 
 | ||||
|                 # Un-archiving all dialogs | ||||
|                 await client.edit_folder(unpack=1) | ||||
|         """ | ||||
| ) -> types.Updates: | ||||
|     if (entity is None) == (unpack is None): | ||||
|         raise ValueError('You can only set either entities or unpack, not both') | ||||
| 
 | ||||
|  | @ -398,49 +219,12 @@ class DialogMethods: | |||
|         for x, y in zip(entities, folder) | ||||
|     ])) | ||||
| 
 | ||||
|     async def delete_dialog( | ||||
| async def delete_dialog( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         *, | ||||
|         revoke: bool = False | ||||
|     ): | ||||
|         """ | ||||
|         Deletes a dialog (leaves a chat or channel). | ||||
| 
 | ||||
|         This method can be used as a user and as a bot. However, | ||||
|         bots will only be able to use it to leave groups and channels | ||||
|         (trying to delete a private conversation will do nothing). | ||||
| 
 | ||||
|         See also `Dialog.delete() <telethon.tl.custom.dialog.Dialog.delete>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (entities): | ||||
|                 The entity of the dialog to delete. If it's a chat or | ||||
|                 channel, you will leave it. Note that the chat itself | ||||
|                 is not deleted, only the dialog, because you left it. | ||||
| 
 | ||||
|             revoke (`bool`, optional): | ||||
|                 On private chats, you may revoke the messages from | ||||
|                 the other peer too. By default, it's `False`. Set | ||||
|                 it to `True` to delete the history for both. | ||||
| 
 | ||||
|                 This makes no difference for bot accounts, who can | ||||
|                 only leave groups and channels. | ||||
| 
 | ||||
|         Returns | ||||
|             The :tl:`Updates` object that the request produces, | ||||
|             or nothing for private conversations. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Deleting the first dialog | ||||
|                 dialogs = await client.get_dialogs(5) | ||||
|                 await client.delete_dialog(dialogs[0]) | ||||
| 
 | ||||
|                 # Leaving a channel by username | ||||
|                 await client.delete_dialog('username') | ||||
|         """ | ||||
| ): | ||||
|     # If we have enough information (`Dialog.delete` gives it to us), | ||||
|     # then we know we don't have to kick ourselves in deactivated chats. | ||||
|     if isinstance(entity, types.Chat): | ||||
|  | @ -469,7 +253,7 @@ class DialogMethods: | |||
| 
 | ||||
|     return result | ||||
| 
 | ||||
|     def conversation( | ||||
| def conversation( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         *, | ||||
|  | @ -478,120 +262,6 @@ class DialogMethods: | |||
|         max_messages: int = 100, | ||||
|         exclusive: bool = True, | ||||
|         replies_are_responses: bool = True) -> custom.Conversation: | ||||
|         """ | ||||
|         Creates a `Conversation <telethon.tl.custom.conversation.Conversation>` | ||||
|         with the given entity. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             This Conversation API has certain shortcomings, such as lacking | ||||
|             persistence, poor interaction with other event handlers, and | ||||
|             overcomplicated usage for anything beyond the simplest case. | ||||
| 
 | ||||
|             If you plan to interact with a bot without handlers, this works | ||||
|             fine, but when running a bot yourself, you may instead prefer | ||||
|             to follow the advice from https://stackoverflow.com/a/62246569/. | ||||
| 
 | ||||
|         This is not the same as just sending a message to create a "dialog" | ||||
|         with them, but rather a way to easily send messages and await for | ||||
|         responses or other reactions. Refer to its documentation for more. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The entity with which a new conversation should be opened. | ||||
| 
 | ||||
|             timeout (`int` | `float`, optional): | ||||
|                 The default timeout (in seconds) *per action* to be used. You | ||||
|                 may also override this timeout on a per-method basis. By | ||||
|                 default each action can take up to 60 seconds (the value of | ||||
|                 this timeout). | ||||
| 
 | ||||
|             total_timeout (`int` | `float`, optional): | ||||
|                 The total timeout (in seconds) to use for the whole | ||||
|                 conversation. This takes priority over per-action | ||||
|                 timeouts. After these many seconds pass, subsequent | ||||
|                 actions will result in ``asyncio.TimeoutError``. | ||||
| 
 | ||||
|             max_messages (`int`, optional): | ||||
|                 The maximum amount of messages this conversation will | ||||
|                 remember. After these many messages arrive in the | ||||
|                 specified chat, subsequent actions will result in | ||||
|                 ``ValueError``. | ||||
| 
 | ||||
|             exclusive (`bool`, optional): | ||||
|                 By default, conversations are exclusive within a single | ||||
|                 chat. That means that while a conversation is open in a | ||||
|                 chat, you can't open another one in the same chat, unless | ||||
|                 you disable this flag. | ||||
| 
 | ||||
|                 If you try opening an exclusive conversation for | ||||
|                 a chat where it's already open, it will raise | ||||
|                 ``AlreadyInConversationError``. | ||||
| 
 | ||||
|             replies_are_responses (`bool`, optional): | ||||
|                 Whether replies should be treated as responses or not. | ||||
| 
 | ||||
|                 If the setting is enabled, calls to `conv.get_response | ||||
|                 <telethon.tl.custom.conversation.Conversation.get_response>` | ||||
|                 and a subsequent call to `conv.get_reply | ||||
|                 <telethon.tl.custom.conversation.Conversation.get_reply>` | ||||
|                 will return different messages, otherwise they may return | ||||
|                 the same message. | ||||
| 
 | ||||
|                 Consider the following scenario with one outgoing message, | ||||
|                 1, and two incoming messages, the second one replying:: | ||||
| 
 | ||||
|                                         Hello! <1 | ||||
|                     2> (reply to 1) Hi! | ||||
|                     3> (reply to 1) How are you? | ||||
| 
 | ||||
|                 And the following code: | ||||
| 
 | ||||
|                 .. code-block:: python | ||||
| 
 | ||||
|                     async with client.conversation(chat) as conv: | ||||
|                         msg1 = await conv.send_message('Hello!') | ||||
|                         msg2 = await conv.get_response() | ||||
|                         msg3 = await conv.get_reply() | ||||
| 
 | ||||
|                 With the setting enabled, ``msg2`` will be ``'Hi!'`` and | ||||
|                 ``msg3`` be ``'How are you?'`` since replies are also | ||||
|                 responses, and a response was already returned. | ||||
| 
 | ||||
|                 With the setting disabled, both ``msg2`` and ``msg3`` will | ||||
|                 be ``'Hi!'`` since one is a response and also a reply. | ||||
| 
 | ||||
|         Returns | ||||
|             A `Conversation <telethon.tl.custom.conversation.Conversation>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # <you> denotes outgoing messages you sent | ||||
|                 # <usr> denotes incoming response messages | ||||
|                 with bot.conversation(chat) as conv: | ||||
|                     # <you> Hi! | ||||
|                     conv.send_message('Hi!') | ||||
| 
 | ||||
|                     # <usr> Hello! | ||||
|                     hello = conv.get_response() | ||||
| 
 | ||||
|                     # <you> Please tell me your name | ||||
|                     conv.send_message('Please tell me your name') | ||||
| 
 | ||||
|                     # <usr> ? | ||||
|                     name = conv.get_response().raw_text | ||||
| 
 | ||||
|                     while not any(x.isalpha() for x in name): | ||||
|                         # <you> Your name didn't have any letters! Try again | ||||
|                         conv.send_message("Your name didn't have any letters! Try again") | ||||
| 
 | ||||
|                         # <usr> Human | ||||
|                         name = conv.get_response().raw_text | ||||
| 
 | ||||
|                     # <you> Thanks Human! | ||||
|                     conv.send_message('Thanks {}!'.format(name)) | ||||
|         """ | ||||
|     return custom.Conversation( | ||||
|         self, | ||||
|         entity, | ||||
|  | @ -602,5 +272,3 @@ class DialogMethods: | |||
|         replies_are_responses=replies_are_responses | ||||
| 
 | ||||
|     ) | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -192,11 +192,7 @@ class _GenericDownloadIter(_DirectDownloadIter): | |||
|             self.request.offset -= self._stride | ||||
| 
 | ||||
| 
 | ||||
| class DownloadMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     async def download_profile_photo( | ||||
| async def download_profile_photo( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         file: 'hints.FileLike' = None, | ||||
|  | @ -307,7 +303,7 @@ class DownloadMethods: | |||
|             # Until there's a report for chats, no need to. | ||||
|             return None | ||||
| 
 | ||||
|     async def download_media( | ||||
| async def download_media( | ||||
|         self: 'TelegramClient', | ||||
|         message: 'hints.MessageLike', | ||||
|         file: 'hints.FileLike' = None, | ||||
|  | @ -424,7 +420,7 @@ class DownloadMethods: | |||
|             media, file, progress_callback | ||||
|         ) | ||||
| 
 | ||||
|     async def download_file( | ||||
| async def download_file( | ||||
|         self: 'TelegramClient', | ||||
|         input_location: 'hints.FileLike', | ||||
|         file: 'hints.OutFileLike' = None, | ||||
|  | @ -498,7 +494,7 @@ class DownloadMethods: | |||
|         iv=iv, | ||||
|     ) | ||||
| 
 | ||||
|     async def _download_file( | ||||
| async def _download_file( | ||||
|         self: 'TelegramClient', | ||||
|         input_location: 'hints.FileLike', | ||||
|         file: 'hints.OutFileLike' = None, | ||||
|  | @ -558,7 +554,7 @@ class DownloadMethods: | |||
|         if isinstance(file, str) or in_memory: | ||||
|             f.close() | ||||
| 
 | ||||
|     def iter_download( | ||||
| def iter_download( | ||||
|         self: 'TelegramClient', | ||||
|         file: 'hints.FileLike', | ||||
|         *, | ||||
|  | @ -569,7 +565,7 @@ class DownloadMethods: | |||
|         request_size: int = MAX_CHUNK_SIZE, | ||||
|         file_size: int = None, | ||||
|         dc_id: int = None | ||||
|     ): | ||||
| ): | ||||
|     """ | ||||
|     Iterates over a file download, yielding chunks of the file. | ||||
| 
 | ||||
|  | @ -664,7 +660,7 @@ class DownloadMethods: | |||
|         dc_id=dc_id, | ||||
|     ) | ||||
| 
 | ||||
|     def _iter_download( | ||||
| def _iter_download( | ||||
|         self: 'TelegramClient', | ||||
|         file: 'hints.FileLike', | ||||
|         *, | ||||
|  | @ -676,7 +672,7 @@ class DownloadMethods: | |||
|         file_size: int = None, | ||||
|         dc_id: int = None, | ||||
|         msg_data: tuple = None | ||||
|     ): | ||||
| ): | ||||
|     info = utils._get_file_info(file) | ||||
|     if info.dc_id is not None: | ||||
|         dc_id = info.dc_id | ||||
|  | @ -728,12 +724,8 @@ class DownloadMethods: | |||
|         msg_data=msg_data, | ||||
|     ) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Private methods | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _get_thumb(thumbs, thumb): | ||||
| def _get_thumb(thumbs, thumb): | ||||
|     # Seems Telegram has changed the order and put `PhotoStrippedSize` | ||||
|     # last while this is the smallest (layer 116). Ensure we have the | ||||
|     # sizes sorted correctly with a custom function. | ||||
|  | @ -773,7 +765,7 @@ class DownloadMethods: | |||
|     else: | ||||
|         return None | ||||
| 
 | ||||
|     def _download_cached_photo_size(self: 'TelegramClient', size, file): | ||||
| def _download_cached_photo_size(self: 'TelegramClient', size, file): | ||||
|     # No need to download anything, simply write the bytes | ||||
|     if isinstance(size, types.PhotoStrippedSize): | ||||
|         data = utils.stripped_photo_to_jpg(size.bytes) | ||||
|  | @ -795,7 +787,7 @@ class DownloadMethods: | |||
|             f.close() | ||||
|     return file | ||||
| 
 | ||||
|     async def _download_photo(self: 'TelegramClient', photo, file, date, thumb, progress_callback): | ||||
| async def _download_photo(self: 'TelegramClient', photo, file, date, thumb, progress_callback): | ||||
|     """Specialized version of .download_media() for photos""" | ||||
|     # Determine the photo and its largest size | ||||
|     if isinstance(photo, types.MessageMediaPhoto): | ||||
|  | @ -834,8 +826,7 @@ class DownloadMethods: | |||
|     ) | ||||
|     return result if file is bytes else file | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _get_kind_and_names(attributes): | ||||
| def _get_kind_and_names(attributes): | ||||
|     """Gets kind and possible names for :tl:`DocumentAttribute`.""" | ||||
|     kind = 'document' | ||||
|     possible_names = [] | ||||
|  | @ -858,7 +849,7 @@ class DownloadMethods: | |||
| 
 | ||||
|     return kind, possible_names | ||||
| 
 | ||||
|     async def _download_document( | ||||
| async def _download_document( | ||||
|         self, document, file, date, thumb, progress_callback, msg_data): | ||||
|     """Specialized version of .download_media() for documents.""" | ||||
|     if isinstance(document, types.MessageMediaDocument): | ||||
|  | @ -894,8 +885,7 @@ class DownloadMethods: | |||
| 
 | ||||
|     return result if file is bytes else file | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _download_contact(cls, mm_contact, file): | ||||
| def _download_contact(cls, mm_contact, file): | ||||
|     """ | ||||
|     Specialized version of .download_media() for contacts. | ||||
|     Will make use of the vCard 4.0 format. | ||||
|  | @ -936,8 +926,7 @@ class DownloadMethods: | |||
| 
 | ||||
|     return file | ||||
| 
 | ||||
|     @classmethod | ||||
|     async def _download_web_document(cls, web, file, progress_callback): | ||||
| async def _download_web_document(cls, web, file, progress_callback): | ||||
|     """ | ||||
|     Specialized version of .download_media() for web documents. | ||||
|     """ | ||||
|  | @ -977,8 +966,7 @@ class DownloadMethods: | |||
| 
 | ||||
|     return f.getvalue() if in_memory else file | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _get_proper_filename(file, kind, extension, | ||||
| def _get_proper_filename(file, kind, extension, | ||||
|                             date=None, possible_names=None): | ||||
|     """Gets a proper filename for 'file', if this is a path. | ||||
| 
 | ||||
|  | @ -1039,5 +1027,3 @@ class DownloadMethods: | |||
|         if not os.path.isfile(result): | ||||
|             return result | ||||
|         i += 1 | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -9,12 +9,7 @@ if typing.TYPE_CHECKING: | |||
|     from .telegramclient import TelegramClient | ||||
| 
 | ||||
| 
 | ||||
| class MessageParseMethods: | ||||
| 
 | ||||
|     # region Public properties | ||||
| 
 | ||||
|     @property | ||||
|     def parse_mode(self: 'TelegramClient'): | ||||
| def get_parse_mode(self: 'TelegramClient'): | ||||
|     """ | ||||
|     This property is the default parse mode used when sending messages. | ||||
|     Defaults to `telethon.extensions.markdown`. It will always | ||||
|  | @ -49,15 +44,14 @@ class MessageParseMethods: | |||
|     """ | ||||
|     return self._parse_mode | ||||
| 
 | ||||
|     @parse_mode.setter | ||||
|     def parse_mode(self: 'TelegramClient', mode: str): | ||||
| def set_parse_mode(self: 'TelegramClient', mode: str): | ||||
|     self._parse_mode = utils.sanitize_parse_mode(mode) | ||||
| 
 | ||||
|     # endregion | ||||
| # endregion | ||||
| 
 | ||||
|     # region Private methods | ||||
| # region Private methods | ||||
| 
 | ||||
|     async def _replace_with_mention(self: 'TelegramClient', entities, i, user): | ||||
| async def _replace_with_mention(self: 'TelegramClient', entities, i, user): | ||||
|     """ | ||||
|     Helper method to replace ``entities[i]`` to mention ``user``, | ||||
|     or do nothing if it can't be found. | ||||
|  | @ -71,7 +65,7 @@ class MessageParseMethods: | |||
|     except (ValueError, TypeError): | ||||
|         return False | ||||
| 
 | ||||
|     async def _parse_message_text(self: 'TelegramClient', message, parse_mode): | ||||
| async def _parse_message_text(self: 'TelegramClient', message, parse_mode): | ||||
|     """ | ||||
|     Returns a (parsed message, entities) tuple depending on ``parse_mode``. | ||||
|     """ | ||||
|  | @ -105,7 +99,7 @@ class MessageParseMethods: | |||
| 
 | ||||
|     return message, msg_entities | ||||
| 
 | ||||
|     def _get_response_message(self: 'TelegramClient', request, result, input_chat): | ||||
| def _get_response_message(self: 'TelegramClient', request, result, input_chat): | ||||
|     """ | ||||
|     Extracts the response message known a request and Update result. | ||||
|     The request may also be the ID of the message to match. | ||||
|  | @ -224,5 +218,3 @@ class MessageParseMethods: | |||
|         else None | ||||
|         for rnd in random_id | ||||
|     ] | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -320,13 +320,7 @@ class _IDsIter(RequestIter): | |||
|                 self.buffer.append(message) | ||||
| 
 | ||||
| 
 | ||||
| class MessageMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     # region Message retrieval | ||||
| 
 | ||||
|     def iter_messages( | ||||
| def iter_messages( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         limit: float = None, | ||||
|  | @ -344,167 +338,7 @@ class MessageMethods: | |||
|         reverse: bool = False, | ||||
|         reply_to: int = None, | ||||
|         scheduled: bool = False | ||||
|     ) -> 'typing.Union[_MessagesIter, _IDsIter]': | ||||
|         """ | ||||
|         Iterator over the messages for the given chat. | ||||
| 
 | ||||
|         The default order is from newest to oldest, but this | ||||
|         behaviour can be changed with the `reverse` parameter. | ||||
| 
 | ||||
|         If either `search`, `filter` or `from_user` are provided, | ||||
|         :tl:`messages.Search` will be used instead of :tl:`messages.getHistory`. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             Telegram's flood wait limit for :tl:`GetHistoryRequest` seems to | ||||
|             be around 30 seconds per 10 requests, therefore a sleep of 1 | ||||
|             second is the default for this limit (or above). | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The entity from whom to retrieve the message history. | ||||
| 
 | ||||
|                 It may be `None` to perform a global search, or | ||||
|                 to get messages by their ID from no particular chat. | ||||
|                 Note that some of the offsets will not work if this | ||||
|                 is the case. | ||||
| 
 | ||||
|                 Note that if you want to perform a global search, | ||||
|                 you **must** set a non-empty `search` string, a `filter`. | ||||
|                 or `from_user`. | ||||
| 
 | ||||
|             limit (`int` | `None`, optional): | ||||
|                 Number of messages to be retrieved. Due to limitations with | ||||
|                 the API retrieving more than 3000 messages will take longer | ||||
|                 than half a minute (or even more based on previous calls). | ||||
| 
 | ||||
|                 The limit may also be `None`, which would eventually return | ||||
|                 the whole history. | ||||
| 
 | ||||
|             offset_date (`datetime`): | ||||
|                 Offset date (messages *previous* to this date will be | ||||
|                 retrieved). Exclusive. | ||||
| 
 | ||||
|             offset_id (`int`): | ||||
|                 Offset message ID (only messages *previous* to the given | ||||
|                 ID will be retrieved). Exclusive. | ||||
| 
 | ||||
|             max_id (`int`): | ||||
|                 All the messages with a higher (newer) ID or equal to this will | ||||
|                 be excluded. | ||||
| 
 | ||||
|             min_id (`int`): | ||||
|                 All the messages with a lower (older) ID or equal to this will | ||||
|                 be excluded. | ||||
| 
 | ||||
|             add_offset (`int`): | ||||
|                 Additional message offset (all of the specified offsets + | ||||
|                 this offset = older messages). | ||||
| 
 | ||||
|             search (`str`): | ||||
|                 The string to be used as a search query. | ||||
| 
 | ||||
|             filter (:tl:`MessagesFilter` | `type`): | ||||
|                 The filter to use when returning messages. For instance, | ||||
|                 :tl:`InputMessagesFilterPhotos` would yield only messages | ||||
|                 containing photos. | ||||
| 
 | ||||
|             from_user (`entity`): | ||||
|                 Only messages from this entity will be returned. | ||||
| 
 | ||||
|             wait_time (`int`): | ||||
|                 Wait time (in seconds) between different | ||||
|                 :tl:`GetHistoryRequest`. Use this parameter to avoid hitting | ||||
|                 the ``FloodWaitError`` as needed. If left to `None`, it will | ||||
|                 default to 1 second only if the limit is higher than 3000. | ||||
| 
 | ||||
|                 If the ``ids`` parameter is used, this time will default | ||||
|                 to 10 seconds only if the amount of IDs is higher than 300. | ||||
| 
 | ||||
|             ids (`int`, `list`): | ||||
|                 A single integer ID (or several IDs) for the message that | ||||
|                 should be returned. This parameter takes precedence over | ||||
|                 the rest (which will be ignored if this is set). This can | ||||
|                 for instance be used to get the message with ID 123 from | ||||
|                 a channel. Note that if the message doesn't exist, `None` | ||||
|                 will appear in its place, so that zipping the list of IDs | ||||
|                 with the messages can match one-to-one. | ||||
| 
 | ||||
|                 .. note:: | ||||
| 
 | ||||
|                     At the time of writing, Telegram will **not** return | ||||
|                     :tl:`MessageEmpty` for :tl:`InputMessageReplyTo` IDs that | ||||
|                     failed (i.e. the message is not replying to any, or is | ||||
|                     replying to a deleted message). This means that it is | ||||
|                     **not** possible to match messages one-by-one, so be | ||||
|                     careful if you use non-integers in this parameter. | ||||
| 
 | ||||
|             reverse (`bool`, optional): | ||||
|                 If set to `True`, the messages will be returned in reverse | ||||
|                 order (from oldest to newest, instead of the default newest | ||||
|                 to oldest). This also means that the meaning of `offset_id` | ||||
|                 and `offset_date` parameters is reversed, although they will | ||||
|                 still be exclusive. `min_id` becomes equivalent to `offset_id` | ||||
|                 instead of being `max_id` as well since messages are returned | ||||
|                 in ascending order. | ||||
| 
 | ||||
|                 You cannot use this if both `entity` and `ids` are `None`. | ||||
| 
 | ||||
|             reply_to (`int`, optional): | ||||
|                 If set to a message ID, the messages that reply to this ID | ||||
|                 will be returned. This feature is also known as comments in | ||||
|                 posts of broadcast channels, or viewing threads in groups. | ||||
| 
 | ||||
|                 This feature can only be used in broadcast channels and their | ||||
|                 linked megagroups. Using it in a chat or private conversation | ||||
|                 will result in ``telethon.errors.PeerIdInvalidError`` to occur. | ||||
| 
 | ||||
|                 When using this parameter, the ``filter`` and ``search`` | ||||
|                 parameters have no effect, since Telegram's API doesn't | ||||
|                 support searching messages in replies. | ||||
| 
 | ||||
|                 .. note:: | ||||
| 
 | ||||
|                     This feature is used to get replies to a message in the | ||||
|                     *discussion* group. If the same broadcast channel sends | ||||
|                     a message and replies to it itself, that reply will not | ||||
|                     be included in the results. | ||||
| 
 | ||||
|             scheduled (`bool`, optional): | ||||
|                 If set to `True`, messages which are scheduled will be returned. | ||||
|                 All other parameter will be ignored for this, except `entity`. | ||||
| 
 | ||||
|         Yields | ||||
|             Instances of `Message <telethon.tl.custom.message.Message>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # From most-recent to oldest | ||||
|                 async for message in client.iter_messages(chat): | ||||
|                     print(message.id, message.text) | ||||
| 
 | ||||
|                 # From oldest to most-recent | ||||
|                 async for message in client.iter_messages(chat, reverse=True): | ||||
|                     print(message.id, message.text) | ||||
| 
 | ||||
|                 # Filter by sender | ||||
|                 async for message in client.iter_messages(chat, from_user='me'): | ||||
|                     print(message.text) | ||||
| 
 | ||||
|                 # Server-side search with fuzzy text | ||||
|                 async for message in client.iter_messages(chat, search='hello'): | ||||
|                     print(message.id) | ||||
| 
 | ||||
|                 # Filter by message type: | ||||
|                 from telethon.tl.types import InputMessagesFilterPhotos | ||||
|                 async for message in client.iter_messages(chat, filter=InputMessagesFilterPhotos): | ||||
|                     print(message.photo) | ||||
| 
 | ||||
|                 # Getting comments from a post in a channel: | ||||
|                 async for message in client.iter_messages(channel, reply_to=123): | ||||
|                     print(message.chat.title, message.text) | ||||
|         """ | ||||
| ) -> 'typing.Union[_MessagesIter, _IDsIter]': | ||||
|     if ids is not None: | ||||
|         if not utils.is_list_like(ids): | ||||
|             ids = [ids] | ||||
|  | @ -536,37 +370,7 @@ class MessageMethods: | |||
|         scheduled=scheduled | ||||
|     ) | ||||
| 
 | ||||
|     async def get_messages(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | ||||
|         """ | ||||
|         Same as `iter_messages()`, but returns a | ||||
|         `TotalList <telethon.helpers.TotalList>` instead. | ||||
| 
 | ||||
|         If the `limit` is not set, it will be 1 by default unless both | ||||
|         `min_id` **and** `max_id` are set (as *named* arguments), in | ||||
|         which case the entire range will be returned. | ||||
| 
 | ||||
|         This is so because any integer limit would be rather arbitrary and | ||||
|         it's common to only want to fetch one message, but if a range is | ||||
|         specified it makes sense that it should return the entirety of it. | ||||
| 
 | ||||
|         If `ids` is present in the *named* arguments and is not a list, | ||||
|         a single `Message <telethon.tl.custom.message.Message>` will be | ||||
|         returned for convenience instead of a list. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Get 0 photos and print the total to show how many photos there are | ||||
|                 from telethon.tl.types import InputMessagesFilterPhotos | ||||
|                 photos = await client.get_messages(chat, 0, filter=InputMessagesFilterPhotos) | ||||
|                 print(photos.total) | ||||
| 
 | ||||
|                 # Get all the photos | ||||
|                 photos = await client.get_messages(chat, None, filter=InputMessagesFilterPhotos) | ||||
| 
 | ||||
|                 # Get messages by ID: | ||||
|                 message_1337 = await client.get_messages(chat, ids=1337) | ||||
|         """ | ||||
| async def get_messages(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | ||||
|     if len(args) == 1 and 'limit' not in kwargs: | ||||
|         if 'min_id' in kwargs and 'max_id' in kwargs: | ||||
|             kwargs['limit'] = None | ||||
|  | @ -585,17 +389,12 @@ class MessageMethods: | |||
| 
 | ||||
|     return await it.collect() | ||||
| 
 | ||||
|     get_messages.__signature__ = inspect.signature(iter_messages) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Message sending/editing/deleting | ||||
| 
 | ||||
|     async def _get_comment_data( | ||||
| async def _get_comment_data( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'typing.Union[int, types.Message]' | ||||
|     ): | ||||
| ): | ||||
|     r = await self(functions.messages.GetDiscussionMessageRequest( | ||||
|         peer=entity, | ||||
|         msg_id=utils.get_message_id(message) | ||||
|  | @ -604,7 +403,7 @@ class MessageMethods: | |||
|     chat = next(c for c in r.chats if c.id == m.peer_id.channel_id) | ||||
|     return utils.get_input_peer(chat), m.id | ||||
| 
 | ||||
|     async def send_message( | ||||
| async def send_message( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'hints.MessageLike' = '', | ||||
|  | @ -624,178 +423,7 @@ class MessageMethods: | |||
|         supports_streaming: bool = False, | ||||
|         schedule: 'hints.DateLike' = None, | ||||
|         comment_to: 'typing.Union[int, types.Message]' = None | ||||
|     ) -> 'types.Message': | ||||
|         """ | ||||
|         Sends a message to the specified user, chat or channel. | ||||
| 
 | ||||
|         The default parse mode is the same as the official applications | ||||
|         (a custom flavour of markdown). ``**bold**, `code` or __italic__`` | ||||
|         are available. In addition you can send ``[links](https://example.com)`` | ||||
|         and ``[mentions](@username)`` (or using IDs like in the Bot API: | ||||
|         ``[mention](tg://user?id=123456789)``) and ``pre`` blocks with three | ||||
|         backticks. | ||||
| 
 | ||||
|         Sending a ``/start`` command with a parameter (like ``?start=data``) | ||||
|         is also done through this method. Simply send ``'/start data'`` to | ||||
|         the bot. | ||||
| 
 | ||||
|         See also `Message.respond() <telethon.tl.custom.message.Message.respond>` | ||||
|         and `Message.reply() <telethon.tl.custom.message.Message.reply>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 To who will it be sent. | ||||
| 
 | ||||
|             message (`str` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 The message to be sent, or another message object to resend. | ||||
| 
 | ||||
|                 The maximum length for a message is 35,000 bytes or 4,096 | ||||
|                 characters. Longer messages will not be sliced automatically, | ||||
|                 and you should slice them manually if the text to send is | ||||
|                 longer than said length. | ||||
| 
 | ||||
|             reply_to (`int` | `Message <telethon.tl.custom.message.Message>`, optional): | ||||
|                 Whether to reply to a message or not. If an integer is provided, | ||||
|                 it should be the ID of the message that it should reply to. | ||||
| 
 | ||||
|             attributes (`list`, optional): | ||||
|                 Optional attributes that override the inferred ones, like | ||||
|                 :tl:`DocumentAttributeFilename` and so on. | ||||
| 
 | ||||
|             parse_mode (`object`, optional): | ||||
|                 See the `TelegramClient.parse_mode | ||||
|                 <telethon.client.messageparse.MessageParseMethods.parse_mode>` | ||||
|                 property for allowed values. Markdown parsing will be used by | ||||
|                 default. | ||||
| 
 | ||||
|             formatting_entities (`list`, optional): | ||||
|                 A list of message formatting entities. When provided, the ``parse_mode`` is ignored. | ||||
| 
 | ||||
|             link_preview (`bool`, optional): | ||||
|                 Should the link preview be shown? | ||||
| 
 | ||||
|             file (`file`, optional): | ||||
|                 Sends a message with a file attached (e.g. a photo, | ||||
|                 video, audio or document). The ``message`` may be empty. | ||||
| 
 | ||||
|             thumb (`str` | `bytes` | `file`, optional): | ||||
|                 Optional JPEG thumbnail (for documents). **Telegram will | ||||
|                 ignore this parameter** unless you pass a ``.jpg`` file! | ||||
|                 The file must also be small in dimensions and in disk size. | ||||
|                 Successful thumbnails were files below 20kB and 320x320px. | ||||
|                 Width/height and dimensions/size ratios may be important. | ||||
|                 For Telegram to accept a thumbnail, you must provide the | ||||
|                 dimensions of the underlying media through ``attributes=`` | ||||
|                 with :tl:`DocumentAttributesVideo` or by installing the | ||||
|                 optional ``hachoir`` dependency. | ||||
| 
 | ||||
|             force_document (`bool`, optional): | ||||
|                 Whether to send the given file as a document or not. | ||||
| 
 | ||||
|             clear_draft (`bool`, optional): | ||||
|                 Whether the existing draft should be cleared or not. | ||||
| 
 | ||||
|             buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`): | ||||
|                 The matrix (list of lists), row list or button to be shown | ||||
|                 after sending the message. This parameter will only work if | ||||
|                 you have signed in as a bot. You can also pass your own | ||||
|                 :tl:`ReplyMarkup` here. | ||||
| 
 | ||||
|                 All the following limits apply together: | ||||
| 
 | ||||
|                 * There can be 100 buttons at most (any more are ignored). | ||||
|                 * There can be 8 buttons per row at most (more are ignored). | ||||
|                 * The maximum callback data per button is 64 bytes. | ||||
|                 * The maximum data that can be embedded in total is just | ||||
|                   over 4KB, shared between inline callback data and text. | ||||
| 
 | ||||
|             silent (`bool`, optional): | ||||
|                 Whether the message should notify people in a broadcast | ||||
|                 channel or not. Defaults to `False`, which means it will | ||||
|                 notify them. Set it to `True` to alter this behaviour. | ||||
| 
 | ||||
|             background (`bool`, optional): | ||||
|                 Whether the message should be send in background. | ||||
| 
 | ||||
|             supports_streaming (`bool`, optional): | ||||
|                 Whether the sent video supports streaming or not. Note that | ||||
|                 Telegram only recognizes as streamable some formats like MP4, | ||||
|                 and others like AVI or MKV will not work. You should convert | ||||
|                 these to MP4 before sending if you want them to be streamable. | ||||
|                 Unsupported formats will result in ``VideoContentTypeError``. | ||||
| 
 | ||||
|             schedule (`hints.DateLike`, optional): | ||||
|                 If set, the message won't send immediately, and instead | ||||
|                 it will be scheduled to be automatically sent at a later | ||||
|                 time. | ||||
| 
 | ||||
|             comment_to (`int` | `Message <telethon.tl.custom.message.Message>`, optional): | ||||
|                 Similar to ``reply_to``, but replies in the linked group of a | ||||
|                 broadcast channel instead (effectively leaving a "comment to" | ||||
|                 the specified message). | ||||
| 
 | ||||
|                 This parameter takes precedence over ``reply_to``. If there is | ||||
|                 no linked chat, `telethon.errors.sgIdInvalidError` is raised. | ||||
| 
 | ||||
|         Returns | ||||
|             The sent `custom.Message <telethon.tl.custom.message.Message>`. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Markdown is the default | ||||
|                 await client.send_message('me', 'Hello **world**!') | ||||
| 
 | ||||
|                 # Default to another parse mode | ||||
|                 client.parse_mode = 'html' | ||||
| 
 | ||||
|                 await client.send_message('me', 'Some <b>bold</b> and <i>italic</i> text') | ||||
|                 await client.send_message('me', 'An <a href="https://example.com">URL</a>') | ||||
|                 # code and pre tags also work, but those break the documentation :) | ||||
|                 await client.send_message('me', '<a href="tg://user?id=me">Mentions</a>') | ||||
| 
 | ||||
|                 # Explicit parse mode | ||||
|                 # No parse mode by default | ||||
|                 client.parse_mode = None | ||||
| 
 | ||||
|                 # ...but here I want markdown | ||||
|                 await client.send_message('me', 'Hello, **world**!', parse_mode='md') | ||||
| 
 | ||||
|                 # ...and here I need HTML | ||||
|                 await client.send_message('me', 'Hello, <i>world</i>!', parse_mode='html') | ||||
| 
 | ||||
|                 # If you logged in as a bot account, you can send buttons | ||||
|                 from telethon import events, Button | ||||
| 
 | ||||
|                 @client.on(events.CallbackQuery) | ||||
|                 async def callback(event): | ||||
|                     await event.edit('Thank you for clicking {}!'.format(event.data)) | ||||
| 
 | ||||
|                 # Single inline button | ||||
|                 await client.send_message(chat, 'A single button, with "clk1" as data', | ||||
|                                           buttons=Button.inline('Click me', b'clk1')) | ||||
| 
 | ||||
|                 # Matrix of inline buttons | ||||
|                 await client.send_message(chat, 'Pick one from this grid', buttons=[ | ||||
|                     [Button.inline('Left'), Button.inline('Right')], | ||||
|                     [Button.url('Check this site!', 'https://example.com')] | ||||
|                 ]) | ||||
| 
 | ||||
|                 # Reply keyboard | ||||
|                 await client.send_message(chat, 'Welcome', buttons=[ | ||||
|                     Button.text('Thanks!', resize=True, single_use=True), | ||||
|                     Button.request_phone('Send phone'), | ||||
|                     Button.request_location('Send location') | ||||
|                 ]) | ||||
| 
 | ||||
|                 # Forcing replies or clearing buttons. | ||||
|                 await client.send_message(chat, 'Reply to me', buttons=Button.force_reply()) | ||||
|                 await client.send_message(chat, 'Bye Keyboard!', buttons=Button.clear()) | ||||
| 
 | ||||
|                 # Scheduling a message to be sent after 5 minutes | ||||
|                 from datetime import timedelta | ||||
|                 await client.send_message(chat, 'Hi, future!', schedule=timedelta(minutes=5)) | ||||
|         """ | ||||
| ) -> 'types.Message': | ||||
|     if file is not None: | ||||
|         return await self.send_file( | ||||
|             entity, file, caption=message, reply_to=reply_to, | ||||
|  | @ -887,7 +515,7 @@ class MessageMethods: | |||
| 
 | ||||
|     return self._get_response_message(request, result, entity) | ||||
| 
 | ||||
|     async def forward_messages( | ||||
| async def forward_messages( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         messages: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', | ||||
|  | @ -898,75 +526,7 @@ class MessageMethods: | |||
|         silent: bool = None, | ||||
|         as_album: bool = None, | ||||
|         schedule: 'hints.DateLike' = None | ||||
|     ) -> 'typing.Sequence[types.Message]': | ||||
|         """ | ||||
|         Forwards the given messages to the specified entity. | ||||
| 
 | ||||
|         If you want to "forward" a message without the forward header | ||||
|         (the "forwarded from" text), you should use `send_message` with | ||||
|         the original message instead. This will send a copy of it. | ||||
| 
 | ||||
|         See also `Message.forward_to() <telethon.tl.custom.message.Message.forward_to>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 To which entity the message(s) will be forwarded. | ||||
| 
 | ||||
|             messages (`list` | `int` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 The message(s) to forward, or their integer IDs. | ||||
| 
 | ||||
|             from_peer (`entity`): | ||||
|                 If the given messages are integer IDs and not instances | ||||
|                 of the ``Message`` class, this *must* be specified in | ||||
|                 order for the forward to work. This parameter indicates | ||||
|                 the entity from which the messages should be forwarded. | ||||
| 
 | ||||
|             silent (`bool`, optional): | ||||
|                 Whether the message should notify people with sound or not. | ||||
|                 Defaults to `False` (send with a notification sound unless | ||||
|                 the person has the chat muted). Set it to `True` to alter | ||||
|                 this behaviour. | ||||
| 
 | ||||
|             background (`bool`, optional): | ||||
|                 Whether the message should be forwarded in background. | ||||
| 
 | ||||
|             with_my_score (`bool`, optional): | ||||
|                 Whether forwarded should contain your game score. | ||||
| 
 | ||||
|             as_album (`bool`, optional): | ||||
|                 This flag no longer has any effect. | ||||
| 
 | ||||
|             schedule (`hints.DateLike`, optional): | ||||
|                 If set, the message(s) won't forward immediately, and | ||||
|                 instead they will be scheduled to be automatically sent | ||||
|                 at a later time. | ||||
| 
 | ||||
|         Returns | ||||
|             The list of forwarded `Message <telethon.tl.custom.message.Message>`, | ||||
|             or a single one if a list wasn't provided as input. | ||||
| 
 | ||||
|             Note that if all messages are invalid (i.e. deleted) the call | ||||
|             will fail with ``MessageIdInvalidError``. If only some are | ||||
|             invalid, the list will have `None` instead of those messages. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # a single one | ||||
|                 await client.forward_messages(chat, message) | ||||
|                 # or | ||||
|                 await client.forward_messages(chat, message_id, from_chat) | ||||
|                 # or | ||||
|                 await message.forward_to(chat) | ||||
| 
 | ||||
|                 # multiple | ||||
|                 await client.forward_messages(chat, messages) | ||||
|                 # or | ||||
|                 await client.forward_messages(chat, message_ids, from_chat) | ||||
| 
 | ||||
|                 # Forwarding as a copy | ||||
|                 await client.send_message(chat, message) | ||||
|         """ | ||||
| ) -> 'typing.Sequence[types.Message]': | ||||
|     if as_album is not None: | ||||
|         warnings.warn('the as_album argument is deprecated and no longer has any effect') | ||||
| 
 | ||||
|  | @ -1016,7 +576,7 @@ class MessageMethods: | |||
| 
 | ||||
|     return sent[0] if single else sent | ||||
| 
 | ||||
|     async def edit_message( | ||||
| async def edit_message( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'typing.Union[hints.EntityLike, types.Message]', | ||||
|         message: 'hints.MessageLike' = None, | ||||
|  | @ -1032,117 +592,7 @@ class MessageMethods: | |||
|         buttons: 'hints.MarkupLike' = None, | ||||
|         supports_streaming: bool = False, | ||||
|         schedule: 'hints.DateLike' = None | ||||
|     ) -> 'types.Message': | ||||
|         """ | ||||
|         Edits the given message to change its text or media. | ||||
| 
 | ||||
|         See also `Message.edit() <telethon.tl.custom.message.Message.edit>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 From which chat to edit the message. This can also be | ||||
|                 the message to be edited, and the entity will be inferred | ||||
|                 from it, so the next parameter will be assumed to be the | ||||
|                 message text. | ||||
| 
 | ||||
|                 You may also pass a :tl:`InputBotInlineMessageID`, | ||||
|                 which is the only way to edit messages that were sent | ||||
|                 after the user selects an inline query result. | ||||
| 
 | ||||
|             message (`int` | `Message <telethon.tl.custom.message.Message>` | `str`): | ||||
|                 The ID of the message (or `Message | ||||
|                 <telethon.tl.custom.message.Message>` itself) to be edited. | ||||
|                 If the `entity` was a `Message | ||||
|                 <telethon.tl.custom.message.Message>`, then this message | ||||
|                 will be treated as the new text. | ||||
| 
 | ||||
|             text (`str`, optional): | ||||
|                 The new text of the message. Does nothing if the `entity` | ||||
|                 was a `Message <telethon.tl.custom.message.Message>`. | ||||
| 
 | ||||
|             parse_mode (`object`, optional): | ||||
|                 See the `TelegramClient.parse_mode | ||||
|                 <telethon.client.messageparse.MessageParseMethods.parse_mode>` | ||||
|                 property for allowed values. Markdown parsing will be used by | ||||
|                 default. | ||||
| 
 | ||||
|             attributes (`list`, optional): | ||||
|                 Optional attributes that override the inferred ones, like | ||||
|                 :tl:`DocumentAttributeFilename` and so on. | ||||
| 
 | ||||
|             formatting_entities (`list`, optional): | ||||
|                 A list of message formatting entities. When provided, the ``parse_mode`` is ignored. | ||||
| 
 | ||||
|             link_preview (`bool`, optional): | ||||
|                 Should the link preview be shown? | ||||
| 
 | ||||
|             file (`str` | `bytes` | `file` | `media`, optional): | ||||
|                 The file object that should replace the existing media | ||||
|                 in the message. | ||||
| 
 | ||||
|             thumb (`str` | `bytes` | `file`, optional): | ||||
|                 Optional JPEG thumbnail (for documents). **Telegram will | ||||
|                 ignore this parameter** unless you pass a ``.jpg`` file! | ||||
|                 The file must also be small in dimensions and in disk size. | ||||
|                 Successful thumbnails were files below 20kB and 320x320px. | ||||
|                 Width/height and dimensions/size ratios may be important. | ||||
|                 For Telegram to accept a thumbnail, you must provide the | ||||
|                 dimensions of the underlying media through ``attributes=`` | ||||
|                 with :tl:`DocumentAttributesVideo` or by installing the | ||||
|                 optional ``hachoir`` dependency. | ||||
| 
 | ||||
|             force_document (`bool`, optional): | ||||
|                 Whether to send the given file as a document or not. | ||||
| 
 | ||||
|             buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`): | ||||
|                 The matrix (list of lists), row list or button to be shown | ||||
|                 after sending the message. This parameter will only work if | ||||
|                 you have signed in as a bot. You can also pass your own | ||||
|                 :tl:`ReplyMarkup` here. | ||||
| 
 | ||||
|             supports_streaming (`bool`, optional): | ||||
|                 Whether the sent video supports streaming or not. Note that | ||||
|                 Telegram only recognizes as streamable some formats like MP4, | ||||
|                 and others like AVI or MKV will not work. You should convert | ||||
|                 these to MP4 before sending if you want them to be streamable. | ||||
|                 Unsupported formats will result in ``VideoContentTypeError``. | ||||
| 
 | ||||
|             schedule (`hints.DateLike`, optional): | ||||
|                 If set, the message won't be edited immediately, and instead | ||||
|                 it will be scheduled to be automatically edited at a later | ||||
|                 time. | ||||
| 
 | ||||
|                 Note that this parameter will have no effect if you are | ||||
|                 trying to edit a message that was sent via inline bots. | ||||
| 
 | ||||
|         Returns | ||||
|             The edited `Message <telethon.tl.custom.message.Message>`, | ||||
|             unless `entity` was a :tl:`InputBotInlineMessageID` in which | ||||
|             case this method returns a boolean. | ||||
| 
 | ||||
|         Raises | ||||
|             ``MessageAuthorRequiredError`` if you're not the author of the | ||||
|             message but tried editing it anyway. | ||||
| 
 | ||||
|             ``MessageNotModifiedError`` if the contents of the message were | ||||
|             not modified at all. | ||||
| 
 | ||||
|             ``MessageIdInvalidError`` if the ID of the message is invalid | ||||
|             (the ID itself may be correct, but the message with that ID | ||||
|             cannot be edited). For example, when trying to edit messages | ||||
|             with a reply markup (or clear markup) this error will be raised. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 message = await client.send_message(chat, 'hello') | ||||
| 
 | ||||
|                 await client.edit_message(chat, message, 'hello!') | ||||
|                 # or | ||||
|                 await client.edit_message(chat, message.id, 'hello!!') | ||||
|                 # or | ||||
|                 await client.edit_message(message, 'hello!!!') | ||||
|         """ | ||||
| ) -> 'types.Message': | ||||
|     if isinstance(entity, types.InputBotInlineMessageID): | ||||
|         text = text or message | ||||
|         message = entity | ||||
|  | @ -1194,56 +644,12 @@ class MessageMethods: | |||
|     msg = self._get_response_message(request, await self(request), entity) | ||||
|     return msg | ||||
| 
 | ||||
|     async def delete_messages( | ||||
| async def delete_messages( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message_ids: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', | ||||
|         *, | ||||
|         revoke: bool = True) -> 'typing.Sequence[types.messages.AffectedMessages]': | ||||
|         """ | ||||
|         Deletes the given messages, optionally "for everyone". | ||||
| 
 | ||||
|         See also `Message.delete() <telethon.tl.custom.message.Message.delete>`. | ||||
| 
 | ||||
|         .. warning:: | ||||
| 
 | ||||
|             This method does **not** validate that the message IDs belong | ||||
|             to the chat that you passed! It's possible for the method to | ||||
|             delete messages from different private chats and small group | ||||
|             chats at once, so make sure to pass the right IDs. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 From who the message will be deleted. This can actually | ||||
|                 be `None` for normal chats, but **must** be present | ||||
|                 for channels and megagroups. | ||||
| 
 | ||||
|             message_ids (`list` | `int` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 The IDs (or ID) or messages to be deleted. | ||||
| 
 | ||||
|             revoke (`bool`, optional): | ||||
|                 Whether the message should be deleted for everyone or not. | ||||
|                 By default it has the opposite behaviour of official clients, | ||||
|                 and it will delete the message for everyone. | ||||
| 
 | ||||
|                 `Since 24 March 2019 | ||||
|                 <https://telegram.org/blog/unsend-privacy-emoji>`_, you can | ||||
|                 also revoke messages of any age (i.e. messages sent long in | ||||
|                 the past) the *other* person sent in private conversations | ||||
|                 (and of course your messages too). | ||||
| 
 | ||||
|                 Disabling this has no effect on channels or megagroups, | ||||
|                 since it will unconditionally delete the message for everyone. | ||||
| 
 | ||||
|         Returns | ||||
|             A list of :tl:`AffectedMessages`, each item being the result | ||||
|             for the delete calls of the messages in chunks of 100 each. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 await client.delete_messages(chat, messages) | ||||
|         """ | ||||
|     if not utils.is_list_like(message_ids): | ||||
|         message_ids = (message_ids,) | ||||
| 
 | ||||
|  | @ -1267,60 +673,13 @@ class MessageMethods: | |||
|         return await self([functions.messages.DeleteMessagesRequest( | ||||
|                         list(c), revoke) for c in utils.chunks(message_ids)]) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Miscellaneous | ||||
| 
 | ||||
|     async def send_read_acknowledge( | ||||
| async def send_read_acknowledge( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]' = None, | ||||
|         *, | ||||
|         max_id: int = None, | ||||
|         clear_mentions: bool = False) -> bool: | ||||
|         """ | ||||
|         Marks messages as read and optionally clears mentions. | ||||
| 
 | ||||
|         This effectively marks a message as read (or more than one) in the | ||||
|         given conversation. | ||||
| 
 | ||||
|         If neither message nor maximum ID are provided, all messages will be | ||||
|         marked as read by assuming that ``max_id = 0``. | ||||
| 
 | ||||
|         If a message or maximum ID is provided, all the messages up to and | ||||
|         including such ID will be marked as read (for all messages whose ID | ||||
|         ≤ max_id). | ||||
| 
 | ||||
|         See also `Message.mark_read() <telethon.tl.custom.message.Message.mark_read>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The chat where these messages are located. | ||||
| 
 | ||||
|             message (`list` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 Either a list of messages or a single message. | ||||
| 
 | ||||
|             max_id (`int`): | ||||
|                 Until which message should the read acknowledge be sent for. | ||||
|                 This has priority over the ``message`` parameter. | ||||
| 
 | ||||
|             clear_mentions (`bool`): | ||||
|                 Whether the mention badge should be cleared (so that | ||||
|                 there are no more mentions) or not for the given entity. | ||||
| 
 | ||||
|                 If no message is provided, this will be the only action | ||||
|                 taken. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # using a Message object | ||||
|                 await client.send_read_acknowledge(chat, message) | ||||
|                 # ...or using the int ID of a Message | ||||
|                 await client.send_read_acknowledge(chat, message_id) | ||||
|                 # ...or passing a list of messages to mark as read | ||||
|                 await client.send_read_acknowledge(chat, messages) | ||||
|         """ | ||||
|     if max_id is None: | ||||
|         if not message: | ||||
|             max_id = 0 | ||||
|  | @ -1346,78 +705,26 @@ class MessageMethods: | |||
| 
 | ||||
|     return False | ||||
| 
 | ||||
|     async def pin_message( | ||||
| async def pin_message( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'typing.Optional[hints.MessageIDLike]', | ||||
|         *, | ||||
|         notify: bool = False, | ||||
|         pm_oneside: bool = False | ||||
|     ): | ||||
|         """ | ||||
|         Pins a message in a chat. | ||||
| 
 | ||||
|         The default behaviour is to *not* notify members, unlike the | ||||
|         official applications. | ||||
| 
 | ||||
|         See also `Message.pin() <telethon.tl.custom.message.Message.pin>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The chat where the message should be pinned. | ||||
| 
 | ||||
|             message (`int` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 The message or the message ID to pin. If it's | ||||
|                 `None`, all messages will be unpinned instead. | ||||
| 
 | ||||
|             notify (`bool`, optional): | ||||
|                 Whether the pin should notify people or not. | ||||
| 
 | ||||
|             pm_oneside (`bool`, optional): | ||||
|                 Whether the message should be pinned for everyone or not. | ||||
|                 By default it has the opposite behaviour of official clients, | ||||
|                 and it will pin the message for both sides, in private chats. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Send and pin a message to annoy everyone | ||||
|                 message = await client.send_message(chat, 'Pinotifying is fun!') | ||||
|                 await client.pin_message(chat, message, notify=True) | ||||
|         """ | ||||
| ): | ||||
|     return await self._pin(entity, message, unpin=False, notify=notify, pm_oneside=pm_oneside) | ||||
| 
 | ||||
|     async def unpin_message( | ||||
| async def unpin_message( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         message: 'typing.Optional[hints.MessageIDLike]' = None, | ||||
|         *, | ||||
|         notify: bool = False | ||||
|     ): | ||||
|         """ | ||||
|         Unpins a message in a chat. | ||||
| 
 | ||||
|         If no message ID is specified, all pinned messages will be unpinned. | ||||
| 
 | ||||
|         See also `Message.unpin() <telethon.tl.custom.message.Message.unpin>`. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 The chat where the message should be pinned. | ||||
| 
 | ||||
|             message (`int` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 The message or the message ID to unpin. If it's | ||||
|                 `None`, all messages will be unpinned instead. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Unpin all messages from a chat | ||||
|                 await client.unpin_message(chat) | ||||
|         """ | ||||
| ): | ||||
|     return await self._pin(entity, message, unpin=True, notify=notify) | ||||
| 
 | ||||
|     async def _pin(self, entity, message, *, unpin, notify=False, pm_oneside=False): | ||||
| async def _pin(self, entity, message, *, unpin, notify=False, pm_oneside=False): | ||||
|     message = utils.get_message_id(message) or 0 | ||||
|     entity = await self.get_input_entity(entity) | ||||
|     if message <= 0:  # old behaviour accepted negative IDs to unpin | ||||
|  | @ -1442,7 +749,3 @@ class MessageMethods: | |||
| 
 | ||||
|     # Pinning a message that doesn't exist would RPC-error earlier | ||||
|     return self._get_response_message(request, result, entity) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -64,162 +64,9 @@ class _ExportState: | |||
| 
 | ||||
| 
 | ||||
| # TODO How hard would it be to support both `trio` and `asyncio`? | ||||
| class TelegramBaseClient(abc.ABC): | ||||
|     """ | ||||
|     This is the abstract base class for the client. It defines some | ||||
|     basic stuff like connecting, switching data center, etc, and | ||||
|     leaves the `__call__` unimplemented. | ||||
| 
 | ||||
|     Arguments | ||||
|         session (`str` | `telethon.sessions.abstract.Session`, `None`): | ||||
|             The file name of the session file to be used if a string is | ||||
|             given (it may be a full path), or the Session instance to be | ||||
|             used otherwise. If it's `None`, the session will not be saved, | ||||
|             and you should call :meth:`.log_out()` when you're done. | ||||
| 
 | ||||
|             Note that if you pass a string it will be a file in the current | ||||
|             working directory, although you can also pass absolute paths. | ||||
| 
 | ||||
|             The session file contains enough information for you to login | ||||
|             without re-sending the code, so if you have to enter the code | ||||
|             more than once, maybe you're changing the working directory, | ||||
|             renaming or removing the file, or using random names. | ||||
| 
 | ||||
|         api_id (`int` | `str`): | ||||
|             The API ID you obtained from https://my.telegram.org. | ||||
| 
 | ||||
|         api_hash (`str`): | ||||
|             The API hash you obtained from https://my.telegram.org. | ||||
| 
 | ||||
|         connection (`telethon.network.connection.common.Connection`, optional): | ||||
|             The connection instance to be used when creating a new connection | ||||
|             to the servers. It **must** be a type. | ||||
| 
 | ||||
|             Defaults to `telethon.network.connection.tcpfull.ConnectionTcpFull`. | ||||
| 
 | ||||
|         use_ipv6 (`bool`, optional): | ||||
|             Whether to connect to the servers through IPv6 or not. | ||||
|             By default this is `False` as IPv6 support is not | ||||
|             too widespread yet. | ||||
| 
 | ||||
|         proxy (`tuple` | `list` | `dict`, optional): | ||||
|             An iterable consisting of the proxy info. If `connection` is | ||||
|             one of `MTProxy`, then it should contain MTProxy credentials: | ||||
|             ``('hostname', port, 'secret')``. Otherwise, it's meant to store | ||||
|             function parameters for PySocks, like ``(type, 'hostname', port)``. | ||||
|             See https://github.com/Anorov/PySocks#usage-1 for more. | ||||
| 
 | ||||
|         local_addr (`str` | `tuple`, optional): | ||||
|             Local host address (and port, optionally) used to bind the socket to locally. | ||||
|             You only need to use this if you have multiple network cards and | ||||
|             want to use a specific one. | ||||
| 
 | ||||
|         timeout (`int` | `float`, optional): | ||||
|             The timeout in seconds to be used when connecting. | ||||
|             This is **not** the timeout to be used when ``await``'ing for | ||||
|             invoked requests, and you should use ``asyncio.wait`` or | ||||
|             ``asyncio.wait_for`` for that. | ||||
| 
 | ||||
|         request_retries (`int` | `None`, optional): | ||||
|             How many times a request should be retried. Request are retried | ||||
|             when Telegram is having internal issues (due to either | ||||
|             ``errors.ServerError`` or ``errors.RpcCallFailError``), | ||||
|             when there is a ``errors.FloodWaitError`` less than | ||||
|             `flood_sleep_threshold`, or when there's a migrate error. | ||||
| 
 | ||||
|             May take a negative or `None` value for infinite retries, but | ||||
|             this is not recommended, since some requests can always trigger | ||||
|             a call fail (such as searching for messages). | ||||
| 
 | ||||
|         connection_retries (`int` | `None`, optional): | ||||
|             How many times the reconnection should retry, either on the | ||||
|             initial connection or when Telegram disconnects us. May be | ||||
|             set to a negative or `None` value for infinite retries, but | ||||
|             this is not recommended, since the program can get stuck in an | ||||
|             infinite loop. | ||||
| 
 | ||||
|         retry_delay (`int` | `float`, optional): | ||||
|             The delay in seconds to sleep between automatic reconnections. | ||||
| 
 | ||||
|         auto_reconnect (`bool`, optional): | ||||
|             Whether reconnection should be retried `connection_retries` | ||||
|             times automatically if Telegram disconnects us or not. | ||||
| 
 | ||||
|         sequential_updates (`bool`, optional): | ||||
|             By default every incoming update will create a new task, so | ||||
|             you can handle several updates in parallel. Some scripts need | ||||
|             the order in which updates are processed to be sequential, and | ||||
|             this setting allows them to do so. | ||||
| 
 | ||||
|             If set to `True`, incoming updates will be put in a queue | ||||
|             and processed sequentially. This means your event handlers | ||||
|             should *not* perform long-running operations since new | ||||
|             updates are put inside of an unbounded queue. | ||||
| 
 | ||||
|         flood_sleep_threshold (`int` | `float`, optional): | ||||
|             The threshold below which the library should automatically | ||||
|             sleep on flood wait and slow mode wait errors (inclusive). For instance, if a | ||||
|             ``FloodWaitError`` for 17s occurs and `flood_sleep_threshold` | ||||
|             is 20s, the library will ``sleep`` automatically. If the error | ||||
|             was for 21s, it would ``raise FloodWaitError`` instead. Values | ||||
|             larger than a day (like ``float('inf')``) will be changed to a day. | ||||
| 
 | ||||
|         raise_last_call_error (`bool`, optional): | ||||
|             When API calls fail in a way that causes Telethon to retry | ||||
|             automatically, should the RPC error of the last attempt be raised | ||||
|             instead of a generic ValueError. This is mostly useful for | ||||
|             detecting when Telegram has internal issues. | ||||
| 
 | ||||
|         device_model (`str`, optional): | ||||
|             "Device model" to be sent when creating the initial connection. | ||||
|             Defaults to 'PC (n)bit' derived from ``platform.uname().machine``, or its direct value if unknown. | ||||
| 
 | ||||
|         system_version (`str`, optional): | ||||
|             "System version" to be sent when creating the initial connection. | ||||
|             Defaults to ``platform.uname().release`` stripped of everything ahead of -. | ||||
| 
 | ||||
|         app_version (`str`, optional): | ||||
|             "App version" to be sent when creating the initial connection. | ||||
|             Defaults to `telethon.version.__version__`. | ||||
| 
 | ||||
|         lang_code (`str`, optional): | ||||
|             "Language code" to be sent when creating the initial connection. | ||||
|             Defaults to ``'en'``. | ||||
| 
 | ||||
|         system_lang_code (`str`, optional): | ||||
|             "System lang code"  to be sent when creating the initial connection. | ||||
|             Defaults to `lang_code`. | ||||
| 
 | ||||
|         loop (`asyncio.AbstractEventLoop`, optional): | ||||
|             Asyncio event loop to use. Defaults to `asyncio.get_event_loop()`. | ||||
|             This argument is ignored. | ||||
| 
 | ||||
|         base_logger (`str` | `logging.Logger`, optional): | ||||
|             Base logger name or instance to use. | ||||
|             If a `str` is given, it'll be passed to `logging.getLogger()`. If a | ||||
|             `logging.Logger` is given, it'll be used directly. If something | ||||
|             else or nothing is given, the default logger will be used. | ||||
| 
 | ||||
|         receive_updates (`bool`, optional): | ||||
|             Whether the client will receive updates or not. By default, updates | ||||
|             will be received from Telegram as they occur. | ||||
| 
 | ||||
|             Turning this off means that Telegram will not send updates at all | ||||
|             so event handlers, conversations, and QR login will not work. | ||||
|             However, certain scripts don't need updates, so this will reduce | ||||
|             the amount of bandwidth used. | ||||
|     """ | ||||
| 
 | ||||
|     # Current TelegramClient version | ||||
|     __version__ = version.__version__ | ||||
| 
 | ||||
|     # Cached server configuration (with .dc_options), can be "global" | ||||
|     _config = None | ||||
|     _cdn_config = None | ||||
| 
 | ||||
|     # region Initialization | ||||
| 
 | ||||
|     def __init__( | ||||
| def init( | ||||
|         self: 'TelegramClient', | ||||
|         session: 'typing.Union[str, Session]', | ||||
|         api_id: int, | ||||
|  | @ -245,7 +92,7 @@ class TelegramBaseClient(abc.ABC): | |||
|         loop: asyncio.AbstractEventLoop = None, | ||||
|         base_logger: typing.Union[str, logging.Logger] = None, | ||||
|         receive_updates: bool = True | ||||
|     ): | ||||
| ): | ||||
|     if not api_id or not api_hash: | ||||
|         raise ValueError( | ||||
|             "Your API ID or Hash cannot be empty or None. " | ||||
|  | @ -448,80 +295,22 @@ class TelegramBaseClient(abc.ABC): | |||
|     # A place to store if channels are a megagroup or not (see `edit_admin`) | ||||
|     self._megagroup_cache = {} | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Properties | ||||
| 
 | ||||
|     @property | ||||
|     def loop(self: 'TelegramClient') -> asyncio.AbstractEventLoop: | ||||
|         """ | ||||
|         Property with the ``asyncio`` event loop used by this client. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Download media in the background | ||||
|                 task = client.loop.create_task(message.download_media()) | ||||
| 
 | ||||
|                 # Do some work | ||||
|                 ... | ||||
| 
 | ||||
|                 # Join the task (wait for it to complete) | ||||
|                 await task | ||||
|         """ | ||||
| def get_loop(self: 'TelegramClient') -> asyncio.AbstractEventLoop: | ||||
|     return asyncio.get_event_loop() | ||||
| 
 | ||||
|     @property | ||||
|     def disconnected(self: 'TelegramClient') -> asyncio.Future: | ||||
|         """ | ||||
|         Property with a ``Future`` that resolves upon disconnection. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Wait for a disconnection to occur | ||||
|                 try: | ||||
|                     await client.disconnected | ||||
|                 except OSError: | ||||
|                     print('Error on disconnect') | ||||
|         """ | ||||
| def get_disconnected(self: 'TelegramClient') -> asyncio.Future: | ||||
|     return self._sender.disconnected | ||||
| 
 | ||||
|     @property | ||||
|     def flood_sleep_threshold(self): | ||||
| def get_flood_sleep_threshold(self): | ||||
|     return self._flood_sleep_threshold | ||||
| 
 | ||||
|     @flood_sleep_threshold.setter | ||||
|     def flood_sleep_threshold(self, value): | ||||
| def set_flood_sleep_threshold(self, value): | ||||
|     # None -> 0, negative values don't really matter | ||||
|     self._flood_sleep_threshold = min(value or 0, 24 * 60 * 60) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Connecting | ||||
| 
 | ||||
|     async def connect(self: 'TelegramClient') -> None: | ||||
|         """ | ||||
|         Connects to Telegram. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             Connect means connect and nothing else, and only one low-level | ||||
|             request is made to notify Telegram about which layer we will be | ||||
|             using. | ||||
| 
 | ||||
|             Before Telegram sends you updates, you need to make a high-level | ||||
|             request, like `client.get_me() <telethon.client.users.UserMethods.get_me>`, | ||||
|             as described in https://core.telegram.org/api/updates. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 try: | ||||
|                     await client.connect() | ||||
|                 except OSError: | ||||
|                     print('Failed to connect') | ||||
|         """ | ||||
| async def connect(self: 'TelegramClient') -> None: | ||||
|     if not await self._sender.connect(self._connection( | ||||
|         self.session.server_address, | ||||
|         self.session.port, | ||||
|  | @ -544,35 +333,11 @@ class TelegramBaseClient(abc.ABC): | |||
| 
 | ||||
|     self._updates_handle = self.loop.create_task(self._update_loop()) | ||||
| 
 | ||||
|     def is_connected(self: 'TelegramClient') -> bool: | ||||
|         """ | ||||
|         Returns `True` if the user has connected. | ||||
| 
 | ||||
|         This method is **not** asynchronous (don't use ``await`` on it). | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 while client.is_connected(): | ||||
|                     await asyncio.sleep(1) | ||||
|         """ | ||||
| def is_connected(self: 'TelegramClient') -> bool: | ||||
|     sender = getattr(self, '_sender', None) | ||||
|     return sender and sender.is_connected() | ||||
| 
 | ||||
|     def disconnect(self: 'TelegramClient'): | ||||
|         """ | ||||
|         Disconnects from Telegram. | ||||
| 
 | ||||
|         If the event loop is already running, this method returns a | ||||
|         coroutine that you should await on your own code; otherwise | ||||
|         the loop is ran until said coroutine completes. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # You don't need to use this if you used "with client" | ||||
|                 await client.disconnect() | ||||
|         """ | ||||
| def disconnect(self: 'TelegramClient'): | ||||
|     if self.loop.is_running(): | ||||
|         return self._disconnect_coro() | ||||
|     else: | ||||
|  | @ -586,16 +351,7 @@ class TelegramBaseClient(abc.ABC): | |||
|             # However, it doesn't really make a lot of sense. | ||||
|             pass | ||||
| 
 | ||||
|     def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]): | ||||
|         """ | ||||
|         Changes the proxy which will be used on next (re)connection. | ||||
| 
 | ||||
|         Method has no immediate effects if the client is currently connected. | ||||
| 
 | ||||
|         The new proxy will take it's effect on the next reconnection attempt: | ||||
|             - on a call `await client.connect()` (after complete disconnect) | ||||
|             - on auto-reconnect attempt (e.g, after previous connection was lost) | ||||
|         """ | ||||
| def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]): | ||||
|     init_proxy = None if not issubclass(self._connection, TcpMTProxy) else \ | ||||
|         types.InputClientProxy(*self._connection.address_info(proxy)) | ||||
| 
 | ||||
|  | @ -615,7 +371,7 @@ class TelegramBaseClient(abc.ABC): | |||
|         else: | ||||
|             connection._proxy = proxy | ||||
| 
 | ||||
|     async def _disconnect_coro(self: 'TelegramClient'): | ||||
| async def _disconnect_coro(self: 'TelegramClient'): | ||||
|     await self._disconnect() | ||||
| 
 | ||||
|     # Also clean-up all exported senders because we're done with them | ||||
|  | @ -654,7 +410,7 @@ class TelegramBaseClient(abc.ABC): | |||
| 
 | ||||
|     self.session.close() | ||||
| 
 | ||||
|     async def _disconnect(self: 'TelegramClient'): | ||||
| async def _disconnect(self: 'TelegramClient'): | ||||
|     """ | ||||
|     Disconnect only, without closing the session. Used in reconnections | ||||
|     to different data centers, where we don't want to close the session | ||||
|  | @ -665,7 +421,7 @@ class TelegramBaseClient(abc.ABC): | |||
|     await helpers._cancel(self._log[__name__], | ||||
|                             updates_handle=self._updates_handle) | ||||
| 
 | ||||
|     async def _switch_dc(self: 'TelegramClient', new_dc): | ||||
| async def _switch_dc(self: 'TelegramClient', new_dc): | ||||
|     """ | ||||
|     Permanently switches the current connection to the new data center. | ||||
|     """ | ||||
|  | @ -681,7 +437,7 @@ class TelegramBaseClient(abc.ABC): | |||
|     await self._disconnect() | ||||
|     return await self.connect() | ||||
| 
 | ||||
|     def _auth_key_callback(self: 'TelegramClient', auth_key): | ||||
| def _auth_key_callback(self: 'TelegramClient', auth_key): | ||||
|     """ | ||||
|     Callback from the sender whenever it needed to generate a | ||||
|     new authorization key. This means we are not authorized. | ||||
|  | @ -689,11 +445,8 @@ class TelegramBaseClient(abc.ABC): | |||
|     self.session.auth_key = auth_key | ||||
|     self.session.save() | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Working with different connections/Data Centers | ||||
| 
 | ||||
|     async def _get_dc(self: 'TelegramClient', dc_id, cdn=False): | ||||
| async def _get_dc(self: 'TelegramClient', dc_id, cdn=False): | ||||
|     """Gets the Data Center (DC) associated to 'dc_id'""" | ||||
|     cls = self.__class__ | ||||
|     if not cls._config: | ||||
|  | @ -720,7 +473,7 @@ class TelegramBaseClient(abc.ABC): | |||
|             if dc.id == dc_id and bool(dc.cdn) == cdn | ||||
|         ) | ||||
| 
 | ||||
|     async def _create_exported_sender(self: 'TelegramClient', dc_id): | ||||
| async def _create_exported_sender(self: 'TelegramClient', dc_id): | ||||
|     """ | ||||
|     Creates a new exported `MTProtoSender` for the given `dc_id` and | ||||
|     returns it. This method should be used by `_borrow_exported_sender`. | ||||
|  | @ -748,7 +501,7 @@ class TelegramBaseClient(abc.ABC): | |||
|     await sender.send(req) | ||||
|     return sender | ||||
| 
 | ||||
|     async def _borrow_exported_sender(self: 'TelegramClient', dc_id): | ||||
| async def _borrow_exported_sender(self: 'TelegramClient', dc_id): | ||||
|     """ | ||||
|     Borrows a connected `MTProtoSender` for the given `dc_id`. | ||||
|     If it's not cached, creates a new one if it doesn't exist yet, | ||||
|  | @ -780,7 +533,7 @@ class TelegramBaseClient(abc.ABC): | |||
|         state.add_borrow() | ||||
|         return sender | ||||
| 
 | ||||
|     async def _return_exported_sender(self: 'TelegramClient', sender): | ||||
| async def _return_exported_sender(self: 'TelegramClient', sender): | ||||
|     """ | ||||
|     Returns a borrowed exported sender. If all borrows have | ||||
|     been returned, the sender is cleanly disconnected. | ||||
|  | @ -790,7 +543,7 @@ class TelegramBaseClient(abc.ABC): | |||
|         state, _ = self._borrowed_senders[sender.dc_id] | ||||
|         state.add_return() | ||||
| 
 | ||||
|     async def _clean_exported_senders(self: 'TelegramClient'): | ||||
| async def _clean_exported_senders(self: 'TelegramClient'): | ||||
|     """ | ||||
|     Cleans-up all unused exported senders by disconnecting them. | ||||
|     """ | ||||
|  | @ -804,7 +557,7 @@ class TelegramBaseClient(abc.ABC): | |||
|                 await sender.disconnect() | ||||
|                 state.mark_disconnected() | ||||
| 
 | ||||
|     async def _get_cdn_client(self: 'TelegramClient', cdn_redirect): | ||||
| async def _get_cdn_client(self: 'TelegramClient', cdn_redirect): | ||||
|     """Similar to ._borrow_exported_client, but for CDNs""" | ||||
|     # TODO Implement | ||||
|     raise NotImplementedError | ||||
|  | @ -830,46 +583,19 @@ class TelegramBaseClient(abc.ABC): | |||
|     client.connect(_sync_updates=False) | ||||
|     return client | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Invoking Telegram requests | ||||
| 
 | ||||
|     @abc.abstractmethod | ||||
|     def __call__(self: 'TelegramClient', request, ordered=False): | ||||
|         """ | ||||
|         Invokes (sends) one or more MTProtoRequests and returns (receives) | ||||
|         their result. | ||||
| 
 | ||||
|         Args: | ||||
|             request (`TLObject` | `list`): | ||||
|                 The request or requests to be invoked. | ||||
| 
 | ||||
|             ordered (`bool`, optional): | ||||
|                 Whether the requests (if more than one was given) should be | ||||
|                 executed sequentially on the server. They run in arbitrary | ||||
|                 order by default. | ||||
| 
 | ||||
|             flood_sleep_threshold (`int` | `None`, optional): | ||||
|                 The flood sleep threshold to use for this request. This overrides | ||||
|                 the default value stored in | ||||
|                 `client.flood_sleep_threshold <telethon.client.telegrambaseclient.TelegramBaseClient.flood_sleep_threshold>` | ||||
| 
 | ||||
|         Returns: | ||||
|             The result of the request (often a `TLObject`) or a list of | ||||
|             results if more than one request was given. | ||||
|         """ | ||||
| @abc.abstractmethod | ||||
| def __call__(self: 'TelegramClient', request, ordered=False): | ||||
|     raise NotImplementedError | ||||
| 
 | ||||
|     @abc.abstractmethod | ||||
|     def _handle_update(self: 'TelegramClient', update): | ||||
| @abc.abstractmethod | ||||
| def _handle_update(self: 'TelegramClient', update): | ||||
|     raise NotImplementedError | ||||
| 
 | ||||
|     @abc.abstractmethod | ||||
|     def _update_loop(self: 'TelegramClient'): | ||||
| @abc.abstractmethod | ||||
| def _update_loop(self: 'TelegramClient'): | ||||
|     raise NotImplementedError | ||||
| 
 | ||||
|     @abc.abstractmethod | ||||
|     async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||
| @abc.abstractmethod | ||||
| async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||
|     raise NotImplementedError | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -18,11 +18,8 @@ if typing.TYPE_CHECKING: | |||
| 
 | ||||
| Callback = typing.Callable[[typing.Any], typing.Any] | ||||
| 
 | ||||
| class UpdateMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     async def _run_until_disconnected(self: 'TelegramClient'): | ||||
| async def _run_until_disconnected(self: 'TelegramClient'): | ||||
|     try: | ||||
|         # Make a high-level request to notify that we want updates | ||||
|         await self(functions.updates.GetStateRequest()) | ||||
|  | @ -32,7 +29,7 @@ class UpdateMethods: | |||
|     finally: | ||||
|         await self.disconnect() | ||||
| 
 | ||||
|     async def set_receive_updates(self: 'TelegramClient', receive_updates): | ||||
| async def set_receive_updates(self: 'TelegramClient', receive_updates): | ||||
|     """ | ||||
|     Change the value of `receive_updates`. | ||||
| 
 | ||||
|  | @ -43,7 +40,7 @@ class UpdateMethods: | |||
|     if receive_updates: | ||||
|         await self(functions.updates.GetStateRequest()) | ||||
| 
 | ||||
|     def run_until_disconnected(self: 'TelegramClient'): | ||||
| def run_until_disconnected(self: 'TelegramClient'): | ||||
|     """ | ||||
|     Runs the event loop until the library is disconnected. | ||||
| 
 | ||||
|  | @ -88,7 +85,7 @@ class UpdateMethods: | |||
|         # No loop.run_until_complete; it's already syncified | ||||
|         self.disconnect() | ||||
| 
 | ||||
|     def on(self: 'TelegramClient', event: EventBuilder): | ||||
| def on(self: 'TelegramClient', event: EventBuilder): | ||||
|     """ | ||||
|     Decorator used to `add_event_handler` more conveniently. | ||||
| 
 | ||||
|  | @ -115,7 +112,7 @@ class UpdateMethods: | |||
| 
 | ||||
|     return decorator | ||||
| 
 | ||||
|     def add_event_handler( | ||||
| def add_event_handler( | ||||
|         self: 'TelegramClient', | ||||
|         callback: Callback, | ||||
|         event: EventBuilder = None): | ||||
|  | @ -164,7 +161,7 @@ class UpdateMethods: | |||
| 
 | ||||
|     self._event_builders.append((event, callback)) | ||||
| 
 | ||||
|     def remove_event_handler( | ||||
| def remove_event_handler( | ||||
|         self: 'TelegramClient', | ||||
|         callback: Callback, | ||||
|         event: EventBuilder = None) -> int: | ||||
|  | @ -203,7 +200,7 @@ class UpdateMethods: | |||
| 
 | ||||
|     return found | ||||
| 
 | ||||
|     def list_event_handlers(self: 'TelegramClient')\ | ||||
| def list_event_handlers(self: 'TelegramClient')\ | ||||
|         -> 'typing.Sequence[typing.Tuple[Callback, EventBuilder]]': | ||||
|     """ | ||||
|     Lists all registered event handlers. | ||||
|  | @ -224,7 +221,7 @@ class UpdateMethods: | |||
|     """ | ||||
|     return [(callback, event) for event, callback in self._event_builders] | ||||
| 
 | ||||
|     async def catch_up(self: 'TelegramClient'): | ||||
| async def catch_up(self: 'TelegramClient'): | ||||
|     """ | ||||
|     "Catches up" on the missed updates while the client was offline. | ||||
|     You should call this method after registering the event handlers | ||||
|  | @ -293,14 +290,11 @@ class UpdateMethods: | |||
|         self._state_cache._pts_date = (pts, date) | ||||
|         self.session.catching_up = False | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Private methods | ||||
| 
 | ||||
|     # It is important to not make _handle_update async because we rely on | ||||
|     # the order that the updates arrive in to update the pts and date to | ||||
|     # be always-increasing. There is also no need to make this async. | ||||
|     def _handle_update(self: 'TelegramClient', update): | ||||
| # It is important to not make _handle_update async because we rely on | ||||
| # the order that the updates arrive in to update the pts and date to | ||||
| # be always-increasing. There is also no need to make this async. | ||||
| def _handle_update(self: 'TelegramClient', update): | ||||
|     self.session.process_entities(update) | ||||
|     self._entity_cache.add(update) | ||||
| 
 | ||||
|  | @ -316,7 +310,7 @@ class UpdateMethods: | |||
| 
 | ||||
|     self._state_cache.update(update) | ||||
| 
 | ||||
|     def _process_update(self: 'TelegramClient', update, others, entities=None): | ||||
| def _process_update(self: 'TelegramClient', update, others, entities=None): | ||||
|     update._entities = entities or {} | ||||
| 
 | ||||
|     # This part is somewhat hot so we don't bother patching | ||||
|  | @ -336,7 +330,7 @@ class UpdateMethods: | |||
| 
 | ||||
|     self._state_cache.update(update) | ||||
| 
 | ||||
|     async def _update_loop(self: 'TelegramClient'): | ||||
| async def _update_loop(self: 'TelegramClient'): | ||||
|     # Pings' ID don't really need to be secure, just "random" | ||||
|     rnd = lambda: random.randrange(-2**63, 2**63) | ||||
|     while self.is_connected(): | ||||
|  | @ -390,13 +384,13 @@ class UpdateMethods: | |||
|             except (ConnectionError, asyncio.CancelledError): | ||||
|                 return | ||||
| 
 | ||||
|     async def _dispatch_queue_updates(self: 'TelegramClient'): | ||||
| async def _dispatch_queue_updates(self: 'TelegramClient'): | ||||
|     while not self._updates_queue.empty(): | ||||
|         await self._dispatch_update(*self._updates_queue.get_nowait()) | ||||
| 
 | ||||
|     self._dispatching_updates_queue.clear() | ||||
| 
 | ||||
|     async def _dispatch_update(self: 'TelegramClient', update, others, channel_id, pts_date): | ||||
| async def _dispatch_update(self: 'TelegramClient', update, others, channel_id, pts_date): | ||||
|     if not self._entity_cache.ensure_cached(update): | ||||
|         # We could add a lock to not fetch the same pts twice if we are | ||||
|         # already fetching it. However this does not happen in practice, | ||||
|  | @ -482,7 +476,7 @@ class UpdateMethods: | |||
|                 name = getattr(callback, '__name__', repr(callback)) | ||||
|                 self._log[__name__].exception('Unhandled exception on %s', name) | ||||
| 
 | ||||
|     async def _dispatch_event(self: 'TelegramClient', event): | ||||
| async def _dispatch_event(self: 'TelegramClient', event): | ||||
|     """ | ||||
|     Dispatches a single, out-of-order event. Used by `AlbumHack`. | ||||
|     """ | ||||
|  | @ -523,7 +517,7 @@ class UpdateMethods: | |||
|                 name = getattr(callback, '__name__', repr(callback)) | ||||
|                 self._log[__name__].exception('Unhandled exception on %s', name) | ||||
| 
 | ||||
|     async def _get_difference(self: 'TelegramClient', update, channel_id, pts_date): | ||||
| async def _get_difference(self: 'TelegramClient', update, channel_id, pts_date): | ||||
|     """ | ||||
|     Get the difference for this `channel_id` if any, then load entities. | ||||
| 
 | ||||
|  | @ -584,7 +578,7 @@ class UpdateMethods: | |||
|             itertools.chain(result.users, result.chats) | ||||
|         }) | ||||
| 
 | ||||
|     async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||
| async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||
|     # TODO Catch-up | ||||
|     # For now we make a high-level request to let Telegram | ||||
|     # know we are still interested in receiving more updates. | ||||
|  | @ -627,8 +621,6 @@ class UpdateMethods: | |||
|         self._log[__name__].exception( | ||||
|             'Unhandled exception while getting update difference after reconnect') | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
| 
 | ||||
| class EventBuilderDict: | ||||
|     """ | ||||
|  |  | |||
|  | @ -88,11 +88,7 @@ def _resize_photo_if_needed( | |||
|             file.seek(before, io.SEEK_SET) | ||||
| 
 | ||||
| 
 | ||||
| class UploadMethods: | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     async def send_file( | ||||
| async def send_file( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntityLike', | ||||
|         file: 'typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]', | ||||
|  | @ -118,223 +114,6 @@ class UploadMethods: | |||
|         comment_to: 'typing.Union[int, types.Message]' = None, | ||||
|         ttl: int = None, | ||||
|         **kwargs) -> 'types.Message': | ||||
|         """ | ||||
|         Sends message with the given file to the specified entity. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             If the ``hachoir3`` package (``hachoir`` module) is installed, | ||||
|             it will be used to determine metadata from audio and video files. | ||||
| 
 | ||||
|             If the ``pillow`` package is installed and you are sending a photo, | ||||
|             it will be resized to fit within the maximum dimensions allowed | ||||
|             by Telegram to avoid ``errors.PhotoInvalidDimensionsError``. This | ||||
|             cannot be done if you are sending :tl:`InputFile`, however. | ||||
| 
 | ||||
|         Arguments | ||||
|             entity (`entity`): | ||||
|                 Who will receive the file. | ||||
| 
 | ||||
|             file (`str` | `bytes` | `file` | `media`): | ||||
|                 The file to send, which can be one of: | ||||
| 
 | ||||
|                 * A local file path to an in-disk file. The file name | ||||
|                   will be the path's base name. | ||||
| 
 | ||||
|                 * A `bytes` byte array with the file's data to send | ||||
|                   (for example, by using ``text.encode('utf-8')``). | ||||
|                   A default file name will be used. | ||||
| 
 | ||||
|                 * A bytes `io.IOBase` stream over the file to send | ||||
|                   (for example, by using ``open(file, 'rb')``). | ||||
|                   Its ``.name`` property will be used for the file name, | ||||
|                   or a default if it doesn't have one. | ||||
| 
 | ||||
|                 * An external URL to a file over the internet. This will | ||||
|                   send the file as "external" media, and Telegram is the | ||||
|                   one that will fetch the media and send it. | ||||
| 
 | ||||
|                 * A Bot API-like ``file_id``. You can convert previously | ||||
|                   sent media to file IDs for later reusing with | ||||
|                   `telethon.utils.pack_bot_file_id`. | ||||
| 
 | ||||
|                 * A handle to an existing file (for example, if you sent a | ||||
|                   message with media before, you can use its ``message.media`` | ||||
|                   as a file here). | ||||
| 
 | ||||
|                 * A handle to an uploaded file (from `upload_file`). | ||||
| 
 | ||||
|                 * A :tl:`InputMedia` instance. For example, if you want to | ||||
|                   send a dice use :tl:`InputMediaDice`, or if you want to | ||||
|                   send a contact use :tl:`InputMediaContact`. | ||||
| 
 | ||||
|                 To send an album, you should provide a list in this parameter. | ||||
| 
 | ||||
|                 If a list or similar is provided, the files in it will be | ||||
|                 sent as an album in the order in which they appear, sliced | ||||
|                 in chunks of 10 if more than 10 are given. | ||||
| 
 | ||||
|             caption (`str`, optional): | ||||
|                 Optional caption for the sent media message. When sending an | ||||
|                 album, the caption may be a list of strings, which will be | ||||
|                 assigned to the files pairwise. | ||||
| 
 | ||||
|             force_document (`bool`, optional): | ||||
|                 If left to `False` and the file is a path that ends with | ||||
|                 the extension of an image file or a video file, it will be | ||||
|                 sent as such. Otherwise always as a document. | ||||
| 
 | ||||
|             file_size (`int`, optional): | ||||
|                 The size of the file to be uploaded if it needs to be uploaded, | ||||
|                 which will be determined automatically if not specified. | ||||
| 
 | ||||
|                 If the file size can't be determined beforehand, the entire | ||||
|                 file will be read in-memory to find out how large it is. | ||||
| 
 | ||||
|             clear_draft (`bool`, optional): | ||||
|                 Whether the existing draft should be cleared or not. | ||||
| 
 | ||||
|             progress_callback (`callable`, optional): | ||||
|                 A callback function accepting two parameters: | ||||
|                 ``(sent bytes, total)``. | ||||
| 
 | ||||
|             reply_to (`int` | `Message <telethon.tl.custom.message.Message>`): | ||||
|                 Same as `reply_to` from `send_message`. | ||||
| 
 | ||||
|             attributes (`list`, optional): | ||||
|                 Optional attributes that override the inferred ones, like | ||||
|                 :tl:`DocumentAttributeFilename` and so on. | ||||
| 
 | ||||
|             thumb (`str` | `bytes` | `file`, optional): | ||||
|                 Optional JPEG thumbnail (for documents). **Telegram will | ||||
|                 ignore this parameter** unless you pass a ``.jpg`` file! | ||||
| 
 | ||||
|                 The file must also be small in dimensions and in disk size. | ||||
|                 Successful thumbnails were files below 20kB and 320x320px. | ||||
|                 Width/height and dimensions/size ratios may be important. | ||||
|                 For Telegram to accept a thumbnail, you must provide the | ||||
|                 dimensions of the underlying media through ``attributes=`` | ||||
|                 with :tl:`DocumentAttributesVideo` or by installing the | ||||
|                 optional ``hachoir`` dependency. | ||||
| 
 | ||||
| 
 | ||||
|             allow_cache (`bool`, optional): | ||||
|                 This parameter currently does nothing, but is kept for | ||||
|                 backward-compatibility (and it may get its use back in | ||||
|                 the future). | ||||
| 
 | ||||
|             parse_mode (`object`, optional): | ||||
|                 See the `TelegramClient.parse_mode | ||||
|                 <telethon.client.messageparse.MessageParseMethods.parse_mode>` | ||||
|                 property for allowed values. Markdown parsing will be used by | ||||
|                 default. | ||||
| 
 | ||||
|             formatting_entities (`list`, optional): | ||||
|                 A list of message formatting entities. When provided, the ``parse_mode`` is ignored. | ||||
| 
 | ||||
|             voice_note (`bool`, optional): | ||||
|                 If `True` the audio will be sent as a voice note. | ||||
| 
 | ||||
|             video_note (`bool`, optional): | ||||
|                 If `True` the video will be sent as a video note, | ||||
|                 also known as a round video message. | ||||
| 
 | ||||
|             buttons (`list`, `custom.Button <telethon.tl.custom.button.Button>`, :tl:`KeyboardButton`): | ||||
|                 The matrix (list of lists), row list or button to be shown | ||||
|                 after sending the message. This parameter will only work if | ||||
|                 you have signed in as a bot. You can also pass your own | ||||
|                 :tl:`ReplyMarkup` here. | ||||
| 
 | ||||
|             silent (`bool`, optional): | ||||
|                 Whether the message should notify people with sound or not. | ||||
|                 Defaults to `False` (send with a notification sound unless | ||||
|                 the person has the chat muted). Set it to `True` to alter | ||||
|                 this behaviour. | ||||
| 
 | ||||
|             background (`bool`, optional): | ||||
|                 Whether the message should be send in background. | ||||
| 
 | ||||
|             supports_streaming (`bool`, optional): | ||||
|                 Whether the sent video supports streaming or not. Note that | ||||
|                 Telegram only recognizes as streamable some formats like MP4, | ||||
|                 and others like AVI or MKV will not work. You should convert | ||||
|                 these to MP4 before sending if you want them to be streamable. | ||||
|                 Unsupported formats will result in ``VideoContentTypeError``. | ||||
| 
 | ||||
|             schedule (`hints.DateLike`, optional): | ||||
|                 If set, the file won't send immediately, and instead | ||||
|                 it will be scheduled to be automatically sent at a later | ||||
|                 time. | ||||
| 
 | ||||
|             comment_to (`int` | `Message <telethon.tl.custom.message.Message>`, optional): | ||||
|                 Similar to ``reply_to``, but replies in the linked group of a | ||||
|                 broadcast channel instead (effectively leaving a "comment to" | ||||
|                 the specified message). | ||||
| 
 | ||||
|                 This parameter takes precedence over ``reply_to``. If there is | ||||
|                 no linked chat, `telethon.errors.sgIdInvalidError` is raised. | ||||
| 
 | ||||
|             ttl (`int`. optional): | ||||
|                 The Time-To-Live of the file (also known as "self-destruct timer" | ||||
|                 or "self-destructing media"). If set, files can only be viewed for | ||||
|                 a short period of time before they disappear from the message | ||||
|                 history automatically. | ||||
| 
 | ||||
|                 The value must be at least 1 second, and at most 60 seconds, | ||||
|                 otherwise Telegram will ignore this parameter. | ||||
| 
 | ||||
|                 Not all types of media can be used with this parameter, such | ||||
|                 as text documents, which will fail with ``TtlMediaInvalidError``. | ||||
| 
 | ||||
|         Returns | ||||
|             The `Message <telethon.tl.custom.message.Message>` (or messages) | ||||
|             containing the sent file, or messages if a list of them was passed. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Normal files like photos | ||||
|                 await client.send_file(chat, '/my/photos/me.jpg', caption="It's me!") | ||||
|                 # or | ||||
|                 await client.send_message(chat, "It's me!", file='/my/photos/me.jpg') | ||||
| 
 | ||||
|                 # Voice notes or round videos | ||||
|                 await client.send_file(chat, '/my/songs/song.mp3', voice_note=True) | ||||
|                 await client.send_file(chat, '/my/videos/video.mp4', video_note=True) | ||||
| 
 | ||||
|                 # Custom thumbnails | ||||
|                 await client.send_file(chat, '/my/documents/doc.txt', thumb='photo.jpg') | ||||
| 
 | ||||
|                 # Only documents | ||||
|                 await client.send_file(chat, '/my/photos/photo.png', force_document=True) | ||||
| 
 | ||||
|                 # Albums | ||||
|                 await client.send_file(chat, [ | ||||
|                     '/my/photos/holiday1.jpg', | ||||
|                     '/my/photos/holiday2.jpg', | ||||
|                     '/my/drawings/portrait.png' | ||||
|                 ]) | ||||
| 
 | ||||
|                 # Printing upload progress | ||||
|                 def callback(current, total): | ||||
|                     print('Uploaded', current, 'out of', total, | ||||
|                           'bytes: {:.2%}'.format(current / total)) | ||||
| 
 | ||||
|                 await client.send_file(chat, file, progress_callback=callback) | ||||
| 
 | ||||
|                 # Dices, including dart and other future emoji | ||||
|                 from telethon.tl import types | ||||
|                 await client.send_file(chat, types.InputMediaDice('')) | ||||
|                 await client.send_file(chat, types.InputMediaDice('🎯')) | ||||
| 
 | ||||
|                 # Contacts | ||||
|                 await client.send_file(chat, types.InputMediaContact( | ||||
|                     phone_number='+34 123 456 789', | ||||
|                     first_name='Example', | ||||
|                     last_name='', | ||||
|                     vcard='' | ||||
|                 )) | ||||
|         """ | ||||
|     # TODO Properly implement allow_cache to reuse the sha256 of the file | ||||
|     # i.e. `None` was used | ||||
|     if not file: | ||||
|  | @ -411,7 +190,7 @@ class UploadMethods: | |||
|     ) | ||||
|     return self._get_response_message(request, await self(request), entity) | ||||
| 
 | ||||
|     async def _send_album(self: 'TelegramClient', entity, files, caption='', | ||||
| async def _send_album(self: 'TelegramClient', entity, files, caption='', | ||||
|                         progress_callback=None, reply_to=None, | ||||
|                         parse_mode=(), silent=None, schedule=None, | ||||
|                         supports_streaming=None, clear_draft=None, | ||||
|  | @ -482,7 +261,7 @@ class UploadMethods: | |||
|     random_ids = [m.random_id for m in media] | ||||
|     return self._get_response_message(random_ids, result, entity) | ||||
| 
 | ||||
|     async def upload_file( | ||||
| async def upload_file( | ||||
|         self: 'TelegramClient', | ||||
|         file: 'hints.FileLike', | ||||
|         *, | ||||
|  | @ -493,80 +272,6 @@ class UploadMethods: | |||
|         key: bytes = None, | ||||
|         iv: bytes = None, | ||||
|         progress_callback: 'hints.ProgressCallback' = None) -> 'types.TypeInputFile': | ||||
|         """ | ||||
|         Uploads a file to Telegram's servers, without sending it. | ||||
| 
 | ||||
|         .. note:: | ||||
| 
 | ||||
|             Generally, you want to use `send_file` instead. | ||||
| 
 | ||||
|         This method returns a handle (an instance of :tl:`InputFile` or | ||||
|         :tl:`InputFileBig`, as required) which can be later used before | ||||
|         it expires (they are usable during less than a day). | ||||
| 
 | ||||
|         Uploading a file will simply return a "handle" to the file stored | ||||
|         remotely in the Telegram servers, which can be later used on. This | ||||
|         will **not** upload the file to your own chat or any chat at all. | ||||
| 
 | ||||
|         Arguments | ||||
|             file (`str` | `bytes` | `file`): | ||||
|                 The path of the file, byte array, or stream that will be sent. | ||||
|                 Note that if a byte array or a stream is given, a filename | ||||
|                 or its type won't be inferred, and it will be sent as an | ||||
|                 "unnamed application/octet-stream". | ||||
| 
 | ||||
|             part_size_kb (`int`, optional): | ||||
|                 Chunk size when uploading files. The larger, the less | ||||
|                 requests will be made (up to 512KB maximum). | ||||
| 
 | ||||
|             file_size (`int`, optional): | ||||
|                 The size of the file to be uploaded, which will be determined | ||||
|                 automatically if not specified. | ||||
| 
 | ||||
|                 If the file size can't be determined beforehand, the entire | ||||
|                 file will be read in-memory to find out how large it is. | ||||
| 
 | ||||
|             file_name (`str`, optional): | ||||
|                 The file name which will be used on the resulting InputFile. | ||||
|                 If not specified, the name will be taken from the ``file`` | ||||
|                 and if this is not a `str`, it will be ``"unnamed"``. | ||||
| 
 | ||||
|             use_cache (`type`, optional): | ||||
|                 This parameter currently does nothing, but is kept for | ||||
|                 backward-compatibility (and it may get its use back in | ||||
|                 the future). | ||||
| 
 | ||||
|             key ('bytes', optional): | ||||
|                 In case of an encrypted upload (secret chats) a key is supplied | ||||
| 
 | ||||
|             iv ('bytes', optional): | ||||
|                 In case of an encrypted upload (secret chats) an iv is supplied | ||||
| 
 | ||||
|             progress_callback (`callable`, optional): | ||||
|                 A callback function accepting two parameters: | ||||
|                 ``(sent bytes, total)``. | ||||
| 
 | ||||
|         Returns | ||||
|             :tl:`InputFileBig` if the file size is larger than 10MB, | ||||
|             `InputSizedFile <telethon.tl.custom.inputsizedfile.InputSizedFile>` | ||||
|             (subclass of :tl:`InputFile`) otherwise. | ||||
| 
 | ||||
|         Example | ||||
|             .. code-block:: python | ||||
| 
 | ||||
|                 # Photos as photo and document | ||||
|                 file = await client.upload_file('photo.jpg') | ||||
|                 await client.send_file(chat, file)                       # sends as photo | ||||
|                 await client.send_file(chat, file, force_document=True)  # sends as document | ||||
| 
 | ||||
|                 file.name = 'not a photo.jpg' | ||||
|                 await client.send_file(chat, file, force_document=True)  # document, new name | ||||
| 
 | ||||
|                 # As song or as voice note | ||||
|                 file = await client.upload_file('song.ogg') | ||||
|                 await client.send_file(chat, file)                   # sends as song | ||||
|                 await client.send_file(chat, file, voice_note=True)  # sends as voice note | ||||
|         """ | ||||
|     if isinstance(file, (types.InputFile, types.InputFileBig)): | ||||
|         return file  # Already uploaded | ||||
| 
 | ||||
|  | @ -661,9 +366,8 @@ class UploadMethods: | |||
|             file_id, part_count, file_name, md5=hash_md5, size=file_size | ||||
|         ) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     async def _file_to_media( | ||||
| async def _file_to_media( | ||||
|         self, file, force_document=False, file_size=None, | ||||
|         progress_callback=None, attributes=None, thumb=None, | ||||
|         allow_cache=True, voice_note=False, video_note=False, | ||||
|  | @ -762,5 +466,3 @@ class UploadMethods: | |||
|             ttl_seconds=ttl | ||||
|         ) | ||||
|     return file_handle, media, as_image | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
|  | @ -25,11 +25,7 @@ def _fmt_flood(delay, request, *, early=False, td=datetime.timedelta): | |||
|     ) | ||||
| 
 | ||||
| 
 | ||||
| class UserMethods: | ||||
|     async def __call__(self: 'TelegramClient', request, ordered=False, flood_sleep_threshold=None): | ||||
|         return await self._call(self._sender, request, ordered=ordered) | ||||
| 
 | ||||
|     async def _call(self: 'TelegramClient', sender, request, ordered=False, flood_sleep_threshold=None): | ||||
| async def call(self: 'TelegramClient', sender, request, ordered=False, flood_sleep_threshold=None): | ||||
|     if flood_sleep_threshold is None: | ||||
|         flood_sleep_threshold = self.flood_sleep_threshold | ||||
|     requests = (request if utils.is_list_like(request) else (request,)) | ||||
|  | @ -130,9 +126,8 @@ class UserMethods: | |||
|     raise ValueError('Request was unsuccessful {} time(s)' | ||||
|                         .format(attempt)) | ||||
| 
 | ||||
|     # region Public methods | ||||
| 
 | ||||
|     async def get_me(self: 'TelegramClient', input_peer: bool = False) \ | ||||
| async def get_me(self: 'TelegramClient', input_peer: bool = False) \ | ||||
|         -> 'typing.Union[types.User, types.InputPeerUser]': | ||||
|     """ | ||||
|     Gets "me", the current :tl:`User` who is logged in. | ||||
|  | @ -171,8 +166,7 @@ class UserMethods: | |||
|     except errors.UnauthorizedError: | ||||
|         return None | ||||
| 
 | ||||
|     @property | ||||
|     def _self_id(self: 'TelegramClient') -> typing.Optional[int]: | ||||
| def _self_id(self: 'TelegramClient') -> typing.Optional[int]: | ||||
|     """ | ||||
|     Returns the ID of the logged-in user, if known. | ||||
| 
 | ||||
|  | @ -181,7 +175,7 @@ class UserMethods: | |||
|     """ | ||||
|     return self._self_input_peer.user_id if self._self_input_peer else None | ||||
| 
 | ||||
|     async def is_bot(self: 'TelegramClient') -> bool: | ||||
| async def is_bot(self: 'TelegramClient') -> bool: | ||||
|     """ | ||||
|     Return `True` if the signed-in user is a bot, `False` otherwise. | ||||
| 
 | ||||
|  | @ -198,7 +192,7 @@ class UserMethods: | |||
| 
 | ||||
|     return self._bot | ||||
| 
 | ||||
|     async def is_user_authorized(self: 'TelegramClient') -> bool: | ||||
| async def is_user_authorized(self: 'TelegramClient') -> bool: | ||||
|     """ | ||||
|     Returns `True` if the user is authorized (logged in). | ||||
| 
 | ||||
|  | @ -220,7 +214,7 @@ class UserMethods: | |||
| 
 | ||||
|     return self._authorized | ||||
| 
 | ||||
|     async def get_entity( | ||||
| async def get_entity( | ||||
|         self: 'TelegramClient', | ||||
|         entity: 'hints.EntitiesLike') -> 'hints.Entity': | ||||
|     """ | ||||
|  | @ -343,7 +337,7 @@ class UserMethods: | |||
| 
 | ||||
|     return result[0] if single else result | ||||
| 
 | ||||
|     async def get_input_entity( | ||||
| async def get_input_entity( | ||||
|         self: 'TelegramClient', | ||||
|         peer: 'hints.EntityLike') -> 'types.TypeInputPeer': | ||||
|     """ | ||||
|  | @ -470,11 +464,11 @@ class UserMethods: | |||
|         .format(peer, type(peer).__name__) | ||||
|     ) | ||||
| 
 | ||||
|     async def _get_peer(self: 'TelegramClient', peer: 'hints.EntityLike'): | ||||
| async def _get_peer(self: 'TelegramClient', peer: 'hints.EntityLike'): | ||||
|     i, cls = utils.resolve_id(await self.get_peer_id(peer)) | ||||
|     return cls(i) | ||||
| 
 | ||||
|     async def get_peer_id( | ||||
| async def get_peer_id( | ||||
|         self: 'TelegramClient', | ||||
|         peer: 'hints.EntityLike', | ||||
|         add_mark: bool = True) -> int: | ||||
|  | @ -507,11 +501,8 @@ class UserMethods: | |||
| 
 | ||||
|     return utils.get_peer_id(peer, add_mark=add_mark) | ||||
| 
 | ||||
|     # endregion | ||||
| 
 | ||||
|     # region Private methods | ||||
| 
 | ||||
|     async def _get_entity_from_string(self: 'TelegramClient', string): | ||||
| async def _get_entity_from_string(self: 'TelegramClient', string): | ||||
|     """ | ||||
|     Gets a full entity from the given string, which may be a phone or | ||||
|     a username, and processes all the found entities on the session. | ||||
|  | @ -575,7 +566,7 @@ class UserMethods: | |||
|         'Cannot find any entity corresponding to "{}"'.format(string) | ||||
|     ) | ||||
| 
 | ||||
|     async def _get_input_dialog(self: 'TelegramClient', dialog): | ||||
| async def _get_input_dialog(self: 'TelegramClient', dialog): | ||||
|     """ | ||||
|     Returns a :tl:`InputDialogPeer`. This is a bit tricky because | ||||
|     it may or not need access to the client to convert what's given | ||||
|  | @ -592,7 +583,7 @@ class UserMethods: | |||
| 
 | ||||
|     return types.InputDialogPeer(await self.get_input_entity(dialog)) | ||||
| 
 | ||||
|     async def _get_input_notify(self: 'TelegramClient', notify): | ||||
| async def _get_input_notify(self: 'TelegramClient', notify): | ||||
|     """ | ||||
|     Returns a :tl:`InputNotifyPeer`. This is a bit tricky because | ||||
|     it may or not need access to the client to convert what's given | ||||
|  | @ -607,5 +598,3 @@ class UserMethods: | |||
|         pass | ||||
| 
 | ||||
|     return types.InputNotifyPeer(await self.get_input_entity(notify)) | ||||
| 
 | ||||
|     # endregion | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user