2
0
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:
Dan 2019-06-28 11:12:19 +02:00
commit d8d72395b7
17 changed files with 351 additions and 210 deletions

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -28,8 +28,8 @@ Welcome to Pyrogram
api/bound-methods
api/handlers
api/decorators
api/filters
api/errors
api/filters
.. toctree::
:hidden:

View File

@ -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

View File

@ -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.

View File

@ -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!")

View File

@ -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.

View File

@ -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 ``&lt;``, ``>`` with ``&gt;`` and ``&`` with ``&amp;``). 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
&lt;my text&gt;
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--``

View File

@ -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.

View File

@ -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")

View File

@ -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""
)
)

View File

@ -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
]
)

View File

@ -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}"