mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-29 13:27:47 +00:00
Merge branch 'develop' into asyncio
This commit is contained in:
commit
d8d72395b7
@ -81,6 +81,8 @@ def start():
|
||||
|
||||
sub_classes = []
|
||||
|
||||
f_all.write(" \"_\": \"{}\",\n".format(super_class))
|
||||
|
||||
for j, row in enumerate(reader):
|
||||
if j == 0:
|
||||
continue
|
||||
@ -90,13 +92,13 @@ def start():
|
||||
if not row: # Row is empty (blank line)
|
||||
continue
|
||||
|
||||
id, message = row
|
||||
error_id, error_message = row
|
||||
|
||||
sub_class = caml(re.sub(r"_X", "_", id))
|
||||
sub_class = caml(re.sub(r"_X", "_", error_id))
|
||||
|
||||
f_all.write(" \"{}\": \"{}\",\n".format(id, sub_class))
|
||||
f_all.write(" \"{}\": \"{}\",\n".format(error_id, sub_class))
|
||||
|
||||
sub_classes.append((sub_class, id, message))
|
||||
sub_classes.append((sub_class, error_id, error_message))
|
||||
|
||||
with open("{}/template/class.txt".format(HOME), "r", encoding="utf-8") as f_class_template:
|
||||
class_template = f_class_template.read()
|
||||
|
@ -1,7 +1,11 @@
|
||||
Pyrogram Client
|
||||
===============
|
||||
|
||||
This is the Client class. It exposes high-level methods for an easy access to the API.
|
||||
You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client
|
||||
class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be
|
||||
found starting from this page.
|
||||
|
||||
This page is about the Client class, which exposes high-level methods for an easy access to the API.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1-3
|
||||
|
@ -6,7 +6,7 @@ deserve a dedicated page.
|
||||
|
||||
Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to
|
||||
:doc:`Handlers <handlers>`; they do so by instantiating the correct handler and calling
|
||||
:meth:`~pyrogram.Client.add_handler`, automatically. All you need to do is adding the decorators on top of your
|
||||
:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your
|
||||
functions.
|
||||
|
||||
.. code-block:: python
|
||||
|
@ -2,10 +2,7 @@ Update Handlers
|
||||
===============
|
||||
|
||||
Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions.
|
||||
|
||||
For a much more convenient way of registering callback functions have a look at :doc:`Decorators <decorators>` instead.
|
||||
In case you decided to manually create a handler, use :class:`~pyrogram.Client.add_handler` to register
|
||||
it.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1, 10
|
||||
|
@ -1,7 +1,7 @@
|
||||
Available Methods
|
||||
=================
|
||||
|
||||
All Pyrogram methods listed here are bound to a :class:`~pyrogram.Client` instance.
|
||||
This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 6
|
||||
|
@ -1,7 +1,7 @@
|
||||
Available Types
|
||||
===============
|
||||
|
||||
All Pyrogram types listed here are accessible through the main package directly.
|
||||
This page is about Pyrogram types. All types listed here are accessible through the main package directly.
|
||||
|
||||
.. code-block:: python
|
||||
:emphasize-lines: 1
|
||||
|
@ -28,8 +28,8 @@ Welcome to Pyrogram
|
||||
api/bound-methods
|
||||
api/handlers
|
||||
api/decorators
|
||||
api/filters
|
||||
api/errors
|
||||
api/filters
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
@ -28,7 +28,7 @@ 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 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
|
||||
|
||||
@ -71,14 +71,22 @@ RPCError, thus building a class of error hierarchy such as this:
|
||||
Unknown Errors
|
||||
--------------
|
||||
|
||||
In case Pyrogram does not know anything yet about a specific error, it raises a special ``520 - UnknownError`` exception
|
||||
and logs it in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors.
|
||||
In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category,
|
||||
for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the
|
||||
whole category of errors and be sure to also handle these unknown errors.
|
||||
|
||||
In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special
|
||||
``520 UnknownError`` exception.
|
||||
|
||||
In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report
|
||||
these unknown errors in the `discussion group <https://t.me/pyrogram>`_.
|
||||
|
||||
Errors with Values
|
||||
------------------
|
||||
|
||||
Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you
|
||||
have to wait before you can try again. The value is always stored in the ``x`` field of the returned exception object:
|
||||
have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on.
|
||||
The value is stored in the ``x`` attribute of the exception object:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -88,4 +96,4 @@ have to wait before you can try again. The value is always stored in the ``x`` f
|
||||
try:
|
||||
...
|
||||
except FloodWait as e:
|
||||
time.sleep(e.x) # Wait before trying again
|
||||
time.sleep(e.x) # Wait "x" seconds before continuing
|
||||
|
@ -45,7 +45,9 @@ arrives:
|
||||
|
||||
app.run()
|
||||
|
||||
#. Let's examine these four new pieces. First one: a callback function we defined which accepts two arguments -
|
||||
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.
|
||||
|
||||
@ -54,14 +56,14 @@ arrives:
|
||||
def my_function(client, message):
|
||||
print(message)
|
||||
|
||||
#. Second one: 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`:
|
||||
#. 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)
|
||||
|
||||
#. Third: the method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let
|
||||
#. 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.
|
||||
|
||||
@ -69,7 +71,7 @@ arrives:
|
||||
|
||||
app.add_handler(my_handler)
|
||||
|
||||
#. Last one, the :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and
|
||||
#. 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.
|
||||
|
||||
|
@ -24,7 +24,7 @@ button:
|
||||
|
||||
app.send_message(
|
||||
"username", # Change this to your username or id
|
||||
"Pyrogram's custom filter test",
|
||||
"Pyrogram custom filter test",
|
||||
reply_markup=InlineKeyboardMarkup(
|
||||
[[InlineKeyboardButton("Press me", "pyrogram")]]
|
||||
)
|
||||
@ -33,61 +33,54 @@ button:
|
||||
Basic Filters
|
||||
-------------
|
||||
|
||||
For this basic filter we will be using only the first two parameters of :meth:`~pyrogram.Filters.create`.
|
||||
For this basic filter we will be using only the first parameter of :meth:`~pyrogram.Filters.create`.
|
||||
|
||||
The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries
|
||||
containing "Pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data
|
||||
equals to ``"Pyrogram"``.
|
||||
containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data
|
||||
equals to ``"pyrogram"``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
static_data = Filters.create(
|
||||
name="StaticdData",
|
||||
func=lambda flt, query: query.data == "Pyrogram"
|
||||
)
|
||||
static_data_filter = Filters.create(lambda _, query: query.data == "pyrogram")
|
||||
|
||||
The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same
|
||||
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope:
|
||||
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter scope:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def func(flt, query):
|
||||
return query.data == "Pyrogram"
|
||||
def func(_, query):
|
||||
return query.data == "pyrogram"
|
||||
|
||||
static_data = Filters.create(
|
||||
name="StaticData",
|
||||
func=func
|
||||
)
|
||||
static_data_filter = Filters.create(func)
|
||||
|
||||
The filter usage remains the same:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.on_callback_query(static_data)
|
||||
@app.on_callback_query(static_data_filter)
|
||||
def pyrogram_data(_, query):
|
||||
query.answer("it works!")
|
||||
|
||||
Filters with Arguments
|
||||
----------------------
|
||||
|
||||
A much cooler filter would be one that accepts "Pyrogram" or any other data as argument at usage time.
|
||||
A dynamic filter like this will make use of the third parameter of :meth:`~pyrogram.Filters.create`.
|
||||
A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time.
|
||||
A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.Filters.create` method.
|
||||
|
||||
This is how a dynamic custom filter looks like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def dynamic_data(data):
|
||||
def dynamic_data_filter(data):
|
||||
return Filters.create(
|
||||
name="DynamicData",
|
||||
func=lambda flt, query: flt.data == query.data,
|
||||
data=data # "data" kwarg is accessed with "flt.data"
|
||||
lambda flt, query: flt.data == query.data,
|
||||
data=data # "data" kwarg is accessed with "flt.data" above
|
||||
)
|
||||
|
||||
And its usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@app.on_callback_query(dynamic_data("Pyrogram"))
|
||||
@app.on_callback_query(dynamic_data_filter("pyrogram"))
|
||||
def pyrogram_data(_, query):
|
||||
query.answer("it works!")
|
||||
|
@ -95,5 +95,5 @@ engine to properly work as intended.
|
||||
|
||||
But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare
|
||||
minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major
|
||||
contributor to the overall length. Needless to repeat that this string, as well as any other session storage, represent
|
||||
contributor to the overall length. Needless to say that this string, as well as any other session storage, represent
|
||||
strictly personal data. Keep them safe.
|
||||
|
@ -1,40 +1,99 @@
|
||||
Text Formatting
|
||||
===============
|
||||
|
||||
Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text
|
||||
messages and media captions.
|
||||
.. role:: strike
|
||||
:class: strike
|
||||
|
||||
Markdown style uses the same syntax as Telegram Desktop's and is enabled by default.
|
||||
.. role:: underline
|
||||
:class: underline
|
||||
|
||||
Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**.
|
||||
.. role:: bold-underline
|
||||
:class: bold-underline
|
||||
|
||||
.. role:: strike-italic
|
||||
:class: strike-italic
|
||||
|
||||
Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled
|
||||
texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great
|
||||
variety of decorations that can also be nested in order to combine multiple styles together.
|
||||
|
||||
Basic Styles
|
||||
------------
|
||||
|
||||
When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a
|
||||
list of the basic styles currently supported by Pyrogram.
|
||||
|
||||
- **bold**
|
||||
- *italic*
|
||||
- :strike:`strike`
|
||||
- :underline:`underline`
|
||||
- `text URL <https://pyrogram.org>`_
|
||||
- `user text mention <https://t.me/haskell>`_
|
||||
- ``inline fixed-width code``
|
||||
- .. code-block:: text
|
||||
|
||||
pre-formatted
|
||||
fixed-width
|
||||
code block
|
||||
|
||||
.. note::
|
||||
|
||||
User text mentions are only guaranteed to work if you have already met the user (in groups or private chats).
|
||||
|
||||
Markdown Style
|
||||
--------------
|
||||
|
||||
To use this mode, pass "markdown" in the *parse_mode* field when using
|
||||
To strictly use this mode, pass "markdown" to the *parse_mode* parameter when using
|
||||
:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
**bold text**
|
||||
**bold**
|
||||
|
||||
__italic text__
|
||||
__italic__
|
||||
|
||||
[inline URL](https://docs.pyrogram.org/)
|
||||
--underline--
|
||||
|
||||
[inline mention of a user](tg://user?id=23122162)
|
||||
~~strike~~
|
||||
|
||||
[text URL](https://docs.pyrogram.org/)
|
||||
|
||||
[text user mention](tg://user?id=23122162)
|
||||
|
||||
`inline fixed-width code`
|
||||
|
||||
```block_language
|
||||
pre-formatted fixed-width code block
|
||||
```
|
||||
pre-formatted
|
||||
fixed-width
|
||||
code block
|
||||
```
|
||||
|
||||
**Example**:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.send_message(
|
||||
"haskell",
|
||||
(
|
||||
"**bold**, "
|
||||
"__italic__, "
|
||||
"--underline--, "
|
||||
"~~strikethrough~~, "
|
||||
"[mention](tg://user?id=23122162), "
|
||||
"[URL](https://pyrogram.org), "
|
||||
"`code`, "
|
||||
"```"
|
||||
"for i in range(10):\n"
|
||||
" print(i)"
|
||||
"```"
|
||||
),
|
||||
parse_mode="markdown"
|
||||
)
|
||||
|
||||
HTML Style
|
||||
----------
|
||||
|
||||
To use this mode, pass "html" in the *parse_mode* field when using :meth:`~pyrogram.Client.send_message`.
|
||||
To strictly use this mode, pass "html" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`.
|
||||
The following tags are currently supported:
|
||||
|
||||
.. code-block:: text
|
||||
@ -43,55 +102,124 @@ The following tags are currently supported:
|
||||
|
||||
<i>italic</i>, <em>italic</em>
|
||||
|
||||
<a href="http://docs.pyrogram.org/">inline URL</a>
|
||||
<u>underline</u>
|
||||
|
||||
<a href="tg://user?id=23122162">inline mention of a user</a>
|
||||
<s>strike</s>, <del>strike</del>, <strike>strike</strike>
|
||||
|
||||
<a href="http://docs.pyrogram.org/">text URL</a>
|
||||
|
||||
<a href="tg://user?id=23122162">inline mention</a>
|
||||
|
||||
<code>inline fixed-width code</code>
|
||||
|
||||
<pre>pre-formatted fixed-width code block</pre>
|
||||
<pre>
|
||||
pre-formatted
|
||||
fixed-width
|
||||
code block
|
||||
</pre>
|
||||
|
||||
.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats).
|
||||
**Example**:
|
||||
|
||||
Examples
|
||||
--------
|
||||
.. code-block:: python
|
||||
|
||||
- Markdown:
|
||||
app.send_message(
|
||||
"haskell",
|
||||
(
|
||||
"<b>bold</b>, "
|
||||
"<i>italic</i>, "
|
||||
"<u>underline</u>, "
|
||||
"<s>strikethrough</s>, "
|
||||
"<a href=\"tg://user?id=23122162\">mention</a>, "
|
||||
"<a href=\"https://pyrogram.org/\">URL</a>, "
|
||||
"<code>code</code>\n\n"
|
||||
"<pre>"
|
||||
"for i in range(10):\n"
|
||||
" print(i)"
|
||||
"</pre>"
|
||||
),
|
||||
parse_mode="html"
|
||||
)
|
||||
|
||||
.. note::
|
||||
|
||||
All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the
|
||||
corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this
|
||||
snippet to quickly escape those characters:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.send_message(
|
||||
chat_id="haskell",
|
||||
text=(
|
||||
"**bold**, "
|
||||
"__italic__, "
|
||||
"[mention](tg://user?id=23122162), "
|
||||
"[URL](https://docs.pyrogram.org), "
|
||||
"`code`, "
|
||||
"```"
|
||||
"for i in range(10):\n"
|
||||
" print(i)```"
|
||||
)
|
||||
)
|
||||
import html
|
||||
|
||||
- HTML:
|
||||
text = "<my text>"
|
||||
text = html.escape(text)
|
||||
|
||||
.. code-block:: python
|
||||
print(text)
|
||||
|
||||
app.send_message(
|
||||
chat_id="haskell",
|
||||
text=(
|
||||
"<b>bold</b>, "
|
||||
"<i>italic</i>, "
|
||||
"<a href=\"tg://user?id=23122162\">mention</a>, "
|
||||
"<a href=\"https://pyrogram.org/\">URL</a>, "
|
||||
"<code>code</code>, "
|
||||
"<pre>"
|
||||
"for i in range(10):\n"
|
||||
" print(i)"
|
||||
"</pre>"
|
||||
),
|
||||
parse_mode="html"
|
||||
)
|
||||
.. code-block:: text
|
||||
|
||||
.. _Telegram Bot API: https://core.telegram.org/bots/api#formatting-options
|
||||
<my text>
|
||||
|
||||
Different Styles
|
||||
----------------
|
||||
|
||||
By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together.
|
||||
This means you can combine together both syntaxes in the same text:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.send_message("haskell", "**bold**, <i>italic</i>")
|
||||
|
||||
Result:
|
||||
|
||||
**bold**, *italic*
|
||||
|
||||
If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing
|
||||
"markdown" or "html" as argument to the *parse_mode* parameter.
|
||||
|
||||
.. code-block::
|
||||
|
||||
app.send_message("haskell", "**bold**, <i>italic</i>", parse_mode="markdown")
|
||||
app.send_message("haskell", "**bold**, <i>italic</i>", parse_mode="html")
|
||||
|
||||
Result:
|
||||
|
||||
**bold**, <i>italic</i>
|
||||
|
||||
\*\*bold**, *italic*
|
||||
|
||||
In case you want to completely turn off the style parser, simply pass ``None`` to *parse_mode*. The text will be sent
|
||||
as-is.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
app.send_message("haskell", "**bold**, <i>italic</i>", parse_mode=None)
|
||||
|
||||
Result:
|
||||
|
||||
\*\*bold**, <i>italic</i>
|
||||
|
||||
Nested and Overlapping Entities
|
||||
-------------------------------
|
||||
|
||||
You can also style texts with more than one decoration at once by nesting entities together. For example, you can send
|
||||
a text message with both :bold-underline:`bold and underline` styles, or a text that has both :italic-strike:`italic and
|
||||
strike` styles, and you can still combine both Markdown and HTML together.
|
||||
|
||||
Here there are some example texts you can try sending:
|
||||
|
||||
**Markdown**:
|
||||
|
||||
- ``**bold, --underline--**``
|
||||
- ``**bold __italic --underline ~~striked~~--__**``
|
||||
- ``**bold __and** italic__``
|
||||
|
||||
**HTML**:
|
||||
|
||||
- ``<b>bold, <u>underline</u></b>``
|
||||
- ``<b>bold <i>italic <u>underline <s>striked</s></u></i></b>``
|
||||
- ``<b>bold <i>and</b> italic</i>``
|
||||
|
||||
**Combined**:
|
||||
|
||||
- ``--you can combine <i>HTML</i> with **Markdown**--``
|
||||
- ``**and also <i>overlap** --entities</i> this way--``
|
||||
|
@ -1,8 +1,8 @@
|
||||
Using Filters
|
||||
=============
|
||||
|
||||
So far we've seen how to register a callback function that executes every time a specific update comes from the server,
|
||||
but there's much more than that to come.
|
||||
So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update
|
||||
comes from the server, but there's much more than that to come.
|
||||
|
||||
Here we'll discuss about :class:`~pyrogram.Filters`. Filters enable a fine-grain control over what kind of
|
||||
updates are allowed or not to be passed in your callback functions, based on their inner details.
|
||||
|
@ -17,180 +17,183 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
from typing import Callable
|
||||
|
||||
from .filter import Filter
|
||||
from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
||||
|
||||
CUSTOM_FILTER_NAME = "CustomFilter"
|
||||
|
||||
def create(name: str, func: callable, **kwargs) -> type:
|
||||
"""Create a Filter.
|
||||
|
||||
def create(func: Callable, name: str = None, **kwargs) -> Filter:
|
||||
"""Easily create a custom filter.
|
||||
|
||||
Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
|
||||
|
||||
Parameters:
|
||||
name (``str``):
|
||||
Your filter's name. Can be anything you like.
|
||||
|
||||
func (``callable``):
|
||||
A function that accepts two arguments *(filter, update)* and returns a Boolean: True if the update should be
|
||||
handled, False otherwise.
|
||||
The "update" argument type will vary depending on which `Handler <Handlers.html>`_ is coming from.
|
||||
For example, in a :obj:`MessageHandler` the update type will be
|
||||
a :obj:`Message`; in a :obj:`CallbackQueryHandler` the
|
||||
update type will be a :obj:`CallbackQuery`. Your function body can then access the
|
||||
incoming update and decide whether to allow it or not.
|
||||
A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the
|
||||
update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used
|
||||
to access keyword arguments (read below). The *update* argument type will vary depending on which
|
||||
`Handler <handlers>`_ is coming from. For example, in a :obj:`MessageHandler` the *update* argument will be
|
||||
a :obj:`Message`; in a :obj:`CallbackQueryHandler` the *update* will be a :obj:`CallbackQuery`. Your
|
||||
function body can then access the incoming update attributes and decide whether to allow it or not.
|
||||
|
||||
name (``str``, *optional*):
|
||||
Your filter's name. Can be anything you like.
|
||||
Defaults to "CustomFilter".
|
||||
|
||||
**kwargs (``any``, *optional*):
|
||||
Any keyword argument you would like to pass. Useful for custom filters that accept parameters (e.g.:
|
||||
:meth:`~Filters.command`, :meth:`~Filters.regex`).
|
||||
Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as
|
||||
:meth:`~Filters.command` or :meth:`~Filters.regex`.
|
||||
"""
|
||||
# TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only
|
||||
d = {"__call__": func}
|
||||
d.update(kwargs)
|
||||
|
||||
return type(name, (Filter,), d)()
|
||||
return type(name or CUSTOM_FILTER_NAME, (Filter,), d)()
|
||||
|
||||
|
||||
class Filters:
|
||||
"""This class provides access to all library-defined Filters available in Pyrogram.
|
||||
|
||||
The Filters listed here are intended to be used with the :obj:`MessageHandler` only.
|
||||
The Filters listed here are currently intended to be used with the :obj:`MessageHandler` only.
|
||||
At the moment, if you want to filter updates coming from different `Handlers <Handlers.html>`_ you have to create
|
||||
your own filters with :meth:`~Filters.create` and use them in the same way.
|
||||
"""
|
||||
|
||||
create = create
|
||||
|
||||
me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self))
|
||||
me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter")
|
||||
"""Filter messages generated by you yourself."""
|
||||
|
||||
bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
|
||||
bot = create(lambda _, m: bool(m.from_user and m.from_user.is_bot), "BotFilter")
|
||||
"""Filter messages coming from bots."""
|
||||
|
||||
incoming = create("Incoming", lambda _, m: not m.outgoing)
|
||||
incoming = create(lambda _, m: not m.outgoing, "IncomingFilter")
|
||||
"""Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming."""
|
||||
|
||||
outgoing = create("Outgoing", lambda _, m: m.outgoing)
|
||||
outgoing = create(lambda _, m: m.outgoing, "OutgoingFilter")
|
||||
"""Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing."""
|
||||
|
||||
text = create("Text", lambda _, m: bool(m.text))
|
||||
text = create(lambda _, m: bool(m.text), "TextFilter")
|
||||
"""Filter text messages."""
|
||||
|
||||
reply = create("Reply", lambda _, m: bool(m.reply_to_message))
|
||||
reply = create(lambda _, m: bool(m.reply_to_message), "ReplyFilter")
|
||||
"""Filter messages that are replies to other messages."""
|
||||
|
||||
forwarded = create("Forwarded", lambda _, m: bool(m.forward_date))
|
||||
forwarded = create(lambda _, m: bool(m.forward_date), "ForwardedFilter")
|
||||
"""Filter messages that are forwarded."""
|
||||
|
||||
caption = create("Caption", lambda _, m: bool(m.caption))
|
||||
caption = create(lambda _, m: bool(m.caption), "CaptionFilter")
|
||||
"""Filter media messages that contain captions."""
|
||||
|
||||
edited = create("Edited", lambda _, m: bool(m.edit_date))
|
||||
edited = create(lambda _, m: bool(m.edit_date), "EditedFilter")
|
||||
"""Filter edited messages."""
|
||||
|
||||
audio = create("Audio", lambda _, m: bool(m.audio))
|
||||
audio = create(lambda _, m: bool(m.audio), "AudioFilter")
|
||||
"""Filter messages that contain :obj:`Audio` objects."""
|
||||
|
||||
document = create("Document", lambda _, m: bool(m.document))
|
||||
document = create(lambda _, m: bool(m.document), "DocumentFilter")
|
||||
"""Filter messages that contain :obj:`Document` objects."""
|
||||
|
||||
photo = create("Photo", lambda _, m: bool(m.photo))
|
||||
photo = create(lambda _, m: bool(m.photo), "PhotoFilter")
|
||||
"""Filter messages that contain :obj:`Photo` objects."""
|
||||
|
||||
sticker = create("Sticker", lambda _, m: bool(m.sticker))
|
||||
sticker = create(lambda _, m: bool(m.sticker), "StickerFilter")
|
||||
"""Filter messages that contain :obj:`Sticker` objects."""
|
||||
|
||||
animation = create("Animation", lambda _, m: bool(m.animation))
|
||||
animation = create(lambda _, m: bool(m.animation), "AnimationFilter")
|
||||
"""Filter messages that contain :obj:`Animation` objects."""
|
||||
|
||||
game = create("Game", lambda _, m: bool(m.game))
|
||||
game = create(lambda _, m: bool(m.game), "GameFilter")
|
||||
"""Filter messages that contain :obj:`Game` objects."""
|
||||
|
||||
video = create("Video", lambda _, m: bool(m.video))
|
||||
video = create(lambda _, m: bool(m.video), "VideoFilter")
|
||||
"""Filter messages that contain :obj:`Video` objects."""
|
||||
|
||||
media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id))
|
||||
media_group = create(lambda _, m: bool(m.media_group_id), "MediaGroupFilter")
|
||||
"""Filter messages containing photos or videos being part of an album."""
|
||||
|
||||
voice = create("Voice", lambda _, m: bool(m.voice))
|
||||
voice = create(lambda _, m: bool(m.voice), "VoiceFilter")
|
||||
"""Filter messages that contain :obj:`Voice` note objects."""
|
||||
|
||||
video_note = create("VideoNote", lambda _, m: bool(m.video_note))
|
||||
video_note = create(lambda _, m: bool(m.video_note), "VideoNoteFilter")
|
||||
"""Filter messages that contain :obj:`VideoNote` objects."""
|
||||
|
||||
contact = create("Contact", lambda _, m: bool(m.contact))
|
||||
contact = create(lambda _, m: bool(m.contact), "ContactFilter")
|
||||
"""Filter messages that contain :obj:`Contact` objects."""
|
||||
|
||||
location = create("Location", lambda _, m: bool(m.location))
|
||||
location = create(lambda _, m: bool(m.location), "LocationFilter")
|
||||
"""Filter messages that contain :obj:`Location` objects."""
|
||||
|
||||
venue = create("Venue", lambda _, m: bool(m.venue))
|
||||
venue = create(lambda _, m: bool(m.venue), "VenueFilter")
|
||||
"""Filter messages that contain :obj:`Venue` objects."""
|
||||
|
||||
web_page = create("WebPage", lambda _, m: m.web_page)
|
||||
web_page = create(lambda _, m: m.web_page, "WebPageFilter")
|
||||
"""Filter messages sent with a webpage preview."""
|
||||
|
||||
poll = create("Poll", lambda _, m: m.poll)
|
||||
poll = create(lambda _, m: m.poll, "PollFilter")
|
||||
"""Filter messages that contain :obj:`Poll` objects."""
|
||||
|
||||
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
|
||||
private = create(lambda _, m: bool(m.chat and m.chat.type == "private"), "PrivateFilter")
|
||||
"""Filter messages sent in private chats."""
|
||||
|
||||
group = create("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}))
|
||||
group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter")
|
||||
"""Filter messages sent in group or supergroup chats."""
|
||||
|
||||
channel = create("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel"))
|
||||
channel = create(lambda _, m: bool(m.chat and m.chat.type == "channel"), "ChannelFilter")
|
||||
"""Filter messages sent in channels."""
|
||||
|
||||
new_chat_members = create("NewChatMembers", lambda _, m: bool(m.new_chat_members))
|
||||
new_chat_members = create(lambda _, m: bool(m.new_chat_members), "NewChatMembersFilter")
|
||||
"""Filter service messages for new chat members."""
|
||||
|
||||
left_chat_member = create("LeftChatMember", lambda _, m: bool(m.left_chat_member))
|
||||
left_chat_member = create(lambda _, m: bool(m.left_chat_member), "LeftChatMemberFilter")
|
||||
"""Filter service messages for members that left the chat."""
|
||||
|
||||
new_chat_title = create("NewChatTitle", lambda _, m: bool(m.new_chat_title))
|
||||
new_chat_title = create(lambda _, m: bool(m.new_chat_title), "NewChatTitleFilter")
|
||||
"""Filter service messages for new chat titles."""
|
||||
|
||||
new_chat_photo = create("NewChatPhoto", lambda _, m: bool(m.new_chat_photo))
|
||||
new_chat_photo = create(lambda _, m: bool(m.new_chat_photo), "NewChatPhotoFilter")
|
||||
"""Filter service messages for new chat photos."""
|
||||
|
||||
delete_chat_photo = create("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo))
|
||||
delete_chat_photo = create(lambda _, m: bool(m.delete_chat_photo), "DeleteChatPhotoFilter")
|
||||
"""Filter service messages for deleted photos."""
|
||||
|
||||
group_chat_created = create("GroupChatCreated", lambda _, m: bool(m.group_chat_created))
|
||||
group_chat_created = create(lambda _, m: bool(m.group_chat_created), "GroupChatCreatedFilter")
|
||||
"""Filter service messages for group chat creations."""
|
||||
|
||||
supergroup_chat_created = create("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created))
|
||||
supergroup_chat_created = create(lambda _, m: bool(m.supergroup_chat_created), "SupergroupChatCreatedFilter")
|
||||
"""Filter service messages for supergroup chat creations."""
|
||||
|
||||
channel_chat_created = create("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created))
|
||||
channel_chat_created = create(lambda _, m: bool(m.channel_chat_created), "ChannelChatCreatedFilter")
|
||||
"""Filter service messages for channel chat creations."""
|
||||
|
||||
migrate_to_chat_id = create("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id))
|
||||
migrate_to_chat_id = create(lambda _, m: bool(m.migrate_to_chat_id), "MigrateToChatIdFilter")
|
||||
"""Filter service messages that contain migrate_to_chat_id."""
|
||||
|
||||
migrate_from_chat_id = create("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id))
|
||||
migrate_from_chat_id = create(lambda _, m: bool(m.migrate_from_chat_id), "MigrateFromChatIdFilter")
|
||||
"""Filter service messages that contain migrate_from_chat_id."""
|
||||
|
||||
pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message))
|
||||
pinned_message = create(lambda _, m: bool(m.pinned_message), "PinnedMessageFilter")
|
||||
"""Filter service messages for pinned messages."""
|
||||
|
||||
game_high_score = create("GameHighScore", lambda _, m: bool(m.game_high_score))
|
||||
game_high_score = create(lambda _, m: bool(m.game_high_score), "GameHighScoreFilter")
|
||||
"""Filter service messages for game high scores."""
|
||||
|
||||
reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
|
||||
reply_keyboard = create(lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup), "ReplyKeyboardFilter")
|
||||
"""Filter messages containing reply keyboard markups"""
|
||||
|
||||
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
|
||||
inline_keyboard = create(lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup), "InlineKeyboardFilter")
|
||||
"""Filter messages containing inline keyboard markups"""
|
||||
|
||||
mentioned = create("Mentioned", lambda _, m: bool(m.mentioned))
|
||||
mentioned = create(lambda _, m: bool(m.mentioned), "MentionedFilter")
|
||||
"""Filter messages containing mentions"""
|
||||
|
||||
via_bot = create("ViaBot", lambda _, m: bool(m.via_bot))
|
||||
via_bot = create(lambda _, m: bool(m.via_bot), "ViaBotFilter")
|
||||
"""Filter messages sent via inline bots"""
|
||||
|
||||
service = create("Service", lambda _, m: bool(m.service))
|
||||
service = create(lambda _, m: bool(m.service), "ServiceFilter")
|
||||
"""Filter service messages.
|
||||
|
||||
A service message contains any of the following fields set: *left_chat_member*,
|
||||
@ -198,7 +201,7 @@ class Filters:
|
||||
*channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*.
|
||||
"""
|
||||
|
||||
media = create("Media", lambda _, m: bool(m.media))
|
||||
media = create(lambda _, m: bool(m.media), "MediaFilter")
|
||||
"""Filter media messages.
|
||||
|
||||
A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*,
|
||||
@ -253,17 +256,17 @@ class Filters:
|
||||
commands = {c if case_sensitive else c.lower() for c in commands}
|
||||
prefixes = set(prefix) if prefix else {""}
|
||||
|
||||
return create("Command", func=func, c=commands, p=prefixes, s=separator, cs=case_sensitive)
|
||||
return create(func, "CommandFilter", c=commands, p=prefixes, s=separator, cs=case_sensitive)
|
||||
|
||||
@staticmethod
|
||||
def regex(pattern, flags: int = 0):
|
||||
"""Filter messages that match a given RegEx pattern.
|
||||
"""Filter message texts or captions that match a given regular expression pattern.
|
||||
|
||||
Parameters:
|
||||
pattern (``str``):
|
||||
The RegEx pattern as string, it will be applied to the text of a message. When a pattern matches,
|
||||
all the `Match Objects <https://docs.python.org/3/library/re.html#match-objects>`_
|
||||
are stored in the *matches* field of the :obj:`Message` itself.
|
||||
The RegEx pattern as string, it will be applied to the text or the caption of a message. When a pattern
|
||||
matches, all the `Match Objects <https://docs.python.org/3/library/re.html#match-objects>`_ are stored
|
||||
in the *matches* field of the :obj:`Message` itself.
|
||||
|
||||
flags (``int``, *optional*):
|
||||
RegEx flags.
|
||||
@ -273,7 +276,7 @@ class Filters:
|
||||
m.matches = [i for i in _.p.finditer(m.text or m.caption or "")]
|
||||
return bool(m.matches)
|
||||
|
||||
return create("Regex", f, p=re.compile(pattern, flags))
|
||||
return create(f, "RegexFilter", p=re.compile(pattern, flags))
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class user(Filter, set):
|
||||
@ -348,6 +351,6 @@ class Filters:
|
||||
Pass the data you want to filter for.
|
||||
"""
|
||||
|
||||
return create("CallbackData", lambda flt, cb: cb.data == flt.data, data=data)
|
||||
return create(lambda flt, cb: cb.data == flt.data, "CallbackDataFilter", data=data)
|
||||
|
||||
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))
|
||||
dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter")
|
||||
|
@ -16,23 +16,24 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from base64 import b64decode
|
||||
from struct import unpack
|
||||
from typing import List, Union
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.client.ext import utils
|
||||
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class DeleteProfilePhotos(BaseClient):
|
||||
async def delete_profile_photos(
|
||||
self,
|
||||
id: Union[str, List[str]]
|
||||
photo_ids: Union[str, List[str]]
|
||||
) -> bool:
|
||||
"""Delete your own profile photos.
|
||||
|
||||
Parameters:
|
||||
id (``str`` | ``list``):
|
||||
photo_ids (``str`` | List of ``str``):
|
||||
A single :obj:`Photo` id as string or multiple ids as list of strings for deleting
|
||||
more than one photos at once.
|
||||
|
||||
@ -42,16 +43,16 @@ class DeleteProfilePhotos(BaseClient):
|
||||
Raises:
|
||||
RPCError: In case of a Telegram RPC error.
|
||||
"""
|
||||
id = id if isinstance(id, list) else [id]
|
||||
photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids]
|
||||
input_photos = []
|
||||
|
||||
for i in id:
|
||||
s = unpack("<qq", b64decode(i + "=" * (-len(i) % 4), "-_"))
|
||||
for photo_id in photo_ids:
|
||||
unpacked = unpack("<iiqqc", utils.decode(photo_id))
|
||||
|
||||
input_photos.append(
|
||||
types.InputPhoto(
|
||||
id=s[0],
|
||||
access_hash=s[1],
|
||||
id=unpacked[2],
|
||||
access_hash=unpacked[3],
|
||||
file_reference=b""
|
||||
)
|
||||
)
|
||||
|
@ -50,7 +50,7 @@ class Object(metaclass=Meta):
|
||||
else (attr, str(datetime.fromtimestamp(getattr(obj, attr))))
|
||||
if attr.endswith("date")
|
||||
else (attr, getattr(obj, attr))
|
||||
for attr in obj.__slots__
|
||||
for attr in getattr(obj, "__slots__", [])
|
||||
if getattr(obj, attr) is not None
|
||||
]
|
||||
)
|
||||
|
@ -17,65 +17,68 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from importlib import import_module
|
||||
from typing import Type
|
||||
|
||||
from pyrogram.api.core import TLObject
|
||||
from pyrogram.api.types import RpcError as RawRPCError
|
||||
from .exceptions.all import exceptions
|
||||
|
||||
|
||||
class RPCError(Exception):
|
||||
"""This is the base exception class for all Telegram API related errors.
|
||||
For a finer grained control, see the specific errors below.
|
||||
"""
|
||||
ID = None
|
||||
CODE = None
|
||||
NAME = None
|
||||
MESSAGE = None
|
||||
MESSAGE = "{x}"
|
||||
|
||||
def __init__(self, x: int or RawRPCError = None, query_type: type = None):
|
||||
super().__init__("[{} {}]: {}".format(
|
||||
def __init__(self, x: int or RawRPCError, rpc_name: str, is_unknown: bool):
|
||||
super().__init__("[{} {}]: {} ({})".format(
|
||||
self.CODE,
|
||||
self.ID or self.NAME,
|
||||
str(self) or self.MESSAGE.format(x=x)
|
||||
self.MESSAGE.format(x=x),
|
||||
'caused by "{}"'.format(rpc_name)
|
||||
))
|
||||
|
||||
try:
|
||||
self.x = int(x)
|
||||
except (ValueError, TypeError):
|
||||
self.x = x
|
||||
|
||||
# TODO: Proper log unknown errors
|
||||
if self.CODE == 520:
|
||||
if is_unknown:
|
||||
with open("unknown_errors.txt", "a", encoding="utf-8") as f:
|
||||
f.write("{}\t{}\t{}\n".format(x.error_code, x.error_message, query_type))
|
||||
f.write("{}\t{}\t{}\n".format(datetime.now(), x, rpc_name))
|
||||
|
||||
@staticmethod
|
||||
def raise_it(rpc_error: RawRPCError, query_type: type):
|
||||
code = rpc_error.error_code
|
||||
def raise_it(rpc_error: RawRPCError, rpc_type: Type[TLObject]):
|
||||
error_code = rpc_error.error_code
|
||||
error_message = rpc_error.error_message
|
||||
rpc_name = ".".join(rpc_type.QUALNAME.split(".")[1:])
|
||||
|
||||
if code not in exceptions:
|
||||
raise UnknownError(x=rpc_error, query_type=query_type)
|
||||
if error_code not in exceptions:
|
||||
raise UnknownError(
|
||||
x="[{} {}]".format(error_code, error_message),
|
||||
rpc_name=rpc_name,
|
||||
is_unknown=True
|
||||
)
|
||||
|
||||
message = rpc_error.error_message
|
||||
id = re.sub(r"_\d+", "_X", message)
|
||||
error_id = re.sub(r"_\d+", "_X", error_message)
|
||||
|
||||
if id not in exceptions[code]:
|
||||
raise UnknownError(x=rpc_error, query_type=query_type)
|
||||
if error_id not in exceptions[error_code]:
|
||||
raise getattr(
|
||||
import_module("pyrogram.errors"),
|
||||
exceptions[error_code]["_"]
|
||||
)(x="[{} {}]".format(error_code, error_message),
|
||||
rpc_name=rpc_name,
|
||||
is_unknown=True)
|
||||
|
||||
x = re.search(r"_(\d+)", message)
|
||||
x = re.search(r"_(\d+)", error_message)
|
||||
x = x.group(1) if x is not None else x
|
||||
|
||||
raise getattr(
|
||||
import_module("pyrogram.errors"),
|
||||
exceptions[code][id]
|
||||
)(x=x)
|
||||
exceptions[error_code][error_id]
|
||||
)(x=x,
|
||||
rpc_name=rpc_name,
|
||||
is_unknown=False)
|
||||
|
||||
|
||||
class UnknownError(RPCError):
|
||||
"""This object represents an Unknown Error, that is, an error which
|
||||
Pyrogram does not know anything about, yet.
|
||||
"""
|
||||
CODE = 520
|
||||
""":obj:`int`: Error code"""
|
||||
NAME = "Unknown error"
|
||||
MESSAGE = "{x}"
|
||||
|
Loading…
x
Reference in New Issue
Block a user