mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-02-10 00:21:02 +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