mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-30 23:47:33 +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 | User, chat and channel identifiers are now 64-bit numbers | ||||||
| --------------------------------------------------------- | --------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
| `Layer 133 <https://diff.telethon.dev/?from=132&to=133>`__ changed *a lot* of | `Layer 133 <https://diff.telethon.dev/?from=132&to=133>`__ changed *a lot* of identifiers from | ||||||
| identifiers from ``int`` to ``long``, meaning they will no longer fit in 32 | ``int`` to ``long``, meaning they will no longer fit in 32 bits, and instead require 64 bits. | ||||||
| bits, and instead require 64 bits. |  | ||||||
| 
 | 
 | ||||||
| If you were storing these identifiers somewhere size did matter (for example, | If you were storing these identifiers somewhere size did matter (for example, a database), you | ||||||
| a database), you will need to migrate that to support the new size requirement | will need to migrate that to support the new size requirement of 8 bytes. | ||||||
| of 8 bytes. |  | ||||||
| 
 | 
 | ||||||
| For the full list of types changed, please review the above link. | 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 .network import connection | ||||||
| from .tl import types, functions, custom | from .tl import types, functions, custom | ||||||
| from .tl.custom import Button | from .tl.custom import Button | ||||||
|  |  | ||||||
|  | @ -107,8 +107,7 @@ class _TakeoutClient: | ||||||
|         return setattr(self.__client, name, value) |         return setattr(self.__client, name, value) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AccountMethods: | def takeout( | ||||||
|     def takeout( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         finalize: bool = True, |         finalize: bool = True, | ||||||
|         *, |         *, | ||||||
|  | @ -119,87 +118,6 @@ class AccountMethods: | ||||||
|         channels: bool = None, |         channels: bool = None, | ||||||
|         files: bool = None, |         files: bool = None, | ||||||
|         max_file_size: bool = None) -> 'TelegramClient': |         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( |     request_kwargs = dict( | ||||||
|         contacts=contacts, |         contacts=contacts, | ||||||
|         message_users=users, |         message_users=users, | ||||||
|  | @ -219,22 +137,7 @@ class AccountMethods: | ||||||
| 
 | 
 | ||||||
|     return _TakeoutClient(finalize, self, request) |     return _TakeoutClient(finalize, self, request) | ||||||
| 
 | 
 | ||||||
|     async def end_takeout(self: 'TelegramClient', success: bool) -> bool: | 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) |  | ||||||
|         """ |  | ||||||
|     try: |     try: | ||||||
|         async with _TakeoutClient(True, self, None) as takeout: |         async with _TakeoutClient(True, self, None) as takeout: | ||||||
|             takeout.success = success |             takeout.success = success | ||||||
|  |  | ||||||
|  | @ -12,11 +12,7 @@ if typing.TYPE_CHECKING: | ||||||
|     from .telegramclient import TelegramClient |     from .telegramclient import TelegramClient | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class AuthMethods: | def start( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     def start( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         phone: typing.Callable[[], str] = lambda: input('Please enter your phone (or bot token): '), |         phone: typing.Callable[[], str] = lambda: input('Please enter your phone (or bot token): '), | ||||||
|         password: typing.Callable[[], str] = lambda: getpass.getpass('Please enter your password: '), |         password: typing.Callable[[], str] = lambda: getpass.getpass('Please enter your password: '), | ||||||
|  | @ -27,81 +23,6 @@ class AuthMethods: | ||||||
|         first_name: str = 'New User', |         first_name: str = 'New User', | ||||||
|         last_name: str = '', |         last_name: str = '', | ||||||
|         max_attempts: int = 3) -> 'TelegramClient': |         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: |     if code_callback is None: | ||||||
|         def code_callback(): |         def code_callback(): | ||||||
|             return input('Please enter the code you received: ') |             return input('Please enter the code you received: ') | ||||||
|  | @ -133,7 +54,7 @@ class AuthMethods: | ||||||
|         else self.loop.run_until_complete(coro) |         else self.loop.run_until_complete(coro) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def _start( | async def _start( | ||||||
|         self: 'TelegramClient', phone, password, bot_token, force_sms, |         self: 'TelegramClient', phone, password, bot_token, force_sms, | ||||||
|         code_callback, first_name, last_name, max_attempts): |         code_callback, first_name, last_name, max_attempts): | ||||||
|     if not self.is_connected(): |     if not self.is_connected(): | ||||||
|  | @ -261,7 +182,7 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return self |     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. |     Helper method to both parse and validate phone and its hash. | ||||||
|     """ |     """ | ||||||
|  | @ -277,7 +198,7 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return phone, phone_hash |     return phone, phone_hash | ||||||
| 
 | 
 | ||||||
|     async def sign_in( | async def sign_in( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         phone: str = None, |         phone: str = None, | ||||||
|         code: typing.Union[str, int] = None, |         code: typing.Union[str, int] = None, | ||||||
|  | @ -285,55 +206,6 @@ class AuthMethods: | ||||||
|         password: str = None, |         password: str = None, | ||||||
|         bot_token: str = None, |         bot_token: str = None, | ||||||
|         phone_code_hash: str = None) -> 'typing.Union[types.User, types.auth.SentCode]': |         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() |     me = await self.get_me() | ||||||
|     if me: |     if me: | ||||||
|         return me |         return me | ||||||
|  | @ -373,7 +245,7 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return self._on_login(result.user) |     return self._on_login(result.user) | ||||||
| 
 | 
 | ||||||
|     async def sign_up( | async def sign_up( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         code: typing.Union[str, int], |         code: typing.Union[str, int], | ||||||
|         first_name: str, |         first_name: str, | ||||||
|  | @ -381,48 +253,6 @@ class AuthMethods: | ||||||
|         *, |         *, | ||||||
|         phone: str = None, |         phone: str = None, | ||||||
|         phone_code_hash: str = None) -> 'types.User': |         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() |     me = await self.get_me() | ||||||
|     if me: |     if me: | ||||||
|         return me |         return me | ||||||
|  | @ -468,10 +298,9 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return self._on_login(result.user) |     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. |     Callback called whenever the login or sign up process completes. | ||||||
| 
 |  | ||||||
|     Returns the input user parameter. |     Returns the input user parameter. | ||||||
|     """ |     """ | ||||||
|     self._bot = bool(user.bot) |     self._bot = bool(user.bot) | ||||||
|  | @ -480,31 +309,11 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return user |     return user | ||||||
| 
 | 
 | ||||||
|     async def send_code_request( | async def send_code_request( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         phone: str, |         phone: str, | ||||||
|         *, |         *, | ||||||
|         force_sms: bool = False) -> 'types.auth.SentCode': |         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 |     result = None | ||||||
|     phone = utils.parse_phone(phone) or self._phone |     phone = utils.parse_phone(phone) or self._phone | ||||||
|     phone_hash = self._phone_code_hash.get(phone) |     phone_hash = self._phone_code_hash.get(phone) | ||||||
|  | @ -536,56 +345,12 @@ class AuthMethods: | ||||||
| 
 | 
 | ||||||
|     return result |     return result | ||||||
| 
 | 
 | ||||||
|     async def qr_login(self: 'TelegramClient', ignored_ids: typing.List[int] = None) -> custom.QRLogin: | 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() |  | ||||||
|         """ |  | ||||||
|     qr_login = custom.QRLogin(self, ignored_ids or []) |     qr_login = custom.QRLogin(self, ignored_ids or []) | ||||||
|     await qr_login.recreate() |     await qr_login.recreate() | ||||||
|     return qr_login |     return qr_login | ||||||
| 
 | 
 | ||||||
|     async def log_out(self: 'TelegramClient') -> bool: | 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() |  | ||||||
|         """ |  | ||||||
|     try: |     try: | ||||||
|         await self(functions.auth.LogOutRequest()) |         await self(functions.auth.LogOutRequest()) | ||||||
|     except errors.RPCError: |     except errors.RPCError: | ||||||
|  | @ -600,7 +365,7 @@ class AuthMethods: | ||||||
|     self.session.delete() |     self.session.delete() | ||||||
|     return True |     return True | ||||||
| 
 | 
 | ||||||
|     async def edit_2fa( | async def edit_2fa( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         current_password: str = None, |         current_password: str = None, | ||||||
|         new_password: str = None, |         new_password: str = None, | ||||||
|  | @ -608,59 +373,6 @@ class AuthMethods: | ||||||
|         hint: str = '', |         hint: str = '', | ||||||
|         email: str = None, |         email: str = None, | ||||||
|         email_code_callback: typing.Callable[[int], str] = None) -> bool: |         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: |     if new_password is None and current_password is None: | ||||||
|         return False |         return False | ||||||
| 
 | 
 | ||||||
|  | @ -704,18 +416,3 @@ class AuthMethods: | ||||||
|         await self(functions.account.ConfirmPasswordEmailRequest(code)) |         await self(functions.account.ConfirmPasswordEmailRequest(code)) | ||||||
| 
 | 
 | ||||||
|     return True |     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 |     from .telegramclient import TelegramClient | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class BotMethods: | async def inline_query( | ||||||
|     async def inline_query( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         bot: 'hints.EntityLike', |         bot: 'hints.EntityLike', | ||||||
|         query: str, |         query: str, | ||||||
|  | @ -16,45 +15,6 @@ class BotMethods: | ||||||
|         entity: 'hints.EntityLike' = None, |         entity: 'hints.EntityLike' = None, | ||||||
|         offset: str = None, |         offset: str = None, | ||||||
|         geo_point: 'types.GeoPoint' = None) -> custom.InlineResults: |         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) |     bot = await self.get_input_entity(bot) | ||||||
|     if entity: |     if entity: | ||||||
|         peer = await self.get_input_entity(entity) |         peer = await self.get_input_entity(entity) | ||||||
|  |  | ||||||
|  | @ -4,40 +4,9 @@ from .. import utils, hints | ||||||
| from ..tl import types, custom | from ..tl import types, custom | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ButtonMethods: | def build_reply_markup( | ||||||
|     @staticmethod |  | ||||||
|     def build_reply_markup( |  | ||||||
|         buttons: 'typing.Optional[hints.MarkupLike]', |         buttons: 'typing.Optional[hints.MarkupLike]', | ||||||
|         inline_only: bool = False) -> 'typing.Optional[types.TypeReplyMarkup]': |         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: |     if buttons is None: | ||||||
|         return None |         return None | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -404,11 +404,7 @@ class _ProfilePhotoIter(RequestIter): | ||||||
|                 self.request.offset_id = result.messages[-1].id |                 self.request.offset_id = result.messages[-1].id | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ChatMethods: | def iter_participants( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     def iter_participants( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         limit: float = None, |         limit: float = None, | ||||||
|  | @ -416,67 +412,6 @@ class ChatMethods: | ||||||
|         search: str = '', |         search: str = '', | ||||||
|         filter: 'types.TypeChannelParticipantsFilter' = None, |         filter: 'types.TypeChannelParticipantsFilter' = None, | ||||||
|         aggressive: bool = False) -> _ParticipantsIter: |         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( |     return _ParticipantsIter( | ||||||
|         self, |         self, | ||||||
|         limit, |         limit, | ||||||
|  | @ -486,30 +421,14 @@ class ChatMethods: | ||||||
|         aggressive=aggressive |         aggressive=aggressive | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def get_participants( | async def get_participants( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         *args, |         *args, | ||||||
|         **kwargs) -> 'hints.TotalList': |         **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() |     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', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         limit: float = None, |         limit: float = None, | ||||||
|  | @ -533,105 +452,6 @@ class ChatMethods: | ||||||
|         edit: bool = None, |         edit: bool = None, | ||||||
|         delete: bool = None, |         delete: bool = None, | ||||||
|         group_call: bool = None) -> _AdminLogIter: |         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( |     return _AdminLogIter( | ||||||
|         self, |         self, | ||||||
|         limit, |         limit, | ||||||
|  | @ -657,64 +477,20 @@ class ChatMethods: | ||||||
|         group_call=group_call |         group_call=group_call | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def get_admin_log( | async def get_admin_log( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         *args, |         *args, | ||||||
|         **kwargs) -> 'hints.TotalList': |         **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() |     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', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         limit: int = None, |         limit: int = None, | ||||||
|         *, |         *, | ||||||
|         offset: int = 0, |         offset: int = 0, | ||||||
|         max_id: int = 0) -> _ProfilePhotoIter: |         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( |     return _ProfilePhotoIter( | ||||||
|         self, |         self, | ||||||
|         limit, |         limit, | ||||||
|  | @ -723,103 +499,20 @@ class ChatMethods: | ||||||
|         max_id=max_id |         max_id=max_id | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def get_profile_photos( | async def get_profile_photos( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         *args, |         *args, | ||||||
|         **kwargs) -> 'hints.TotalList': |         **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() |     return await self.iter_profile_photos(*args, **kwargs).collect() | ||||||
| 
 | 
 | ||||||
|     get_profile_photos.__signature__ = inspect.signature(iter_profile_photos) |  | ||||||
| 
 | 
 | ||||||
|     def action( | def action( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         action: 'typing.Union[str, types.TypeSendMessageAction]', |         action: 'typing.Union[str, types.TypeSendMessageAction]', | ||||||
|         *, |         *, | ||||||
|         delay: float = 4, |         delay: float = 4, | ||||||
|         auto_cancel: bool = True) -> 'typing.Union[_ChatAction, typing.Coroutine]': |         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): |     if isinstance(action, str): | ||||||
|         try: |         try: | ||||||
|             action = _ChatAction._str_mapping[action.lower()] |             action = _ChatAction._str_mapping[action.lower()] | ||||||
|  | @ -841,7 +534,7 @@ class ChatMethods: | ||||||
|     return _ChatAction( |     return _ChatAction( | ||||||
|         self, entity, action, delay=delay, auto_cancel=auto_cancel) |         self, entity, action, delay=delay, auto_cancel=auto_cancel) | ||||||
| 
 | 
 | ||||||
|     async def edit_admin( | async def edit_admin( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         user: 'hints.EntityLike', |         user: 'hints.EntityLike', | ||||||
|  | @ -858,93 +551,6 @@ class ChatMethods: | ||||||
|         anonymous: bool = None, |         anonymous: bool = None, | ||||||
|         is_admin: bool = None, |         is_admin: bool = None, | ||||||
|         title: str = None) -> types.Updates: |         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) |     entity = await self.get_input_entity(entity) | ||||||
|     user = await self.get_input_entity(user) |     user = await self.get_input_entity(user) | ||||||
|     ty = helpers._entity_type(user) |     ty = helpers._entity_type(user) | ||||||
|  | @ -993,7 +599,7 @@ class ChatMethods: | ||||||
|         raise ValueError( |         raise ValueError( | ||||||
|             'You can only edit permissions in groups and channels') |             'You can only edit permissions in groups and channels') | ||||||
| 
 | 
 | ||||||
|     async def edit_permissions( | async def edit_permissions( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         user: 'typing.Optional[hints.EntityLike]' = None, |         user: 'typing.Optional[hints.EntityLike]' = None, | ||||||
|  | @ -1011,103 +617,6 @@ class ChatMethods: | ||||||
|         change_info: bool = True, |         change_info: bool = True, | ||||||
|         invite_users: bool = True, |         invite_users: bool = True, | ||||||
|         pin_messages: bool = True) -> types.Updates: |         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) |     entity = await self.get_input_entity(entity) | ||||||
|     ty = helpers._entity_type(entity) |     ty = helpers._entity_type(entity) | ||||||
|     if ty != helpers._EntityType.CHANNEL: |     if ty != helpers._EntityType.CHANNEL: | ||||||
|  | @ -1149,43 +658,11 @@ class ChatMethods: | ||||||
|         banned_rights=rights |         banned_rights=rights | ||||||
|     )) |     )) | ||||||
| 
 | 
 | ||||||
|     async def kick_participant( | async def kick_participant( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         user: 'typing.Optional[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) |     entity = await self.get_input_entity(entity) | ||||||
|     user = await self.get_input_entity(user) |     user = await self.get_input_entity(user) | ||||||
|     if helpers._entity_type(user) != helpers._EntityType.USER: |     if helpers._entity_type(user) != helpers._EntityType.USER: | ||||||
|  | @ -1217,42 +694,11 @@ class ChatMethods: | ||||||
| 
 | 
 | ||||||
|     return self._get_response_message(None, resp, entity) |     return self._get_response_message(None, resp, entity) | ||||||
| 
 | 
 | ||||||
|     async def get_permissions( | async def get_permissions( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         user: 'hints.EntityLike' = None |         user: 'hints.EntityLike' = None | ||||||
|     ) -> 'typing.Optional[custom.ParticipantPermissions]': | ) -> '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) |  | ||||||
|         """ |  | ||||||
|     entity = await self.get_entity(entity) |     entity = await self.get_entity(entity) | ||||||
| 
 | 
 | ||||||
|     if not user: |     if not user: | ||||||
|  | @ -1287,50 +733,11 @@ class ChatMethods: | ||||||
| 
 | 
 | ||||||
|     raise ValueError('You must pass either a channel or a chat') |     raise ValueError('You must pass either a channel or a chat') | ||||||
| 
 | 
 | ||||||
|     async def get_stats( | async def get_stats( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'typing.Union[int, types.Message]' = None, |         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) |     entity = await self.get_input_entity(entity) | ||||||
|     if helpers._entity_type(entity) != helpers._EntityType.CHANNEL: |     if helpers._entity_type(entity) != helpers._EntityType.CHANNEL: | ||||||
|         raise TypeError('You must pass a channel entity') |         raise TypeError('You must pass a channel entity') | ||||||
|  | @ -1364,5 +771,3 @@ class ChatMethods: | ||||||
|         return await sender.send(req) |         return await sender.send(req) | ||||||
|     finally: |     finally: | ||||||
|         await self._return_exported_sender(sender) |         await self._return_exported_sender(sender) | ||||||
| 
 |  | ||||||
|     # endregion |  | ||||||
|  |  | ||||||
|  | @ -136,11 +136,7 @@ class _DraftsIter(RequestIter): | ||||||
|         return [] |         return [] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DialogMethods: | def iter_dialogs( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     def iter_dialogs( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         limit: float = None, |         limit: float = None, | ||||||
|         *, |         *, | ||||||
|  | @ -151,70 +147,7 @@ class DialogMethods: | ||||||
|         ignore_migrated: bool = False, |         ignore_migrated: bool = False, | ||||||
|         folder: int = None, |         folder: int = None, | ||||||
|         archived: bool = None |         archived: bool = None | ||||||
|     ) -> _DialogsIter: | ) -> _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)) |  | ||||||
|         """ |  | ||||||
|     if archived is not None: |     if archived is not None: | ||||||
|         folder = 1 if archived else 0 |         folder = 1 if archived else 0 | ||||||
| 
 | 
 | ||||||
|  | @ -229,149 +162,37 @@ class DialogMethods: | ||||||
|         folder=folder |         folder=folder | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def get_dialogs(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | 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) |  | ||||||
|         """ |  | ||||||
|     return await self.iter_dialogs(*args, **kwargs).collect() |     return await self.iter_dialogs(*args, **kwargs).collect() | ||||||
| 
 | 
 | ||||||
|     get_dialogs.__signature__ = inspect.signature(iter_dialogs) |  | ||||||
| 
 | 
 | ||||||
|     def iter_drafts( | def iter_drafts( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntitiesLike' = None |         entity: 'hints.EntitiesLike' = None | ||||||
|     ) -> _DraftsIter: | ) -> _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) |  | ||||||
|         """ |  | ||||||
|     if entity and not utils.is_list_like(entity): |     if entity and not utils.is_list_like(entity): | ||||||
|         entity = (entity,) |         entity = (entity,) | ||||||
| 
 | 
 | ||||||
|     # TODO Passing a limit here makes no sense |     # TODO Passing a limit here makes no sense | ||||||
|     return _DraftsIter(self, None, entities=entity) |     return _DraftsIter(self, None, entities=entity) | ||||||
| 
 | 
 | ||||||
|     async def get_drafts( | async def get_drafts( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntitiesLike' = None |         entity: 'hints.EntitiesLike' = None | ||||||
|     ) -> 'hints.TotalList': | ) -> '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) |  | ||||||
|         """ |  | ||||||
|     items = await self.iter_drafts(entity).collect() |     items = await self.iter_drafts(entity).collect() | ||||||
|     if not entity or utils.is_list_like(entity): |     if not entity or utils.is_list_like(entity): | ||||||
|         return items |         return items | ||||||
|     else: |     else: | ||||||
|         return items[0] |         return items[0] | ||||||
| 
 | 
 | ||||||
|     async def edit_folder( | async def edit_folder( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntitiesLike' = None, |         entity: 'hints.EntitiesLike' = None, | ||||||
|         folder: typing.Union[int, typing.Sequence[int]] = None, |         folder: typing.Union[int, typing.Sequence[int]] = None, | ||||||
|         *, |         *, | ||||||
|         unpack=None |         unpack=None | ||||||
|     ) -> types.Updates: | ) -> 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) |  | ||||||
|         """ |  | ||||||
|     if (entity is None) == (unpack is None): |     if (entity is None) == (unpack is None): | ||||||
|         raise ValueError('You can only set either entities or unpack, not both') |         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) |         for x, y in zip(entities, folder) | ||||||
|     ])) |     ])) | ||||||
| 
 | 
 | ||||||
|     async def delete_dialog( | async def delete_dialog( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         *, |         *, | ||||||
|         revoke: bool = False |         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), |     # If we have enough information (`Dialog.delete` gives it to us), | ||||||
|     # then we know we don't have to kick ourselves in deactivated chats. |     # then we know we don't have to kick ourselves in deactivated chats. | ||||||
|     if isinstance(entity, types.Chat): |     if isinstance(entity, types.Chat): | ||||||
|  | @ -469,7 +253,7 @@ class DialogMethods: | ||||||
| 
 | 
 | ||||||
|     return result |     return result | ||||||
| 
 | 
 | ||||||
|     def conversation( | def conversation( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         *, |         *, | ||||||
|  | @ -478,120 +262,6 @@ class DialogMethods: | ||||||
|         max_messages: int = 100, |         max_messages: int = 100, | ||||||
|         exclusive: bool = True, |         exclusive: bool = True, | ||||||
|         replies_are_responses: bool = True) -> custom.Conversation: |         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( |     return custom.Conversation( | ||||||
|         self, |         self, | ||||||
|         entity, |         entity, | ||||||
|  | @ -602,5 +272,3 @@ class DialogMethods: | ||||||
|         replies_are_responses=replies_are_responses |         replies_are_responses=replies_are_responses | ||||||
| 
 | 
 | ||||||
|     ) |     ) | ||||||
| 
 |  | ||||||
|     # endregion |  | ||||||
|  |  | ||||||
|  | @ -192,11 +192,7 @@ class _GenericDownloadIter(_DirectDownloadIter): | ||||||
|             self.request.offset -= self._stride |             self.request.offset -= self._stride | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class DownloadMethods: | async def download_profile_photo( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     async def download_profile_photo( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         file: 'hints.FileLike' = None, |         file: 'hints.FileLike' = None, | ||||||
|  | @ -307,7 +303,7 @@ class DownloadMethods: | ||||||
|             # Until there's a report for chats, no need to. |             # Until there's a report for chats, no need to. | ||||||
|             return None |             return None | ||||||
| 
 | 
 | ||||||
|     async def download_media( | async def download_media( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         message: 'hints.MessageLike', |         message: 'hints.MessageLike', | ||||||
|         file: 'hints.FileLike' = None, |         file: 'hints.FileLike' = None, | ||||||
|  | @ -424,7 +420,7 @@ class DownloadMethods: | ||||||
|             media, file, progress_callback |             media, file, progress_callback | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     async def download_file( | async def download_file( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         input_location: 'hints.FileLike', |         input_location: 'hints.FileLike', | ||||||
|         file: 'hints.OutFileLike' = None, |         file: 'hints.OutFileLike' = None, | ||||||
|  | @ -498,7 +494,7 @@ class DownloadMethods: | ||||||
|         iv=iv, |         iv=iv, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def _download_file( | async def _download_file( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         input_location: 'hints.FileLike', |         input_location: 'hints.FileLike', | ||||||
|         file: 'hints.OutFileLike' = None, |         file: 'hints.OutFileLike' = None, | ||||||
|  | @ -558,7 +554,7 @@ class DownloadMethods: | ||||||
|         if isinstance(file, str) or in_memory: |         if isinstance(file, str) or in_memory: | ||||||
|             f.close() |             f.close() | ||||||
| 
 | 
 | ||||||
|     def iter_download( | def iter_download( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         file: 'hints.FileLike', |         file: 'hints.FileLike', | ||||||
|         *, |         *, | ||||||
|  | @ -569,7 +565,7 @@ class DownloadMethods: | ||||||
|         request_size: int = MAX_CHUNK_SIZE, |         request_size: int = MAX_CHUNK_SIZE, | ||||||
|         file_size: int = None, |         file_size: int = None, | ||||||
|         dc_id: int = None |         dc_id: int = None | ||||||
|     ): | ): | ||||||
|     """ |     """ | ||||||
|     Iterates over a file download, yielding chunks of the file. |     Iterates over a file download, yielding chunks of the file. | ||||||
| 
 | 
 | ||||||
|  | @ -664,7 +660,7 @@ class DownloadMethods: | ||||||
|         dc_id=dc_id, |         dc_id=dc_id, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     def _iter_download( | def _iter_download( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         file: 'hints.FileLike', |         file: 'hints.FileLike', | ||||||
|         *, |         *, | ||||||
|  | @ -676,7 +672,7 @@ class DownloadMethods: | ||||||
|         file_size: int = None, |         file_size: int = None, | ||||||
|         dc_id: int = None, |         dc_id: int = None, | ||||||
|         msg_data: tuple = None |         msg_data: tuple = None | ||||||
|     ): | ): | ||||||
|     info = utils._get_file_info(file) |     info = utils._get_file_info(file) | ||||||
|     if info.dc_id is not None: |     if info.dc_id is not None: | ||||||
|         dc_id = info.dc_id |         dc_id = info.dc_id | ||||||
|  | @ -728,12 +724,8 @@ class DownloadMethods: | ||||||
|         msg_data=msg_data, |         msg_data=msg_data, | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     # endregion |  | ||||||
| 
 | 
 | ||||||
|     # region Private methods | def _get_thumb(thumbs, thumb): | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def _get_thumb(thumbs, thumb): |  | ||||||
|     # Seems Telegram has changed the order and put `PhotoStrippedSize` |     # Seems Telegram has changed the order and put `PhotoStrippedSize` | ||||||
|     # last while this is the smallest (layer 116). Ensure we have the |     # last while this is the smallest (layer 116). Ensure we have the | ||||||
|     # sizes sorted correctly with a custom function. |     # sizes sorted correctly with a custom function. | ||||||
|  | @ -773,7 +765,7 @@ class DownloadMethods: | ||||||
|     else: |     else: | ||||||
|         return None |         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 |     # No need to download anything, simply write the bytes | ||||||
|     if isinstance(size, types.PhotoStrippedSize): |     if isinstance(size, types.PhotoStrippedSize): | ||||||
|         data = utils.stripped_photo_to_jpg(size.bytes) |         data = utils.stripped_photo_to_jpg(size.bytes) | ||||||
|  | @ -795,7 +787,7 @@ class DownloadMethods: | ||||||
|             f.close() |             f.close() | ||||||
|     return file |     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""" |     """Specialized version of .download_media() for photos""" | ||||||
|     # Determine the photo and its largest size |     # Determine the photo and its largest size | ||||||
|     if isinstance(photo, types.MessageMediaPhoto): |     if isinstance(photo, types.MessageMediaPhoto): | ||||||
|  | @ -834,8 +826,7 @@ class DownloadMethods: | ||||||
|     ) |     ) | ||||||
|     return result if file is bytes else file |     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`.""" |     """Gets kind and possible names for :tl:`DocumentAttribute`.""" | ||||||
|     kind = 'document' |     kind = 'document' | ||||||
|     possible_names = [] |     possible_names = [] | ||||||
|  | @ -858,7 +849,7 @@ class DownloadMethods: | ||||||
| 
 | 
 | ||||||
|     return kind, possible_names |     return kind, possible_names | ||||||
| 
 | 
 | ||||||
|     async def _download_document( | async def _download_document( | ||||||
|         self, document, file, date, thumb, progress_callback, msg_data): |         self, document, file, date, thumb, progress_callback, msg_data): | ||||||
|     """Specialized version of .download_media() for documents.""" |     """Specialized version of .download_media() for documents.""" | ||||||
|     if isinstance(document, types.MessageMediaDocument): |     if isinstance(document, types.MessageMediaDocument): | ||||||
|  | @ -894,8 +885,7 @@ class DownloadMethods: | ||||||
| 
 | 
 | ||||||
|     return result if file is bytes else file |     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. |     Specialized version of .download_media() for contacts. | ||||||
|     Will make use of the vCard 4.0 format. |     Will make use of the vCard 4.0 format. | ||||||
|  | @ -936,8 +926,7 @@ class DownloadMethods: | ||||||
| 
 | 
 | ||||||
|     return file |     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. |     Specialized version of .download_media() for web documents. | ||||||
|     """ |     """ | ||||||
|  | @ -977,8 +966,7 @@ class DownloadMethods: | ||||||
| 
 | 
 | ||||||
|     return f.getvalue() if in_memory else file |     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): |                             date=None, possible_names=None): | ||||||
|     """Gets a proper filename for 'file', if this is a path. |     """Gets a proper filename for 'file', if this is a path. | ||||||
| 
 | 
 | ||||||
|  | @ -1039,5 +1027,3 @@ class DownloadMethods: | ||||||
|         if not os.path.isfile(result): |         if not os.path.isfile(result): | ||||||
|             return result |             return result | ||||||
|         i += 1 |         i += 1 | ||||||
| 
 |  | ||||||
|     # endregion |  | ||||||
|  |  | ||||||
|  | @ -9,12 +9,7 @@ if typing.TYPE_CHECKING: | ||||||
|     from .telegramclient import TelegramClient |     from .telegramclient import TelegramClient | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MessageParseMethods: | def get_parse_mode(self: 'TelegramClient'): | ||||||
| 
 |  | ||||||
|     # region Public properties |  | ||||||
| 
 |  | ||||||
|     @property |  | ||||||
|     def parse_mode(self: 'TelegramClient'): |  | ||||||
|     """ |     """ | ||||||
|     This property is the default parse mode used when sending messages. |     This property is the default parse mode used when sending messages. | ||||||
|     Defaults to `telethon.extensions.markdown`. It will always |     Defaults to `telethon.extensions.markdown`. It will always | ||||||
|  | @ -49,15 +44,14 @@ class MessageParseMethods: | ||||||
|     """ |     """ | ||||||
|     return self._parse_mode |     return self._parse_mode | ||||||
| 
 | 
 | ||||||
|     @parse_mode.setter | def set_parse_mode(self: 'TelegramClient', mode: str): | ||||||
|     def parse_mode(self: 'TelegramClient', mode: str): |  | ||||||
|     self._parse_mode = utils.sanitize_parse_mode(mode) |     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``, |     Helper method to replace ``entities[i]`` to mention ``user``, | ||||||
|     or do nothing if it can't be found. |     or do nothing if it can't be found. | ||||||
|  | @ -71,7 +65,7 @@ class MessageParseMethods: | ||||||
|     except (ValueError, TypeError): |     except (ValueError, TypeError): | ||||||
|         return False |         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``. |     Returns a (parsed message, entities) tuple depending on ``parse_mode``. | ||||||
|     """ |     """ | ||||||
|  | @ -105,7 +99,7 @@ class MessageParseMethods: | ||||||
| 
 | 
 | ||||||
|     return message, msg_entities |     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. |     Extracts the response message known a request and Update result. | ||||||
|     The request may also be the ID of the message to match. |     The request may also be the ID of the message to match. | ||||||
|  | @ -224,5 +218,3 @@ class MessageParseMethods: | ||||||
|         else None |         else None | ||||||
|         for rnd in random_id |         for rnd in random_id | ||||||
|     ] |     ] | ||||||
| 
 |  | ||||||
|     # endregion |  | ||||||
|  |  | ||||||
|  | @ -320,13 +320,7 @@ class _IDsIter(RequestIter): | ||||||
|                 self.buffer.append(message) |                 self.buffer.append(message) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class MessageMethods: | def iter_messages( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     # region Message retrieval |  | ||||||
| 
 |  | ||||||
|     def iter_messages( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         limit: float = None, |         limit: float = None, | ||||||
|  | @ -344,167 +338,7 @@ class MessageMethods: | ||||||
|         reverse: bool = False, |         reverse: bool = False, | ||||||
|         reply_to: int = None, |         reply_to: int = None, | ||||||
|         scheduled: bool = False |         scheduled: bool = False | ||||||
|     ) -> 'typing.Union[_MessagesIter, _IDsIter]': | ) -> '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) |  | ||||||
|         """ |  | ||||||
|     if ids is not None: |     if ids is not None: | ||||||
|         if not utils.is_list_like(ids): |         if not utils.is_list_like(ids): | ||||||
|             ids = [ids] |             ids = [ids] | ||||||
|  | @ -536,37 +370,7 @@ class MessageMethods: | ||||||
|         scheduled=scheduled |         scheduled=scheduled | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     async def get_messages(self: 'TelegramClient', *args, **kwargs) -> 'hints.TotalList': | 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) |  | ||||||
|         """ |  | ||||||
|     if len(args) == 1 and 'limit' not in kwargs: |     if len(args) == 1 and 'limit' not in kwargs: | ||||||
|         if 'min_id' in kwargs and 'max_id' in kwargs: |         if 'min_id' in kwargs and 'max_id' in kwargs: | ||||||
|             kwargs['limit'] = None |             kwargs['limit'] = None | ||||||
|  | @ -585,17 +389,12 @@ class MessageMethods: | ||||||
| 
 | 
 | ||||||
|     return await it.collect() |     return await it.collect() | ||||||
| 
 | 
 | ||||||
|     get_messages.__signature__ = inspect.signature(iter_messages) |  | ||||||
| 
 | 
 | ||||||
|     # endregion | async def _get_comment_data( | ||||||
| 
 |  | ||||||
|     # region Message sending/editing/deleting |  | ||||||
| 
 |  | ||||||
|     async def _get_comment_data( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'typing.Union[int, types.Message]' |         message: 'typing.Union[int, types.Message]' | ||||||
|     ): | ): | ||||||
|     r = await self(functions.messages.GetDiscussionMessageRequest( |     r = await self(functions.messages.GetDiscussionMessageRequest( | ||||||
|         peer=entity, |         peer=entity, | ||||||
|         msg_id=utils.get_message_id(message) |         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) |     chat = next(c for c in r.chats if c.id == m.peer_id.channel_id) | ||||||
|     return utils.get_input_peer(chat), m.id |     return utils.get_input_peer(chat), m.id | ||||||
| 
 | 
 | ||||||
|     async def send_message( | async def send_message( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'hints.MessageLike' = '', |         message: 'hints.MessageLike' = '', | ||||||
|  | @ -624,178 +423,7 @@ class MessageMethods: | ||||||
|         supports_streaming: bool = False, |         supports_streaming: bool = False, | ||||||
|         schedule: 'hints.DateLike' = None, |         schedule: 'hints.DateLike' = None, | ||||||
|         comment_to: 'typing.Union[int, types.Message]' = None |         comment_to: 'typing.Union[int, types.Message]' = None | ||||||
|     ) -> 'types.Message': | ) -> '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)) |  | ||||||
|         """ |  | ||||||
|     if file is not None: |     if file is not None: | ||||||
|         return await self.send_file( |         return await self.send_file( | ||||||
|             entity, file, caption=message, reply_to=reply_to, |             entity, file, caption=message, reply_to=reply_to, | ||||||
|  | @ -887,7 +515,7 @@ class MessageMethods: | ||||||
| 
 | 
 | ||||||
|     return self._get_response_message(request, result, entity) |     return self._get_response_message(request, result, entity) | ||||||
| 
 | 
 | ||||||
|     async def forward_messages( | async def forward_messages( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         messages: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', |         messages: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', | ||||||
|  | @ -898,75 +526,7 @@ class MessageMethods: | ||||||
|         silent: bool = None, |         silent: bool = None, | ||||||
|         as_album: bool = None, |         as_album: bool = None, | ||||||
|         schedule: 'hints.DateLike' = None |         schedule: 'hints.DateLike' = None | ||||||
|     ) -> 'typing.Sequence[types.Message]': | ) -> '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) |  | ||||||
|         """ |  | ||||||
|     if as_album is not None: |     if as_album is not None: | ||||||
|         warnings.warn('the as_album argument is deprecated and no longer has any effect') |         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 |     return sent[0] if single else sent | ||||||
| 
 | 
 | ||||||
|     async def edit_message( | async def edit_message( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'typing.Union[hints.EntityLike, types.Message]', |         entity: 'typing.Union[hints.EntityLike, types.Message]', | ||||||
|         message: 'hints.MessageLike' = None, |         message: 'hints.MessageLike' = None, | ||||||
|  | @ -1032,117 +592,7 @@ class MessageMethods: | ||||||
|         buttons: 'hints.MarkupLike' = None, |         buttons: 'hints.MarkupLike' = None, | ||||||
|         supports_streaming: bool = False, |         supports_streaming: bool = False, | ||||||
|         schedule: 'hints.DateLike' = None |         schedule: 'hints.DateLike' = None | ||||||
|     ) -> 'types.Message': | ) -> '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!!!') |  | ||||||
|         """ |  | ||||||
|     if isinstance(entity, types.InputBotInlineMessageID): |     if isinstance(entity, types.InputBotInlineMessageID): | ||||||
|         text = text or message |         text = text or message | ||||||
|         message = entity |         message = entity | ||||||
|  | @ -1194,56 +644,12 @@ class MessageMethods: | ||||||
|     msg = self._get_response_message(request, await self(request), entity) |     msg = self._get_response_message(request, await self(request), entity) | ||||||
|     return msg |     return msg | ||||||
| 
 | 
 | ||||||
|     async def delete_messages( | async def delete_messages( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message_ids: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', |         message_ids: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]', | ||||||
|         *, |         *, | ||||||
|         revoke: bool = True) -> 'typing.Sequence[types.messages.AffectedMessages]': |         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): |     if not utils.is_list_like(message_ids): | ||||||
|         message_ids = (message_ids,) |         message_ids = (message_ids,) | ||||||
| 
 | 
 | ||||||
|  | @ -1267,60 +673,13 @@ class MessageMethods: | ||||||
|         return await self([functions.messages.DeleteMessagesRequest( |         return await self([functions.messages.DeleteMessagesRequest( | ||||||
|                         list(c), revoke) for c in utils.chunks(message_ids)]) |                         list(c), revoke) for c in utils.chunks(message_ids)]) | ||||||
| 
 | 
 | ||||||
|     # endregion | async def send_read_acknowledge( | ||||||
| 
 |  | ||||||
|     # region Miscellaneous |  | ||||||
| 
 |  | ||||||
|     async def send_read_acknowledge( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]' = None, |         message: 'typing.Union[hints.MessageIDLike, typing.Sequence[hints.MessageIDLike]]' = None, | ||||||
|         *, |         *, | ||||||
|         max_id: int = None, |         max_id: int = None, | ||||||
|         clear_mentions: bool = False) -> bool: |         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 max_id is None: | ||||||
|         if not message: |         if not message: | ||||||
|             max_id = 0 |             max_id = 0 | ||||||
|  | @ -1346,78 +705,26 @@ class MessageMethods: | ||||||
| 
 | 
 | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
|     async def pin_message( | async def pin_message( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'typing.Optional[hints.MessageIDLike]', |         message: 'typing.Optional[hints.MessageIDLike]', | ||||||
|         *, |         *, | ||||||
|         notify: bool = False, |         notify: bool = False, | ||||||
|         pm_oneside: 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) |     return await self._pin(entity, message, unpin=False, notify=notify, pm_oneside=pm_oneside) | ||||||
| 
 | 
 | ||||||
|     async def unpin_message( | async def unpin_message( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         message: 'typing.Optional[hints.MessageIDLike]' = None, |         message: 'typing.Optional[hints.MessageIDLike]' = None, | ||||||
|         *, |         *, | ||||||
|         notify: bool = False |         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) |     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 |     message = utils.get_message_id(message) or 0 | ||||||
|     entity = await self.get_input_entity(entity) |     entity = await self.get_input_entity(entity) | ||||||
|     if message <= 0:  # old behaviour accepted negative IDs to unpin |     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 |     # Pinning a message that doesn't exist would RPC-error earlier | ||||||
|     return self._get_response_message(request, result, entity) |     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`? | # 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 | def init( | ||||||
|             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__( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         session: 'typing.Union[str, Session]', |         session: 'typing.Union[str, Session]', | ||||||
|         api_id: int, |         api_id: int, | ||||||
|  | @ -245,7 +92,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|         loop: asyncio.AbstractEventLoop = None, |         loop: asyncio.AbstractEventLoop = None, | ||||||
|         base_logger: typing.Union[str, logging.Logger] = None, |         base_logger: typing.Union[str, logging.Logger] = None, | ||||||
|         receive_updates: bool = True |         receive_updates: bool = True | ||||||
|     ): | ): | ||||||
|     if not api_id or not api_hash: |     if not api_id or not api_hash: | ||||||
|         raise ValueError( |         raise ValueError( | ||||||
|             "Your API ID or Hash cannot be empty or None. " |             "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`) |     # A place to store if channels are a megagroup or not (see `edit_admin`) | ||||||
|     self._megagroup_cache = {} |     self._megagroup_cache = {} | ||||||
| 
 | 
 | ||||||
|     # endregion |  | ||||||
| 
 | 
 | ||||||
|     # region Properties | def get_loop(self: 'TelegramClient') -> asyncio.AbstractEventLoop: | ||||||
| 
 |  | ||||||
|     @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 |  | ||||||
|         """ |  | ||||||
|     return asyncio.get_event_loop() |     return asyncio.get_event_loop() | ||||||
| 
 | 
 | ||||||
|     @property | def get_disconnected(self: 'TelegramClient') -> asyncio.Future: | ||||||
|     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') |  | ||||||
|         """ |  | ||||||
|     return self._sender.disconnected |     return self._sender.disconnected | ||||||
| 
 | 
 | ||||||
|     @property | def get_flood_sleep_threshold(self): | ||||||
|     def flood_sleep_threshold(self): |  | ||||||
|     return self._flood_sleep_threshold |     return self._flood_sleep_threshold | ||||||
| 
 | 
 | ||||||
|     @flood_sleep_threshold.setter | def set_flood_sleep_threshold(self, value): | ||||||
|     def flood_sleep_threshold(self, value): |  | ||||||
|     # None -> 0, negative values don't really matter |     # None -> 0, negative values don't really matter | ||||||
|     self._flood_sleep_threshold = min(value or 0, 24 * 60 * 60) |     self._flood_sleep_threshold = min(value or 0, 24 * 60 * 60) | ||||||
| 
 | 
 | ||||||
|     # endregion |  | ||||||
| 
 | 
 | ||||||
|     # region Connecting | async def connect(self: 'TelegramClient') -> None: | ||||||
| 
 |  | ||||||
|     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') |  | ||||||
|         """ |  | ||||||
|     if not await self._sender.connect(self._connection( |     if not await self._sender.connect(self._connection( | ||||||
|         self.session.server_address, |         self.session.server_address, | ||||||
|         self.session.port, |         self.session.port, | ||||||
|  | @ -544,35 +333,11 @@ class TelegramBaseClient(abc.ABC): | ||||||
| 
 | 
 | ||||||
|     self._updates_handle = self.loop.create_task(self._update_loop()) |     self._updates_handle = self.loop.create_task(self._update_loop()) | ||||||
| 
 | 
 | ||||||
|     def is_connected(self: 'TelegramClient') -> bool: | 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) |  | ||||||
|         """ |  | ||||||
|     sender = getattr(self, '_sender', None) |     sender = getattr(self, '_sender', None) | ||||||
|     return sender and sender.is_connected() |     return sender and sender.is_connected() | ||||||
| 
 | 
 | ||||||
|     def disconnect(self: 'TelegramClient'): | 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() |  | ||||||
|         """ |  | ||||||
|     if self.loop.is_running(): |     if self.loop.is_running(): | ||||||
|         return self._disconnect_coro() |         return self._disconnect_coro() | ||||||
|     else: |     else: | ||||||
|  | @ -586,16 +351,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|             # However, it doesn't really make a lot of sense. |             # However, it doesn't really make a lot of sense. | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|     def set_proxy(self: 'TelegramClient', proxy: typing.Union[tuple, dict]): | 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) |  | ||||||
|         """ |  | ||||||
|     init_proxy = None if not issubclass(self._connection, TcpMTProxy) else \ |     init_proxy = None if not issubclass(self._connection, TcpMTProxy) else \ | ||||||
|         types.InputClientProxy(*self._connection.address_info(proxy)) |         types.InputClientProxy(*self._connection.address_info(proxy)) | ||||||
| 
 | 
 | ||||||
|  | @ -615,7 +371,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|         else: |         else: | ||||||
|             connection._proxy = proxy |             connection._proxy = proxy | ||||||
| 
 | 
 | ||||||
|     async def _disconnect_coro(self: 'TelegramClient'): | async def _disconnect_coro(self: 'TelegramClient'): | ||||||
|     await self._disconnect() |     await self._disconnect() | ||||||
| 
 | 
 | ||||||
|     # Also clean-up all exported senders because we're done with them |     # Also clean-up all exported senders because we're done with them | ||||||
|  | @ -654,7 +410,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
| 
 | 
 | ||||||
|     self.session.close() |     self.session.close() | ||||||
| 
 | 
 | ||||||
|     async def _disconnect(self: 'TelegramClient'): | async def _disconnect(self: 'TelegramClient'): | ||||||
|     """ |     """ | ||||||
|     Disconnect only, without closing the session. Used in reconnections |     Disconnect only, without closing the session. Used in reconnections | ||||||
|     to different data centers, where we don't want to close the session |     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__], |     await helpers._cancel(self._log[__name__], | ||||||
|                             updates_handle=self._updates_handle) |                             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. |     Permanently switches the current connection to the new data center. | ||||||
|     """ |     """ | ||||||
|  | @ -681,7 +437,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|     await self._disconnect() |     await self._disconnect() | ||||||
|     return await self.connect() |     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 |     Callback from the sender whenever it needed to generate a | ||||||
|     new authorization key. This means we are not authorized. |     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.auth_key = auth_key | ||||||
|     self.session.save() |     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'""" |     """Gets the Data Center (DC) associated to 'dc_id'""" | ||||||
|     cls = self.__class__ |     cls = self.__class__ | ||||||
|     if not cls._config: |     if not cls._config: | ||||||
|  | @ -720,7 +473,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|             if dc.id == dc_id and bool(dc.cdn) == cdn |             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 |     Creates a new exported `MTProtoSender` for the given `dc_id` and | ||||||
|     returns it. This method should be used by `_borrow_exported_sender`. |     returns it. This method should be used by `_borrow_exported_sender`. | ||||||
|  | @ -748,7 +501,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|     await sender.send(req) |     await sender.send(req) | ||||||
|     return sender |     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`. |     Borrows a connected `MTProtoSender` for the given `dc_id`. | ||||||
|     If it's not cached, creates a new one if it doesn't exist yet, |     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() |         state.add_borrow() | ||||||
|         return sender |         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 |     Returns a borrowed exported sender. If all borrows have | ||||||
|     been returned, the sender is cleanly disconnected. |     been returned, the sender is cleanly disconnected. | ||||||
|  | @ -790,7 +543,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|         state, _ = self._borrowed_senders[sender.dc_id] |         state, _ = self._borrowed_senders[sender.dc_id] | ||||||
|         state.add_return() |         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. |     Cleans-up all unused exported senders by disconnecting them. | ||||||
|     """ |     """ | ||||||
|  | @ -804,7 +557,7 @@ class TelegramBaseClient(abc.ABC): | ||||||
|                 await sender.disconnect() |                 await sender.disconnect() | ||||||
|                 state.mark_disconnected() |                 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""" |     """Similar to ._borrow_exported_client, but for CDNs""" | ||||||
|     # TODO Implement |     # TODO Implement | ||||||
|     raise NotImplementedError |     raise NotImplementedError | ||||||
|  | @ -830,46 +583,19 @@ class TelegramBaseClient(abc.ABC): | ||||||
|     client.connect(_sync_updates=False) |     client.connect(_sync_updates=False) | ||||||
|     return client |     return client | ||||||
| 
 | 
 | ||||||
|     # endregion |  | ||||||
| 
 | 
 | ||||||
|     # region Invoking Telegram requests | @abc.abstractmethod | ||||||
| 
 | def __call__(self: 'TelegramClient', request, ordered=False): | ||||||
|     @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. |  | ||||||
|         """ |  | ||||||
|     raise NotImplementedError |     raise NotImplementedError | ||||||
| 
 | 
 | ||||||
|     @abc.abstractmethod | @abc.abstractmethod | ||||||
|     def _handle_update(self: 'TelegramClient', update): | def _handle_update(self: 'TelegramClient', update): | ||||||
|     raise NotImplementedError |     raise NotImplementedError | ||||||
| 
 | 
 | ||||||
|     @abc.abstractmethod | @abc.abstractmethod | ||||||
|     def _update_loop(self: 'TelegramClient'): | def _update_loop(self: 'TelegramClient'): | ||||||
|     raise NotImplementedError |     raise NotImplementedError | ||||||
| 
 | 
 | ||||||
|     @abc.abstractmethod | @abc.abstractmethod | ||||||
|     async def _handle_auto_reconnect(self: 'TelegramClient'): | async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||||
|     raise NotImplementedError |     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] | 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: |     try: | ||||||
|         # Make a high-level request to notify that we want updates |         # Make a high-level request to notify that we want updates | ||||||
|         await self(functions.updates.GetStateRequest()) |         await self(functions.updates.GetStateRequest()) | ||||||
|  | @ -32,7 +29,7 @@ class UpdateMethods: | ||||||
|     finally: |     finally: | ||||||
|         await self.disconnect() |         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`. |     Change the value of `receive_updates`. | ||||||
| 
 | 
 | ||||||
|  | @ -43,7 +40,7 @@ class UpdateMethods: | ||||||
|     if receive_updates: |     if receive_updates: | ||||||
|         await self(functions.updates.GetStateRequest()) |         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. |     Runs the event loop until the library is disconnected. | ||||||
| 
 | 
 | ||||||
|  | @ -88,7 +85,7 @@ class UpdateMethods: | ||||||
|         # No loop.run_until_complete; it's already syncified |         # No loop.run_until_complete; it's already syncified | ||||||
|         self.disconnect() |         self.disconnect() | ||||||
| 
 | 
 | ||||||
|     def on(self: 'TelegramClient', event: EventBuilder): | def on(self: 'TelegramClient', event: EventBuilder): | ||||||
|     """ |     """ | ||||||
|     Decorator used to `add_event_handler` more conveniently. |     Decorator used to `add_event_handler` more conveniently. | ||||||
| 
 | 
 | ||||||
|  | @ -115,7 +112,7 @@ class UpdateMethods: | ||||||
| 
 | 
 | ||||||
|     return decorator |     return decorator | ||||||
| 
 | 
 | ||||||
|     def add_event_handler( | def add_event_handler( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         callback: Callback, |         callback: Callback, | ||||||
|         event: EventBuilder = None): |         event: EventBuilder = None): | ||||||
|  | @ -164,7 +161,7 @@ class UpdateMethods: | ||||||
| 
 | 
 | ||||||
|     self._event_builders.append((event, callback)) |     self._event_builders.append((event, callback)) | ||||||
| 
 | 
 | ||||||
|     def remove_event_handler( | def remove_event_handler( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         callback: Callback, |         callback: Callback, | ||||||
|         event: EventBuilder = None) -> int: |         event: EventBuilder = None) -> int: | ||||||
|  | @ -203,7 +200,7 @@ class UpdateMethods: | ||||||
| 
 | 
 | ||||||
|     return found |     return found | ||||||
| 
 | 
 | ||||||
|     def list_event_handlers(self: 'TelegramClient')\ | def list_event_handlers(self: 'TelegramClient')\ | ||||||
|         -> 'typing.Sequence[typing.Tuple[Callback, EventBuilder]]': |         -> 'typing.Sequence[typing.Tuple[Callback, EventBuilder]]': | ||||||
|     """ |     """ | ||||||
|     Lists all registered event handlers. |     Lists all registered event handlers. | ||||||
|  | @ -224,7 +221,7 @@ class UpdateMethods: | ||||||
|     """ |     """ | ||||||
|     return [(callback, event) for event, callback in self._event_builders] |     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. |     "Catches up" on the missed updates while the client was offline. | ||||||
|     You should call this method after registering the event handlers |     You should call this method after registering the event handlers | ||||||
|  | @ -293,14 +290,11 @@ class UpdateMethods: | ||||||
|         self._state_cache._pts_date = (pts, date) |         self._state_cache._pts_date = (pts, date) | ||||||
|         self.session.catching_up = False |         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 | ||||||
|     # It is important to not make _handle_update async because we rely on | # be always-increasing. There is also no need to make this async. | ||||||
|     # the order that the updates arrive in to update the pts and date to | def _handle_update(self: 'TelegramClient', update): | ||||||
|     # be always-increasing. There is also no need to make this async. |  | ||||||
|     def _handle_update(self: 'TelegramClient', update): |  | ||||||
|     self.session.process_entities(update) |     self.session.process_entities(update) | ||||||
|     self._entity_cache.add(update) |     self._entity_cache.add(update) | ||||||
| 
 | 
 | ||||||
|  | @ -316,7 +310,7 @@ class UpdateMethods: | ||||||
| 
 | 
 | ||||||
|     self._state_cache.update(update) |     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 {} |     update._entities = entities or {} | ||||||
| 
 | 
 | ||||||
|     # This part is somewhat hot so we don't bother patching |     # This part is somewhat hot so we don't bother patching | ||||||
|  | @ -336,7 +330,7 @@ class UpdateMethods: | ||||||
| 
 | 
 | ||||||
|     self._state_cache.update(update) |     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" |     # Pings' ID don't really need to be secure, just "random" | ||||||
|     rnd = lambda: random.randrange(-2**63, 2**63) |     rnd = lambda: random.randrange(-2**63, 2**63) | ||||||
|     while self.is_connected(): |     while self.is_connected(): | ||||||
|  | @ -390,13 +384,13 @@ class UpdateMethods: | ||||||
|             except (ConnectionError, asyncio.CancelledError): |             except (ConnectionError, asyncio.CancelledError): | ||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
|     async def _dispatch_queue_updates(self: 'TelegramClient'): | async def _dispatch_queue_updates(self: 'TelegramClient'): | ||||||
|     while not self._updates_queue.empty(): |     while not self._updates_queue.empty(): | ||||||
|         await self._dispatch_update(*self._updates_queue.get_nowait()) |         await self._dispatch_update(*self._updates_queue.get_nowait()) | ||||||
| 
 | 
 | ||||||
|     self._dispatching_updates_queue.clear() |     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): |     if not self._entity_cache.ensure_cached(update): | ||||||
|         # We could add a lock to not fetch the same pts twice if we are |         # 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, |         # already fetching it. However this does not happen in practice, | ||||||
|  | @ -482,7 +476,7 @@ class UpdateMethods: | ||||||
|                 name = getattr(callback, '__name__', repr(callback)) |                 name = getattr(callback, '__name__', repr(callback)) | ||||||
|                 self._log[__name__].exception('Unhandled exception on %s', name) |                 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`. |     Dispatches a single, out-of-order event. Used by `AlbumHack`. | ||||||
|     """ |     """ | ||||||
|  | @ -523,7 +517,7 @@ class UpdateMethods: | ||||||
|                 name = getattr(callback, '__name__', repr(callback)) |                 name = getattr(callback, '__name__', repr(callback)) | ||||||
|                 self._log[__name__].exception('Unhandled exception on %s', name) |                 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. |     Get the difference for this `channel_id` if any, then load entities. | ||||||
| 
 | 
 | ||||||
|  | @ -584,7 +578,7 @@ class UpdateMethods: | ||||||
|             itertools.chain(result.users, result.chats) |             itertools.chain(result.users, result.chats) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|     async def _handle_auto_reconnect(self: 'TelegramClient'): | async def _handle_auto_reconnect(self: 'TelegramClient'): | ||||||
|     # TODO Catch-up |     # TODO Catch-up | ||||||
|     # For now we make a high-level request to let Telegram |     # For now we make a high-level request to let Telegram | ||||||
|     # know we are still interested in receiving more updates. |     # know we are still interested in receiving more updates. | ||||||
|  | @ -627,8 +621,6 @@ class UpdateMethods: | ||||||
|         self._log[__name__].exception( |         self._log[__name__].exception( | ||||||
|             'Unhandled exception while getting update difference after reconnect') |             'Unhandled exception while getting update difference after reconnect') | ||||||
| 
 | 
 | ||||||
|     # endregion |  | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| class EventBuilderDict: | class EventBuilderDict: | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  | @ -88,11 +88,7 @@ def _resize_photo_if_needed( | ||||||
|             file.seek(before, io.SEEK_SET) |             file.seek(before, io.SEEK_SET) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class UploadMethods: | async def send_file( | ||||||
| 
 |  | ||||||
|     # region Public methods |  | ||||||
| 
 |  | ||||||
|     async def send_file( |  | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntityLike', |         entity: 'hints.EntityLike', | ||||||
|         file: 'typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]', |         file: 'typing.Union[hints.FileLike, typing.Sequence[hints.FileLike]]', | ||||||
|  | @ -118,223 +114,6 @@ class UploadMethods: | ||||||
|         comment_to: 'typing.Union[int, types.Message]' = None, |         comment_to: 'typing.Union[int, types.Message]' = None, | ||||||
|         ttl: int = None, |         ttl: int = None, | ||||||
|         **kwargs) -> 'types.Message': |         **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 |     # TODO Properly implement allow_cache to reuse the sha256 of the file | ||||||
|     # i.e. `None` was used |     # i.e. `None` was used | ||||||
|     if not file: |     if not file: | ||||||
|  | @ -411,7 +190,7 @@ class UploadMethods: | ||||||
|     ) |     ) | ||||||
|     return self._get_response_message(request, await self(request), entity) |     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, |                         progress_callback=None, reply_to=None, | ||||||
|                         parse_mode=(), silent=None, schedule=None, |                         parse_mode=(), silent=None, schedule=None, | ||||||
|                         supports_streaming=None, clear_draft=None, |                         supports_streaming=None, clear_draft=None, | ||||||
|  | @ -482,7 +261,7 @@ class UploadMethods: | ||||||
|     random_ids = [m.random_id for m in media] |     random_ids = [m.random_id for m in media] | ||||||
|     return self._get_response_message(random_ids, result, entity) |     return self._get_response_message(random_ids, result, entity) | ||||||
| 
 | 
 | ||||||
|     async def upload_file( | async def upload_file( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         file: 'hints.FileLike', |         file: 'hints.FileLike', | ||||||
|         *, |         *, | ||||||
|  | @ -493,80 +272,6 @@ class UploadMethods: | ||||||
|         key: bytes = None, |         key: bytes = None, | ||||||
|         iv: bytes = None, |         iv: bytes = None, | ||||||
|         progress_callback: 'hints.ProgressCallback' = None) -> 'types.TypeInputFile': |         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)): |     if isinstance(file, (types.InputFile, types.InputFileBig)): | ||||||
|         return file  # Already uploaded |         return file  # Already uploaded | ||||||
| 
 | 
 | ||||||
|  | @ -661,9 +366,8 @@ class UploadMethods: | ||||||
|             file_id, part_count, file_name, md5=hash_md5, size=file_size |             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, |         self, file, force_document=False, file_size=None, | ||||||
|         progress_callback=None, attributes=None, thumb=None, |         progress_callback=None, attributes=None, thumb=None, | ||||||
|         allow_cache=True, voice_note=False, video_note=False, |         allow_cache=True, voice_note=False, video_note=False, | ||||||
|  | @ -762,5 +466,3 @@ class UploadMethods: | ||||||
|             ttl_seconds=ttl |             ttl_seconds=ttl | ||||||
|         ) |         ) | ||||||
|     return file_handle, media, as_image |     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', sender, request, ordered=False, flood_sleep_threshold=None): | ||||||
|     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): |  | ||||||
|     if flood_sleep_threshold is None: |     if flood_sleep_threshold is None: | ||||||
|         flood_sleep_threshold = self.flood_sleep_threshold |         flood_sleep_threshold = self.flood_sleep_threshold | ||||||
|     requests = (request if utils.is_list_like(request) else (request,)) |     requests = (request if utils.is_list_like(request) else (request,)) | ||||||
|  | @ -130,9 +126,8 @@ class UserMethods: | ||||||
|     raise ValueError('Request was unsuccessful {} time(s)' |     raise ValueError('Request was unsuccessful {} time(s)' | ||||||
|                         .format(attempt)) |                         .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]': |         -> 'typing.Union[types.User, types.InputPeerUser]': | ||||||
|     """ |     """ | ||||||
|     Gets "me", the current :tl:`User` who is logged in. |     Gets "me", the current :tl:`User` who is logged in. | ||||||
|  | @ -171,8 +166,7 @@ class UserMethods: | ||||||
|     except errors.UnauthorizedError: |     except errors.UnauthorizedError: | ||||||
|         return None |         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. |     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 |     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. |     Return `True` if the signed-in user is a bot, `False` otherwise. | ||||||
| 
 | 
 | ||||||
|  | @ -198,7 +192,7 @@ class UserMethods: | ||||||
| 
 | 
 | ||||||
|     return self._bot |     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). |     Returns `True` if the user is authorized (logged in). | ||||||
| 
 | 
 | ||||||
|  | @ -220,7 +214,7 @@ class UserMethods: | ||||||
| 
 | 
 | ||||||
|     return self._authorized |     return self._authorized | ||||||
| 
 | 
 | ||||||
|     async def get_entity( | async def get_entity( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         entity: 'hints.EntitiesLike') -> 'hints.Entity': |         entity: 'hints.EntitiesLike') -> 'hints.Entity': | ||||||
|     """ |     """ | ||||||
|  | @ -343,7 +337,7 @@ class UserMethods: | ||||||
| 
 | 
 | ||||||
|     return result[0] if single else result |     return result[0] if single else result | ||||||
| 
 | 
 | ||||||
|     async def get_input_entity( | async def get_input_entity( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         peer: 'hints.EntityLike') -> 'types.TypeInputPeer': |         peer: 'hints.EntityLike') -> 'types.TypeInputPeer': | ||||||
|     """ |     """ | ||||||
|  | @ -470,11 +464,11 @@ class UserMethods: | ||||||
|         .format(peer, type(peer).__name__) |         .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)) |     i, cls = utils.resolve_id(await self.get_peer_id(peer)) | ||||||
|     return cls(i) |     return cls(i) | ||||||
| 
 | 
 | ||||||
|     async def get_peer_id( | async def get_peer_id( | ||||||
|         self: 'TelegramClient', |         self: 'TelegramClient', | ||||||
|         peer: 'hints.EntityLike', |         peer: 'hints.EntityLike', | ||||||
|         add_mark: bool = True) -> int: |         add_mark: bool = True) -> int: | ||||||
|  | @ -507,11 +501,8 @@ class UserMethods: | ||||||
| 
 | 
 | ||||||
|     return utils.get_peer_id(peer, add_mark=add_mark) |     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 |     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. |     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) |         '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 |     Returns a :tl:`InputDialogPeer`. This is a bit tricky because | ||||||
|     it may or not need access to the client to convert what's given |     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)) |     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 |     Returns a :tl:`InputNotifyPeer`. This is a bit tricky because | ||||||
|     it may or not need access to the client to convert what's given |     it may or not need access to the client to convert what's given | ||||||
|  | @ -607,5 +598,3 @@ class UserMethods: | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     return types.InputNotifyPeer(await self.get_input_entity(notify)) |     return types.InputNotifyPeer(await self.get_input_entity(notify)) | ||||||
| 
 |  | ||||||
|     # endregion |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user