diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 6cb33a50..6ae6e98c 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -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!") diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fb0a3615..d8768b3b 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -17,190 +17,193 @@ # along with Pyrogram. If not, see . 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 `_ 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 `_ 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 `_ 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*, *new_chat_title*, *new_chat_photo*, *delete_chat_photo*, *group_chat_created*, *supergroup_chat_created*, *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*, *animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*. """ @@ -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 `_ - 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 `_ 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): @@ -285,7 +288,7 @@ class Filters: Parameters: users (``int`` | ``str`` | ``list``): Pass one or more user ids/usernames to filter users. - For you yourself, "me" or "self" can be used as well. + For you yourself, "me" or "self" can be used as well. Defaults to None (no users). """ @@ -342,12 +345,12 @@ class Filters: @staticmethod def callback_data(data: str or bytes): """Filter callback queries for their data. - + Parameters: data (``str`` | ``bytes``): 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")