mirror of
https://github.com/pyrogram/pyrogram
synced 2025-09-07 17:55:24 +00:00
Merge branch 'develop' into other_start
This commit is contained in:
@@ -174,6 +174,17 @@ class Client(Methods, BaseClient):
|
||||
download_media, ...) are less prone to throw FloodWait exceptions.
|
||||
Only available for users, bots will ignore this parameter.
|
||||
Defaults to False (normal session).
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
with app:
|
||||
app.send_message("me", "Hi!")
|
||||
|
||||
"""
|
||||
|
||||
terms_of_service_displayed = False
|
||||
@@ -619,11 +630,28 @@ class Client(Methods, BaseClient):
|
||||
return self
|
||||
|
||||
def start(self):
|
||||
"""Start the Client.
|
||||
"""Start the client.
|
||||
|
||||
This method connects the client to Telegram and, in case of new sessions, automatically manages the full login
|
||||
process using an interactive prompt (by default).
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ConnectionError: In case you try to start an already started Client.
|
||||
ConnectionError: In case you try to start an already started client.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 4
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
... # Call API methods
|
||||
|
||||
app.stop()
|
||||
"""
|
||||
if self.is_started:
|
||||
raise ConnectionError("Client has already been started")
|
||||
@@ -696,8 +724,25 @@ class Client(Methods, BaseClient):
|
||||
def stop(self):
|
||||
"""Stop the Client.
|
||||
|
||||
This method disconnects the client from Telegram and stops the underlying tasks.
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Raises:
|
||||
ConnectionError: In case you try to stop an already stopped Client.
|
||||
ConnectionError: In case you try to stop an already stopped client.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
... # Call API methods
|
||||
|
||||
app.stop()
|
||||
"""
|
||||
if not self.is_started:
|
||||
raise ConnectionError("Client is already stopped")
|
||||
@@ -738,64 +783,125 @@ class Client(Methods, BaseClient):
|
||||
def restart(self):
|
||||
"""Restart the Client.
|
||||
|
||||
This method will first call :meth:`~Client.stop` and then :meth:`~Client.start` in a row in order to restart
|
||||
a client using a single method.
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Raises:
|
||||
ConnectionError: In case you try to restart a stopped Client.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
... # Call API methods
|
||||
|
||||
app.restart()
|
||||
|
||||
... # Call other API methods
|
||||
|
||||
app.stop()
|
||||
"""
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
|
||||
"""Block the main script execution until a signal (e.g.: from CTRL+C) is received.
|
||||
Once the signal is received, the client will automatically stop and the main script will continue its execution.
|
||||
@staticmethod
|
||||
def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
|
||||
"""Block the main script execution until a signal is received.
|
||||
|
||||
This is used after starting one or more clients and is useful for event-driven applications only, that are,
|
||||
applications which react upon incoming Telegram updates through handlers, rather than executing a set of methods
|
||||
sequentially.
|
||||
This static method will run an infinite loop in order to block the main script execution and prevent it from
|
||||
exiting while having client(s) that are still running in the background.
|
||||
|
||||
The way Pyrogram works, will keep your handlers in a pool of workers, which are executed concurrently outside
|
||||
the main script; calling idle() will ensure the client(s) will be kept alive by not letting the main script to
|
||||
end, until you decide to quit.
|
||||
It is useful for event-driven application only, that are, applications which react upon incoming Telegram
|
||||
updates through handlers, rather than executing a set of methods sequentially.
|
||||
|
||||
The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently
|
||||
outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main
|
||||
script to end, until you decide to quit.
|
||||
|
||||
Once a signal is received (e.g.: from CTRL+C) the inner infinite loop will break and your main script will
|
||||
continue. Don't forget to call :meth:`~Client.stop` for each running client before the script ends.
|
||||
|
||||
Parameters:
|
||||
stop_signals (``tuple``, *optional*):
|
||||
Iterable containing signals the signal handler will listen to.
|
||||
Defaults to (SIGINT, SIGTERM, SIGABRT).
|
||||
Defaults to *(SIGINT, SIGTERM, SIGABRT)*.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 13
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app1 = Client("account1")
|
||||
app2 = Client("account2")
|
||||
app3 = Client("account3")
|
||||
|
||||
... # Set handlers up
|
||||
|
||||
app1.start()
|
||||
app2.start()
|
||||
app3.start()
|
||||
|
||||
Client.idle()
|
||||
|
||||
app1.stop()
|
||||
app2.stop()
|
||||
app3.stop()
|
||||
"""
|
||||
|
||||
# TODO: Maybe make this method static and don't automatically stop
|
||||
|
||||
def signal_handler(*args):
|
||||
self.is_idle = False
|
||||
Client.is_idling = False
|
||||
|
||||
for s in stop_signals:
|
||||
signal(s, signal_handler)
|
||||
|
||||
self.is_idle = True
|
||||
Client.is_idling = True
|
||||
|
||||
while self.is_idle:
|
||||
while Client.is_idling:
|
||||
time.sleep(1)
|
||||
|
||||
self.stop()
|
||||
|
||||
def run(self):
|
||||
"""Start the Client and automatically idle the main script.
|
||||
"""Start the client, idle the main script and finally stop the client.
|
||||
|
||||
This is a convenience method that literally just calls :meth:`~Client.start` and :meth:`~Client.idle`. It makes
|
||||
running a client less verbose, but is not suitable in case you want to run more than one client in a single main
|
||||
script, since :meth:`~Client.idle` will block.
|
||||
This is a convenience method that calls :meth:`~Client.start`, :meth:`~Client.idle` and :meth:`~Client.stop` in
|
||||
sequence. It makes running a client less verbose, but is not suitable in case you want to run more than one
|
||||
client in a single main script, since idle() will block after starting the own client.
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
ConnectionError: In case you try to run an already started client.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 7
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
... # Set handlers up
|
||||
|
||||
app.run()
|
||||
"""
|
||||
self.start()
|
||||
self.idle()
|
||||
Client.idle()
|
||||
self.stop()
|
||||
|
||||
def add_handler(self, handler: Handler, group: int = 0):
|
||||
"""Register an update handler.
|
||||
|
||||
You can register multiple handlers, but at most one handler within a group
|
||||
will be used for a single update. To handle the same update more than once, register
|
||||
your handler using a different group id (lower group id == higher priority).
|
||||
You can register multiple handlers, but at most one handler within a group will be used for a single update.
|
||||
To handle the same update more than once, register your handler using a different group id (lower group id
|
||||
== higher priority). This mechanism is explained in greater details at
|
||||
:doc:`More on Updates <../../topics/more-on-updates>`.
|
||||
|
||||
Parameters:
|
||||
handler (``Handler``):
|
||||
@@ -805,7 +911,22 @@ class Client(Methods, BaseClient):
|
||||
The group identifier, defaults to 0.
|
||||
|
||||
Returns:
|
||||
``tuple``: A tuple consisting of (handler, group).
|
||||
``tuple``: A tuple consisting of *(handler, group)*.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 8
|
||||
|
||||
from pyrogram import Client, MessageHandler
|
||||
|
||||
def dump(client, message):
|
||||
print(message)
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
app.add_handler(MessageHandler(dump))
|
||||
|
||||
app.run()
|
||||
"""
|
||||
if isinstance(handler, DisconnectHandler):
|
||||
self.disconnect_handler = handler.callback
|
||||
@@ -817,9 +938,8 @@ class Client(Methods, BaseClient):
|
||||
def remove_handler(self, handler: Handler, group: int = 0):
|
||||
"""Remove a previously-registered update handler.
|
||||
|
||||
Make sure to provide the right group that the handler was added in. You can use
|
||||
the return value of the :meth:`~Client.add_handler` method, a tuple of (handler, group), and
|
||||
pass it directly.
|
||||
Make sure to provide the right group where the handler was added in. You can use the return value of the
|
||||
:meth:`~Client.add_handler` method, a tuple of *(handler, group)*, and pass it directly.
|
||||
|
||||
Parameters:
|
||||
handler (``Handler``):
|
||||
@@ -827,6 +947,24 @@ class Client(Methods, BaseClient):
|
||||
|
||||
group (``int``, *optional*):
|
||||
The group identifier, defaults to 0.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 11
|
||||
|
||||
from pyrogram import Client, MessageHandler
|
||||
|
||||
def dump(client, message):
|
||||
print(message)
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
handler = app.add_handler(MessageHandler(dump))
|
||||
|
||||
# Starred expression to unpack (handler, group)
|
||||
app.remove_handler(*handler)
|
||||
|
||||
app.run()
|
||||
"""
|
||||
if isinstance(handler, DisconnectHandler):
|
||||
self.disconnect_handler = None
|
||||
@@ -835,10 +973,109 @@ class Client(Methods, BaseClient):
|
||||
|
||||
def stop_transmission(self):
|
||||
"""Stop downloading or uploading a file.
|
||||
Must be called inside a progress callback function.
|
||||
|
||||
This method must be called inside a progress callback function in order to stop the transmission at the
|
||||
desired time. The progress callback is called every time a file chunk is uploaded/downloaded.
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 9
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
# Example to stop transmission once the upload progress reaches 50%
|
||||
# Useless in practice, but shows how to stop on command
|
||||
def progress(client, current, total):
|
||||
if (current * 100 / total) > 50:
|
||||
client.stop_transmission()
|
||||
|
||||
with app:
|
||||
app.send_document("me", "files.zip", progress=progress)
|
||||
"""
|
||||
raise Client.StopTransmission
|
||||
|
||||
def export_session_string(self):
|
||||
"""Export the current authorized session as a serialized string.
|
||||
|
||||
Session strings are useful for storing in-memory authorized sessions in a portable, serialized string.
|
||||
More detailed information about session strings can be found at the dedicated page of
|
||||
:doc:`Storage Engines <../../topics/storage-engines>`.
|
||||
|
||||
Has no parameters.
|
||||
|
||||
Returns:
|
||||
``str``: The session serialized into a printable, url-safe string.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 6
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
with app:
|
||||
print(app.export_session_string())
|
||||
"""
|
||||
return self.storage.export_session_string()
|
||||
|
||||
def set_parse_mode(self, parse_mode: Union[str, None] = "combined"):
|
||||
"""Set the parse mode to be used globally by the client.
|
||||
|
||||
When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the
|
||||
global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and
|
||||
combined together.
|
||||
|
||||
Parameters:
|
||||
parse_mode (``str``):
|
||||
The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"*
|
||||
to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser
|
||||
completely.
|
||||
|
||||
Raises:
|
||||
ValueError: In case the provided *parse_mode* is not a valid parse mode.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 10,14,18,22
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
|
||||
with app:
|
||||
# Default combined mode: Markdown + HTML
|
||||
app.send_message("haskell", "1. **markdown** and <i>html</i>")
|
||||
|
||||
# Force Markdown-only, HTML is disabled
|
||||
app.set_parse_mode("markdown")
|
||||
app.send_message("haskell", "2. **markdown** and <i>html</i>")
|
||||
|
||||
# Force HTML-only, Markdown is disabled
|
||||
app.set_parse_mode("html")
|
||||
app.send_message("haskell", "3. **markdown** and <i>html</i>")
|
||||
|
||||
# Disable the parser completely
|
||||
app.set_parse_mode(None)
|
||||
app.send_message("haskell", "4. **markdown** and <i>html</i>")
|
||||
|
||||
# Bring back the default combined mode
|
||||
app.set_parse_mode()
|
||||
app.send_message("haskell", "5. **markdown** and <i>html</i>")
|
||||
"""
|
||||
|
||||
if parse_mode not in self.PARSE_MODES:
|
||||
raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format(
|
||||
", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]),
|
||||
parse_mode
|
||||
))
|
||||
|
||||
self.parse_mode = parse_mode
|
||||
|
||||
def authorize_bot(self):
|
||||
try:
|
||||
r = self.send(
|
||||
@@ -1128,7 +1365,7 @@ class Client(Methods, BaseClient):
|
||||
access_hash = 0
|
||||
peer_type = "group"
|
||||
elif isinstance(peer, (types.Channel, types.ChannelForbidden)):
|
||||
peer_id = int("-100" + str(peer.id))
|
||||
peer_id = utils.get_channel_id(peer.id)
|
||||
access_hash = peer.access_hash
|
||||
|
||||
username = getattr(peer, "username", None)
|
||||
@@ -1244,7 +1481,7 @@ class Client(Methods, BaseClient):
|
||||
try:
|
||||
diff = self.send(
|
||||
functions.updates.GetChannelDifference(
|
||||
channel=self.resolve_peer(int("-100" + str(channel_id))),
|
||||
channel=self.resolve_peer(utils.get_channel_id(channel_id)),
|
||||
filter=types.ChannelMessagesFilter(
|
||||
ranges=[types.MessageRange(
|
||||
min_id=update.message.id,
|
||||
@@ -1420,7 +1657,7 @@ class Client(Methods, BaseClient):
|
||||
])
|
||||
|
||||
if session_empty:
|
||||
self.storage.dc_id = 1
|
||||
self.storage.dc_id = 4
|
||||
self.storage.date = 0
|
||||
|
||||
self.storage.test_mode = self.test_mode
|
||||
@@ -1456,7 +1693,7 @@ class Client(Methods, BaseClient):
|
||||
for name in vars(module).keys():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name).pyrogram_plugin
|
||||
handler, group = getattr(module, name).handler
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.add_handler(handler, group)
|
||||
@@ -1491,7 +1728,7 @@ class Client(Methods, BaseClient):
|
||||
for name in handlers:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name).pyrogram_plugin
|
||||
handler, group = getattr(module, name).handler
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.add_handler(handler, group)
|
||||
@@ -1529,7 +1766,7 @@ class Client(Methods, BaseClient):
|
||||
for name in handlers:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
handler, group = getattr(module, name).pyrogram_plugin
|
||||
handler, group = getattr(module, name).handler
|
||||
|
||||
if isinstance(handler, Handler) and isinstance(group, int):
|
||||
self.remove_handler(handler, group)
|
||||
@@ -1632,33 +1869,38 @@ class Client(Methods, BaseClient):
|
||||
except KeyError:
|
||||
raise PeerIdInvalid
|
||||
|
||||
if peer_id > 0:
|
||||
peer_type = utils.get_type(peer_id)
|
||||
|
||||
if peer_type == "user":
|
||||
self.fetch_peers(
|
||||
self.send(
|
||||
functions.users.GetUsers(
|
||||
id=[types.InputUser(
|
||||
user_id=peer_id,
|
||||
access_hash=0
|
||||
)]
|
||||
id=[
|
||||
types.InputUser(
|
||||
user_id=peer_id,
|
||||
access_hash=0
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
elif peer_type == "chat":
|
||||
self.send(
|
||||
functions.messages.GetChats(
|
||||
id=[-peer_id]
|
||||
)
|
||||
)
|
||||
else:
|
||||
if str(peer_id).startswith("-100"):
|
||||
self.send(
|
||||
functions.channels.GetChannels(
|
||||
id=[types.InputChannel(
|
||||
channel_id=int(str(peer_id)[4:]),
|
||||
self.send(
|
||||
functions.channels.GetChannels(
|
||||
id=[
|
||||
types.InputChannel(
|
||||
channel_id=utils.get_channel_id(peer_id),
|
||||
access_hash=0
|
||||
)]
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.send(
|
||||
functions.messages.GetChats(
|
||||
id=[-peer_id]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
return self.storage.get_peer_by_id(peer_id)
|
||||
@@ -1693,23 +1935,22 @@ class Client(Methods, BaseClient):
|
||||
In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk.
|
||||
|
||||
progress (``callable``, *optional*):
|
||||
Pass a callback function to view the upload progress.
|
||||
The function must take *(client, current, total, \*args)* as positional arguments (look at the section
|
||||
below for a detailed description).
|
||||
Pass a callback function to view the file transmission progress.
|
||||
The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
|
||||
detailed description) and will be called back each time a new file chunk has been successfully
|
||||
transmitted.
|
||||
|
||||
progress_args (``tuple``, *optional*):
|
||||
Extra custom arguments for the progress callback function. Useful, for example, if you want to pass
|
||||
a chat_id and a message_id in order to edit a message with the updated progress.
|
||||
Extra custom arguments for the progress callback function.
|
||||
You can pass anything you need to be available in the progress callback scope; for example, a Message
|
||||
object or a Client instance in order to edit the message with the updated progress status.
|
||||
|
||||
Other Parameters:
|
||||
client (:obj:`Client`):
|
||||
The Client itself, useful when you want to call other API methods inside the callback function.
|
||||
|
||||
current (``int``):
|
||||
The amount of bytes uploaded so far.
|
||||
The amount of bytes transmitted so far.
|
||||
|
||||
total (``int``):
|
||||
The size of the file.
|
||||
The total size of the file.
|
||||
|
||||
*args (``tuple``, *optional*):
|
||||
Extra custom arguments as defined in the *progress_args* parameter.
|
||||
@@ -2023,11 +2264,3 @@ class Client(Methods, BaseClient):
|
||||
|
||||
if extensions:
|
||||
return extensions.split(" ")[0]
|
||||
|
||||
def export_session_string(self):
|
||||
"""Export the current session as serialized string.
|
||||
|
||||
Returns:
|
||||
``str``: The session serialized into a printable, url-safe string.
|
||||
"""
|
||||
return self.storage.export_session_string()
|
||||
|
Reference in New Issue
Block a user