2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-09-01 23:05:15 +00:00

Deep rewrite: preparing for v1.0

- Pyrogram core is now fully asynchronous
- Ditched Python 3.5, welcome 3.6 as minimum version.
- Moved all types to pyrogram.types
- Turned the Filters class into a module (filters)
- Moved all filters to pyrogram.filters
- Moved all handlers to pyrogram.handlers
- Moved all emoji to pyrogram.emoji
- Renamed pyrogram.api to pyrogram.raw
- Clock is now synced with server's time
- Telegram schema updated to Layer 117
- Greatly improved the TL compiler (proper type-constructor hierarchy)
- Added "do not edit" warning in generated files
- Crypto parts are executed in a thread pool to avoid blocking the event loop
- idle() is now a separate function (it doesn't deal with Client instances)
- Async storage, async filters and async progress callback (optional, can be sync too)
- Added getpass back, for hidden password inputs
This commit is contained in:
Dan
2020-08-22 08:05:05 +02:00
parent 2f0a1f4119
commit 538f1e3972
367 changed files with 12085 additions and 15090 deletions

View File

@@ -6,6 +6,7 @@ API calls. This section provides all the information you need in order to author
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@@ -41,7 +42,7 @@ keep the session alive, Pyrogram won't ask you again to enter your phone number.
.. important::
Your ``*.session`` files are personal and must be kept secret.
Your ``*.session`` file is personal and must be kept secret.
.. note::

View File

@@ -10,6 +10,7 @@ to control the behaviour of your application. Pyrogram errors all live inside th
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@@ -17,9 +18,8 @@ to control the behaviour of your application. Pyrogram errors all live inside th
RPCError
--------
The father of all errors is named ``RPCError``. This error exists in form of a Python exception which is directly
subclass-ed from Python's main ``Exception`` and is able to catch all Telegram API related errors. This error is raised
every time a method call against Telegram's API was unsuccessful.
The father of all errors is named ``RPCError`` and is able to catch all Telegram API related errors.
This error is raised every time a method call against Telegram's API was unsuccessful.
.. code-block:: python
@@ -34,19 +34,19 @@ Error Categories
----------------
The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram
provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the RPCError:
provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``:
.. code-block:: python
from pyrogram.errors import BadRequest, Forbidden, ...
- `303 - SeeOther <../api/errors#seeother>`_
- `400 - BadRequest <../api/errors#badrequest>`_
- `401 - Unauthorized <../api/errors#unauthorized>`_
- `403 - Forbidden <../api/errors#forbidden>`_
- `406 - NotAcceptable <../api/errors#notacceptable>`_
- `420 - Flood <../api/errors#flood>`_
- `500 - InternalServerError <../api/errors#internalservererror>`_
- :doc:`303 - SeeOther <../api/errors/see-other>`
- :doc:`400 - BadRequest <../api/errors/bad-request>`
- :doc:`401 - Unauthorized <../api/errors/unauthorized>`
- :doc:`403 - Forbidden <../api/errors/forbidden>`
- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>`
- :doc:`420 - Flood <../api/errors/flood>`
- :doc:`500 - InternalServerError <../api/errors/internal-server-error>`
Single Errors
-------------
@@ -59,7 +59,7 @@ issue. For example:
from pyrogram.errors import FloodWait
These errors subclass directly from the category of errors they belong to, which in turn subclass from the father
RPCError, thus building a class of error hierarchy such as this:
``RPCError``, thus building a class of error hierarchy such as this:
- RPCError
- BadRequest

View File

@@ -0,0 +1,61 @@
bot_keyboards
=============
This example will show you how to send normal and inline keyboards (as bot).
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
Any attempt in sending keyboards with a user account will be simply ignored by the server.
send_message() is used as example, but a keyboard can be sent with any other send_* methods,
like send_audio(), send_document(), send_location(), etc...
.. code-block:: python
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
with app:
app.send_message(
"haskell", # Edit this
"This is a ReplyKeyboardMarkup example",
reply_markup=ReplyKeyboardMarkup(
[
["A", "B", "C", "D"], # First row
["E", "F", "G"], # Second row
["H", "I"], # Third row
["J"] # Fourth row
],
resize_keyboard=True # Make the keyboard smaller
)
)
app.send_message(
"haskell", # Edit this
"This is a InlineKeyboardMarkup example",
reply_markup=InlineKeyboardMarkup(
[
[ # First row
InlineKeyboardButton( # Generates a callback query when pressed
"Button",
callback_data="data"
),
InlineKeyboardButton( # Opens a web URL
"URL",
url="https://docs.pyrogram.org"
),
],
[ # Second row
InlineKeyboardButton( # Opens the inline interface
"Choose chat",
switch_inline_query="pyrogram"
),
InlineKeyboardButton( # Opens the inline interface in the current chat
"Inline here",
switch_inline_query_current_chat="pyrogram"
)
]
]
)
)

View File

@@ -0,0 +1,19 @@
callback_queries
================
This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
.. code-block:: python
from pyrogram import Client
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_callback_query()
def answer(client, callback_query):
callback_query.answer(f"Button contains: '{callback_query.data}'", show_alert=True)
app.run() # Automatically start() and idle()

View File

@@ -0,0 +1,21 @@
echobot
=======
This simple echo bot replies to every private text message.
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
Filters.text and Filters.private to make sure it will reply to private text messages only.
.. code-block:: python
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
app.run() # Automatically start() and idle()

View File

@@ -0,0 +1,15 @@
get_chat_members
================
This example shows how to get all the members of a chat.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
with app:
for member in app.iter_chat_members(target):
print(member.user.first_name)

View File

@@ -0,0 +1,14 @@
get_dialogs
===========
This example shows how to get the full dialogs list (as user).
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
with app:
for dialog in app.iter_dialogs():
print(dialog.chat.title or dialog.chat.first_name)

View File

@@ -0,0 +1,15 @@
get_history
===========
This example shows how to get the full message history of a chat, starting from the latest message.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages)
with app:
for message in app.iter_history(target):
print(message.text)

View File

@@ -0,0 +1,21 @@
hello_world
===========
This example demonstrates a basic API usage
.. code-block:: python
from pyrogram import Client
# Create a new Client instance
app = Client("my_account")
with app:
# Send a message, Markdown is enabled by default
app.send_message("me", "Hi there! I'm using **Pyrogram**")
# Send a location
app.send_location("me", 51.500729, -0.124583)
# Send a sticker
app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")

View File

@@ -0,0 +1,46 @@
Examples
========
This page contains example scripts to show you how Pyrogram looks like.
Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste
and run. The only things you have to change are session names and target chats, where applicable.
The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough
to give you a basic idea.
-----
.. csv-table::
:header: Example, Description
:widths: auto
:align: center
:doc:`hello_world`, "Demonstration of basic API usage"
:doc:`echobot`, "Echo every private text message"
:doc:`welcomebot`, "The Welcome Bot in @PyrogramChat"
:doc:`get_history`, "Get the full message history of a chat"
:doc:`get_chat_members`, "Get all the members of a chat"
:doc:`get_dialogs`, "Get all of your dialog chats"
:doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses"
:doc:`inline_queries`, "Handle inline queries (as bot) and answer with results"
:doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat"
:doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots"
:doc:`raw_updates`, "Handle raw updates (old, should be avoided)"
For more advanced examples, see https://snippets.pyrogram.org.
.. toctree::
:hidden:
hello_world
echobot
welcomebot
get_history
get_chat_members
get_dialogs
callback_queries
inline_queries
use_inline_bots
bot_keyboards
raw_updates

View File

@@ -0,0 +1,61 @@
inline_queries
==============
This example shows how to handle inline queries.
Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi.
It uses the @on_inline_query decorator to register an InlineQueryHandler.
.. code-block:: python
from pyrogram import (
Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton
)
app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_inline_query()
def answer(client, inline_query):
inline_query.answer(
results=[
InlineQueryResultArticle(
title="Installation",
input_message_content=InputTextMessageContent(
"Here's how to install **Pyrogram**"
),
url="https://docs.pyrogram.org/intro/install",
description="How to install Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://docs.pyrogram.org/intro/install"
)]
]
)
),
InlineQueryResultArticle(
title="Usage",
input_message_content=InputTextMessageContent(
"Here's how to use **Pyrogram**"
),
url="https://docs.pyrogram.org/start/invoking",
description="How to use Pyrogram",
thumb_url="https://i.imgur.com/JyxrStE.png",
reply_markup=InlineKeyboardMarkup(
[
[InlineKeyboardButton(
"Open website",
url="https://docs.pyrogram.org/start/invoking"
)]
]
)
)
],
cache_time=1
)
app.run() # Automatically start() and idle()

View File

@@ -0,0 +1,18 @@
raw_updates
===========
This example shows how to handle raw updates.
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
@app.on_raw_update()
def raw(client, update, users, chats):
print(update)
app.run() # Automatically start() and idle()

View File

@@ -0,0 +1,18 @@
use_inline_bots
===============
This example shows how to query an inline bot (as user).
.. code-block:: python
from pyrogram import Client
# Create a new Client
app = Client("my_account")
with app:
# Get bot results for "Fuzz Universe" from the inline bot @vid
bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)

View File

@@ -0,0 +1,33 @@
welcomebot
==========
This is the Welcome Bot in @PyrogramChat.
It uses the Emoji module to easily add emojis in your text messages and Filters
to make it only work for specific messages in a specific chat.
.. code-block:: python
from pyrogram import Client, Emoji, Filters
TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MENTION = "[{}](tg://user?id={})" # User mention markup
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" # Welcome message
app = Client("my_account")
# Filter in only new_chat_members updates generated in TARGET chat
@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members)
def welcome(client, message):
# Build the new members list (with mentions) by using their first_name
new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members]
# Build the welcome message by using an emoji and the list we built above
text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members))
# Send the welcome message, without the web page preview
message.reply(text, disable_web_page_preview=True)
app.run() # Automatically start() and idle()

View File

@@ -6,6 +6,7 @@ account; we are now aiming towards the core of the library. It's time to start p
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@@ -13,7 +14,8 @@ account; we are now aiming towards the core of the library. It's time to start p
Basic Usage
-----------
Making API method calls with Pyrogram is very simple. Here's an example we are going to examine:
Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step and
then expand to explain what happens underneath:
.. code-block:: python
@@ -21,16 +23,13 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g
app = Client("my_account")
app.start()
with app:
app.send_message("me", "Hi!")
print(app.get_me())
app.send_message("me", "Hi, it's me!")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
Basic step-by-step
^^^^^^^^^^^^^^^^^^
app.stop()
#. Let's begin by importing the Client class from the Pyrogram package:
#. Let's begin by importing the Client class:
.. code-block:: python
@@ -42,35 +41,26 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g
app = Client("my_account")
#. To actually make use of any method, the client has to be started first:
#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client:
.. code-block:: python
app.start()
with app:
#. Now, you can call any method you like:
.. code-block:: python
print(app.get_me()) # Print information about yourself
# Send messages to yourself:
app.send_message("me", "Hi!") # Text message
app.send_location("me", 51.500729, -0.124583) # Location
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") # Sticker
#. Finally, when done, simply stop the client:
.. code-block:: python
app.stop()
app.send_message("me", "Hi!")
Context Manager
---------------
You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically
:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop` gracefully, even in case of unhandled exceptions in
your code. The example above can be therefore rewritten in a much nicer way:
The ``with`` statement starts a context manager, which is used as a shortcut to automatically call
:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work
properly. The context manager does also gracefully stop the client, even in case of unhandled exceptions in your code.
This is how Pyrogram looks without the context manager:
.. code-block:: python
@@ -78,10 +68,53 @@ your code. The example above can be therefore rewritten in a much nicer way:
app = Client("my_account")
with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI")
app.start()
app.send_message("me", "Hi!")
app.stop()
More examples can be found on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Asynchronous Calls
------------------
In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call
them with ``await``), use the asynchronous context manager:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
async def main():
async with app:
await app.send_message("me", "Hi!")
app.run(main())
Asynchronous step-by-step
^^^^^^^^^^^^^^^^^^^^^^^^^
#. Import the Client class and create an instance:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function;
here we define one and put our code inside; the context manager is also being used differently in asyncio and
method calls require the await keyword:
.. code-block:: python
async def main():
async with app:
await app.send_message("me", "Hi!")
#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's code. Using
:meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose
``asyncio.get_event_loop().run_until_complete(main())``:
.. code-block:: python
app.run(main())

View File

@@ -6,6 +6,7 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha
.. contents:: Contents
:backlinks: none
:depth: 1
:local:
-----
@@ -24,72 +25,14 @@ function will be called back by the framework and its body executed.
Registering a Handler
---------------------
To explain how handlers work let's have a look at the most used one, the :class:`~pyrogram.MessageHandler`, which will
be in charge for handling :class:`~pyrogram.Message` updates coming from all around your chats. Every other handler shares
the same setup logic; you should not have troubles settings them up once you learn from this section.
Using add_handler()
-------------------
The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback
function and registers it in your Client. Here's a full example that prints out the content of a message as soon as it
arrives:
.. code-block:: python
from pyrogram import Client, MessageHandler
def my_function(client, message):
print(message)
app = Client("my_account")
my_handler = MessageHandler(my_function)
app.add_handler(my_handler)
app.run()
Let's examine these four new pieces.
#. A callback function we defined which accepts two arguments -
*(client, message)*. This will be the function that gets executed every time a new message arrives and Pyrogram will
call that function by passing the client instance and the new message instance as argument.
.. code-block:: python
def my_function(client, message):
print(message)
#. The :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must only handle
updates that are in form of a :class:`~pyrogram.Message`:
.. code-block:: python
my_handler = MessageHandler(my_function)
#. The method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let
Pyrogram know it needs to be taken into consideration when new updates arrive and the internal dispatching phase
begins.
.. code-block:: python
app.add_handler(my_handler)
#. The :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and
a special method :meth:`~pyrogram.Client.idle` that keeps your main scripts alive until you press ``CTRL+C``; the
client will be automatically stopped after that.
.. code-block:: python
app.run()
To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have
troubles settings them up once you learn from this section.
Using Decorators
----------------
^^^^^^^^^^^^^^^^
All of the above will become quite verbose, especially in case you have lots of handlers to register. A much nicer way
to do so is by decorating your callback function with the :meth:`~pyrogram.Client.on_message` decorator.
The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator:
.. code-block:: python
@@ -100,7 +43,58 @@ to do so is by decorating your callback function with the :meth:`~pyrogram.Clien
@app.on_message()
def my_handler(client, message):
print(message)
message.forward("me")
app.run()
The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets
executed every time a new message arrives.
Asynchronous handlers
^^^^^^^^^^^^^^^^^^^^^
You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API
methods by placing ``await`` in front of them:
.. code-block:: python
@app.on_message()
async def my_handler(client, message):
await message.forward("me")
.. note::
You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and
efficiently regardless of what you choose.
Using add_handler()
^^^^^^^^^^^^^^^^^^^
The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback
function and registers it in your Client. It us useful in case you want to programmatically add handlers (or in case,
for some reason, you don't like to use decorators).
.. code-block:: python
from pyrogram import Client
from pyrogram.handlers import MessageHandler
def my_function(client, message):
message.forward("me")
app = Client("my_account")
my_handler = MessageHandler(my_function)
app.add_handler(my_handler)
app.run()
The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`:
.. code-block:: python
async def my_function(client, message):
await message.forward("me")