From 71e74da0a2b2e70f91e9aad6d73066249fdfc084 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Wed, 14 Sep 2016 19:29:15 +0200 Subject: [PATCH 01/12] Make filters and/or-able using bitwise operators. See associated PR for more info. --- telegram/ext/__init__.py | 3 +- telegram/ext/filters.py | 150 +++++++++++++++++++++++++++++++++ telegram/ext/messagehandler.py | 63 -------------- tests/test_filters.py | 24 ++++++ 4 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 telegram/ext/filters.py diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 770643184..618013475 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -26,7 +26,8 @@ from .choseninlineresulthandler import ChosenInlineResultHandler from .commandhandler import CommandHandler from .handler import Handler from .inlinequeryhandler import InlineQueryHandler -from .messagehandler import MessageHandler, Filters +from .messagehandler import MessageHandler +from .filters import Filters from .regexhandler import RegexHandler from .stringcommandhandler import StringCommandHandler from .stringregexhandler import StringRegexHandler diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py new file mode 100644 index 000000000..9c44f21b6 --- /dev/null +++ b/telegram/ext/filters.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2016 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser Public License for more details. +# +# You should have received a copy of the GNU Lesser Public License +# along with this program. If not, see [http://www.gnu.org/licenses/]. +""" This module contains the MessageHandler class """ + + +class BaseFilter(object): + """Base class for all Message Filters""" + + def __call__(self, message): + raise NotImplementedError('Please implement a call method in your filter.') + + def __and__(self, other): + return MergedFilter(self, and_filter=other) + + def __or__(self, other): + return MergedFilter(self, or_filter=other) + + +class MergedFilter(BaseFilter): + """Represents a filter consisting of two other filters.""" + + def __init__(self, base_filter, and_filter=None, or_filter=None): + self.base_filter = base_filter + self.and_filter = and_filter + self.or_filter = or_filter + + def __call__(self, message): + if self.and_filter: + return self.base_filter(message) and self.and_filter(message) + elif self.or_filter: + return self.base_filter(message) or self.or_filter(message) + + +class Filters(object): + """ + Convenient namespace (class) & methods for the filter funcs of the + MessageHandler class. + """ + + class Text(BaseFilter): + + def __call__(self, message): + return bool(message.text and not message.text.startswith('/')) + + text = Text() + + class Command(BaseFilter): + + def __call__(self, message): + return bool(message.text and message.text.startswith('/')) + + command = Command() + + class Audio(BaseFilter): + + def __call__(self, message): + return bool(message.audio) + + audio = Audio() + + class Document(BaseFilter): + + def __call__(self, message): + return bool(message.document) + + document = Document() + + class Photo(BaseFilter): + + def __call__(self, message): + return bool(message.photo) + + photo = Photo() + + class Sticker(BaseFilter): + + def __call__(self, message): + return bool(message.sticker) + + sticker = Sticker() + + class Video(BaseFilter): + + def __call__(self, message): + return bool(message.video) + + video = Video() + + class Voice(BaseFilter): + + def __call__(self, message): + return bool(message.voice) + + voice = Voice() + + class Contact(BaseFilter): + + def __call__(self, message): + return bool(message.contact) + + contact = Contact() + + class Location(BaseFilter): + + def __call__(self, message): + return bool(message.location) + + location = Location() + + class Venue(BaseFilter): + + def __call__(self, message): + return bool(message.venue) + + venue = Venue() + + class StatusUpdate(BaseFilter): + + def __call__(self, message): + return bool(message.new_chat_member or message.left_chat_member + or message.new_chat_title or message.new_chat_photo + or message.delete_chat_photo or message.group_chat_created + or message.supergroup_chat_created or message.channel_chat_created + or message.migrate_to_chat_id or message.migrate_from_chat_id + or message.pinned_message) + + status_update = StatusUpdate() + + class Forwarded(BaseFilter): + + def __call__(self, message): + return bool(message.forward_date) + + forwarded = Forwarded() diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 03208fa10..273e8c34a 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -23,69 +23,6 @@ from telegram import Update from telegram.utils.deprecate import deprecate -class Filters(object): - """ - Convenient namespace (class) & methods for the filter funcs of the - MessageHandler class. - """ - - @staticmethod - def text(message): - return message.text and not message.text.startswith('/') - - @staticmethod - def command(message): - return message.text and message.text.startswith('/') - - @staticmethod - def audio(message): - return bool(message.audio) - - @staticmethod - def document(message): - return bool(message.document) - - @staticmethod - def photo(message): - return bool(message.photo) - - @staticmethod - def sticker(message): - return bool(message.sticker) - - @staticmethod - def video(message): - return bool(message.video) - - @staticmethod - def voice(message): - return bool(message.voice) - - @staticmethod - def contact(message): - return bool(message.contact) - - @staticmethod - def location(message): - return bool(message.location) - - @staticmethod - def venue(message): - return bool(message.venue) - - @staticmethod - def status_update(message): - return bool(message.new_chat_member or message.left_chat_member or message.new_chat_title - or message.new_chat_photo or message.delete_chat_photo - or message.group_chat_created or message.supergroup_chat_created - or message.channel_chat_created or message.migrate_to_chat_id - or message.migrate_from_chat_id or message.pinned_message) - - @staticmethod - def forwarded(message): - return bool(message.forward_date) - - class MessageHandler(Handler): """ Handler class to handle telegram messages. Messages are Telegram Updates diff --git a/tests/test_filters.py b/tests/test_filters.py index b023700af..b9f54aeb0 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -150,6 +150,30 @@ class FiltersTest(BaseTest, unittest.TestCase): self.assertTrue(Filters.status_update(self.message)) self.message.pinned_message = None + def test_and_filters(self): + # For now just test with forwarded as that's the only one that makes sense + # That'll change when we get a entities filter + self.message.text = 'test' + self.message.forward_date = True + self.assertTrue((Filters.text & Filters.forwarded)(self.message)) + self.message.text = '/test' + self.assertFalse((Filters.text & Filters.forwarded)(self.message)) + self.message.text = 'test' + self.message.forward_date = None + self.assertFalse((Filters.text & Filters.forwarded)(self.message)) + + def test_or_filters(self): + # For now just test with forwarded as that's the only one that makes sense + # That'll change when we get a entities filter + self.message.text = 'test' + self.assertTrue((Filters.text | Filters.status_update)(self.message)) + self.message.group_chat_created = True + self.assertTrue((Filters.text | Filters.status_update)(self.message)) + self.message.text = None + self.assertTrue((Filters.text | Filters.status_update)(self.message)) + self.message.group_chat_created = False + self.assertFalse((Filters.text | Filters.status_update)(self.message)) + if __name__ == '__main__': unittest.main() From 2161681131aa9d3fcee1a49953fff61c62ecb5dd Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 24 Sep 2016 18:20:32 +0200 Subject: [PATCH 02/12] Use filter method instead of __call__ __call__ is scary looking for users wanted to create their own filters. Also allows us to put additional logic in __call__ if we want in the future. --- telegram/ext/filters.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 9c44f21b6..a23f00859 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -23,7 +23,7 @@ class BaseFilter(object): """Base class for all Message Filters""" def __call__(self, message): - raise NotImplementedError('Please implement a call method in your filter.') + self.filter(message) def __and__(self, other): return MergedFilter(self, and_filter=other) @@ -31,6 +31,9 @@ class BaseFilter(object): def __or__(self, other): return MergedFilter(self, or_filter=other) + def filter(self, message): + raise NotImplementedError + class MergedFilter(BaseFilter): """Represents a filter consisting of two other filters.""" @@ -40,7 +43,7 @@ class MergedFilter(BaseFilter): self.and_filter = and_filter self.or_filter = or_filter - def __call__(self, message): + def filter(self, message): if self.and_filter: return self.base_filter(message) and self.and_filter(message) elif self.or_filter: @@ -55,84 +58,84 @@ class Filters(object): class Text(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.text and not message.text.startswith('/')) text = Text() class Command(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.text and message.text.startswith('/')) command = Command() class Audio(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.audio) audio = Audio() class Document(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.document) document = Document() class Photo(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.photo) photo = Photo() class Sticker(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.sticker) sticker = Sticker() class Video(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.video) video = Video() class Voice(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.voice) voice = Voice() class Contact(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.contact) contact = Contact() class Location(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.location) location = Location() class Venue(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.venue) venue = Venue() class StatusUpdate(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.new_chat_member or message.left_chat_member or message.new_chat_title or message.new_chat_photo or message.delete_chat_photo or message.group_chat_created @@ -144,7 +147,7 @@ class Filters(object): class Forwarded(BaseFilter): - def __call__(self, message): + def filter(self, message): return bool(message.forward_date) forwarded = Forwarded() From 61596400e18de8d3ecf37ecc2f443329e49e0e4f Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 24 Sep 2016 18:56:54 +0200 Subject: [PATCH 03/12] __call__ should return the result Also add tests with both & and |. --- telegram/ext/filters.py | 2 +- tests/test_filters.py | 39 ++++++++++++++++++++++++++------------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index b17c65cc9..5a529b052 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -23,7 +23,7 @@ class BaseFilter(object): """Base class for all Message Filters""" def __call__(self, message): - self.filter(message) + return self.filter(message) def __and__(self, other): return MergedFilter(self, and_filter=other) diff --git a/tests/test_filters.py b/tests/test_filters.py index 24a1abe0f..fc7f99715 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -23,14 +23,11 @@ This module contains a object that represents Tests for MessageHandler.Filters import sys import unittest from datetime import datetime - import functools -from telegram import MessageEntity - sys.path.append('.') -from telegram import Message, User, Chat +from telegram import Message, User, Chat, MessageEntity from telegram.ext import Filters from tests.base import BaseTest @@ -40,6 +37,7 @@ class FiltersTest(BaseTest, unittest.TestCase): def setUp(self): self.message = Message(0, User(0, "Testuser"), datetime.now(), Chat(0, 'private')) + self.e = functools.partial(MessageEntity, offset=0, length=0) def test_filters_text(self): self.message.text = 'test' @@ -155,23 +153,19 @@ class FiltersTest(BaseTest, unittest.TestCase): self.message.pinned_message = None def test_entities_filter(self): - e = functools.partial(MessageEntity, offset=0, length=0) - - self.message.entities = [e(MessageEntity.MENTION)] + self.message.entities = [self.e(MessageEntity.MENTION)] self.assertTrue(Filters.entity(MessageEntity.MENTION)(self.message)) self.message.entities = [] self.assertFalse(Filters.entity(MessageEntity.MENTION)(self.message)) - self.message.entities = [e(MessageEntity.BOLD)] + self.message.entities = [self.e(MessageEntity.BOLD)] self.assertFalse(Filters.entity(MessageEntity.MENTION)(self.message)) - self.message.entities = [e(MessageEntity.BOLD), e(MessageEntity.MENTION)] + self.message.entities = [self.e(MessageEntity.BOLD), self.e(MessageEntity.MENTION)] self.assertTrue(Filters.entity(MessageEntity.MENTION)(self.message)) def test_and_filters(self): - # For now just test with forwarded as that's the only one that makes sense - # That'll change when we get a entities filter self.message.text = 'test' self.message.forward_date = True self.assertTrue((Filters.text & Filters.forwarded)(self.message)) @@ -181,9 +175,16 @@ class FiltersTest(BaseTest, unittest.TestCase): self.message.forward_date = None self.assertFalse((Filters.text & Filters.forwarded)(self.message)) + self.message.text = 'test' + self.message.forward_date = True + self.message.entities = [self.e(MessageEntity.MENTION)] + self.assertTrue((Filters.text & Filters.forwarded & Filters.entity(MessageEntity.MENTION))( + self.message)) + self.message.entities = [self.e(MessageEntity.BOLD)] + self.assertFalse((Filters.text & Filters.forwarded & Filters.entity(MessageEntity.MENTION) + )(self.message)) + def test_or_filters(self): - # For now just test with forwarded as that's the only one that makes sense - # That'll change when we get a entities filter self.message.text = 'test' self.assertTrue((Filters.text | Filters.status_update)(self.message)) self.message.group_chat_created = True @@ -193,6 +194,18 @@ class FiltersTest(BaseTest, unittest.TestCase): self.message.group_chat_created = False self.assertFalse((Filters.text | Filters.status_update)(self.message)) + def test_and_or_filters(self): + self.message.text = 'test' + self.message.forward_date = True + self.assertTrue((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)) + )(self.message)) + self.message.forward_date = False + self.assertFalse((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION) + ))(self.message)) + self.message.entities = [self.e(MessageEntity.MENTION)] + self.assertTrue((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)) + )(self.message)) + if __name__ == '__main__': unittest.main() From 3244417f6178c21d9ce9814dd3a0ad019967e1de Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 25 Sep 2016 00:30:04 +0200 Subject: [PATCH 04/12] Add docs for filters. --- docs/source/telegram.ext.filters.rst | 7 +++ docs/source/telegram.ext.rst | 1 + telegram/ext/__init__.py | 4 +- telegram/ext/filters.py | 85 ++++++++++++++++------------ tests/test_filters.py | 2 +- 5 files changed, 61 insertions(+), 38 deletions(-) create mode 100644 docs/source/telegram.ext.filters.rst diff --git a/docs/source/telegram.ext.filters.rst b/docs/source/telegram.ext.filters.rst new file mode 100644 index 000000000..a04a2d1c1 --- /dev/null +++ b/docs/source/telegram.ext.filters.rst @@ -0,0 +1,7 @@ +telegram.ext.filters module +=========================== + +.. automodule:: telegram.ext.filters + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/telegram.ext.rst b/docs/source/telegram.ext.rst index 51c652e4f..3fa83b1b1 100644 --- a/docs/source/telegram.ext.rst +++ b/docs/source/telegram.ext.rst @@ -15,6 +15,7 @@ Submodules telegram.ext.commandhandler telegram.ext.inlinequeryhandler telegram.ext.messagehandler + telegram.ext.filters telegram.ext.regexhandler telegram.ext.stringcommandhandler telegram.ext.stringregexhandler diff --git a/telegram/ext/__init__.py b/telegram/ext/__init__.py index 618013475..451354abe 100644 --- a/telegram/ext/__init__.py +++ b/telegram/ext/__init__.py @@ -27,7 +27,7 @@ from .commandhandler import CommandHandler from .handler import Handler from .inlinequeryhandler import InlineQueryHandler from .messagehandler import MessageHandler -from .filters import Filters +from .filters import BaseFilter, Filters from .regexhandler import RegexHandler from .stringcommandhandler import StringCommandHandler from .stringregexhandler import StringRegexHandler @@ -36,5 +36,5 @@ from .conversationhandler import ConversationHandler __all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler', 'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler', - 'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler', + 'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler', 'StringRegexHandler', 'TypeHandler', 'ConversationHandler') diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 5a529b052..fabbe0870 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -16,11 +16,31 @@ # # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. -""" This module contains the MessageHandler class """ +""" This module contains the Filters for use with the MessageHandler class """ class BaseFilter(object): - """Base class for all Message Filters""" + """Base class for all Message Filters + + Subclassing from this class filters to be combined using bitwise operators: + + And: + + >>> (Filters.text & Filters.entity(MENTION) + + Or: + + >>> (Filters.audio | Filters.video) + + Also works with more than two filters: + + >>> (Filters.text & (Filters.entity(URL |Filters.entity(TEXT_LINK)))) + + If you want to create your own filters create a class inheriting from this class and implement + a `filter` method that returns a boolean: `True` if the message should be handled, `False` + otherwise. Note that the filters work only as class instances, not actual class objects + (so remember to initialize your filter classes). + """ def __call__(self, message): return self.filter(message) @@ -52,88 +72,87 @@ class MergedFilter(BaseFilter): class Filters(object): """ - Convenient namespace (class) & methods for the filter funcs of the - MessageHandler class. + Predefined filters for use with the `filter` argument of :class:`telegram.ext.MessageHandler`. """ - class Text(BaseFilter): + class _Text(BaseFilter): def filter(self, message): return bool(message.text and not message.text.startswith('/')) - text = Text() + text = _Text() - class Command(BaseFilter): + class _Command(BaseFilter): def filter(self, message): return bool(message.text and message.text.startswith('/')) - command = Command() + command = _Command() - class Audio(BaseFilter): + class _Audio(BaseFilter): def filter(self, message): return bool(message.audio) - audio = Audio() + audio = _Audio() - class Document(BaseFilter): + class _Document(BaseFilter): def filter(self, message): return bool(message.document) - document = Document() + document = _Document() - class Photo(BaseFilter): + class _Photo(BaseFilter): def filter(self, message): return bool(message.photo) - photo = Photo() + photo = _Photo() - class Sticker(BaseFilter): + class _Sticker(BaseFilter): def filter(self, message): return bool(message.sticker) - sticker = Sticker() + sticker = _Sticker() - class Video(BaseFilter): + class _Video(BaseFilter): def filter(self, message): return bool(message.video) - video = Video() + video = _Video() - class Voice(BaseFilter): + class _Voice(BaseFilter): def filter(self, message): return bool(message.voice) - voice = Voice() + voice = _Voice() - class Contact(BaseFilter): + class _Contact(BaseFilter): def filter(self, message): return bool(message.contact) - contact = Contact() + contact = _Contact() - class Location(BaseFilter): + class _Location(BaseFilter): def filter(self, message): return bool(message.location) - location = Location() + location = _Location() - class Venue(BaseFilter): + class _Venue(BaseFilter): def filter(self, message): return bool(message.venue) - venue = Venue() + venue = _Venue() - class StatusUpdate(BaseFilter): + class _StatusUpdate(BaseFilter): def filter(self, message): return bool(message.new_chat_member or message.left_chat_member @@ -143,16 +162,16 @@ class Filters(object): or message.migrate_to_chat_id or message.migrate_from_chat_id or message.pinned_message) - status_update = StatusUpdate() + status_update = _StatusUpdate() - class Forwarded(BaseFilter): + class _Forwarded(BaseFilter): def filter(self, message): return bool(message.forward_date) - forwarded = Forwarded() + forwarded = _Forwarded() - class Entity(BaseFilter): + class entity(BaseFilter): """Filters messages to only allow those which have a :class:`telegram.MessageEntity` where their `type` matches `entity_type`. @@ -168,7 +187,3 @@ class Filters(object): def filter(self, message): return any([entity.type == self.entity_type for entity in message.entities]) - -# We don't initialize since this filter accepts arguments. - - entity = Entity diff --git a/tests/test_filters.py b/tests/test_filters.py index fc7f99715..a7208968e 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """ -This module contains a object that represents Tests for MessageHandler.Filters +This module contains a object that represents Tests for Filters for use with MessageHandler """ import sys From 79e065a730dc8b1df93b99a200229813b892be46 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 25 Sep 2016 16:31:06 +0200 Subject: [PATCH 05/12] Add __str__ and __repr__ to MergedFilter. --- telegram/ext/filters.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index fabbe0870..d2672212a 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -69,6 +69,13 @@ class MergedFilter(BaseFilter): elif self.or_filter: return self.base_filter(message) or self.or_filter(message) + def __str__(self): + return ("").format(self.base_filter, "and" if self.and_filter else "or", + self.and_filter or self.or_filter) + + __repr__ = __str__ + class Filters(object): """ From 79bdfe4c5d4a5659c3ff2dc7e725ab95e7029e7b Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Thu, 29 Sep 2016 19:10:22 +0200 Subject: [PATCH 06/12] Allow filters to be passed without list. Also deprecates actually using a list. --- telegram/ext/messagehandler.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/telegram/ext/messagehandler.py b/telegram/ext/messagehandler.py index 4895c5961..b222de6d4 100644 --- a/telegram/ext/messagehandler.py +++ b/telegram/ext/messagehandler.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. """ This module contains the MessageHandler class """ +import warnings from .handler import Handler from telegram import Update @@ -30,12 +31,10 @@ class MessageHandler(Handler): updates. Args: - filters (list[function]): A list of filter functions. Standard filters - can be found in the Filters class above. - | Each `function` takes ``Update`` as arg and returns ``bool``. - | All messages that match at least one of those filters will be - accepted. If ``bool(filters)`` evaluates to ``False``, messages are - not filtered. + filters (telegram.ext.BaseFilter): A filter inheriting from + :class:`telegram.filters.BaseFilter`. Standard filters can be found in + :class:`telegram.filters.Filters`. Filters can be combined using bitwise + operators (& for and, | for or). callback (function): A function that takes ``bot, update`` as positional arguments. It will be called when the ``check_update`` has determined that an update should be processed by this handler. @@ -57,6 +56,13 @@ class MessageHandler(Handler): self.filters = filters self.allow_edited = allow_edited + # We put this up here instead of with the rest of checking code + # in check_update since we don't wanna spam a ton + if isinstance(self.filters, list): + warnings.warn('Using a list of filters in MessageHandler is getting ' + 'deprecated, please use bitwise operators (& and |) ' + 'instead. More info: https://git.io/vPTbc.') + def check_update(self, update): if (isinstance(update, Update) and (update.message or update.edited_message and self.allow_edited)): @@ -66,7 +72,10 @@ class MessageHandler(Handler): else: message = update.message or update.edited_message - res = any(func(message) for func in self.filters) + if isinstance(self.filters, list): + res = any(func(message) for func in self.filters) + else: + res = self.filters(message) else: res = False From ca5e3146c6578d9e2bc482ee090a863eafbfa573 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Fri, 14 Oct 2016 10:32:12 +0200 Subject: [PATCH 07/12] Fix docstring according to Jannes' commentns. --- telegram/ext/filters.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index d2672212a..2a56bfb65 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -26,7 +26,7 @@ class BaseFilter(object): And: - >>> (Filters.text & Filters.entity(MENTION) + >>> (Filters.text & Filters.entity(MENTION)) Or: @@ -34,7 +34,7 @@ class BaseFilter(object): Also works with more than two filters: - >>> (Filters.text & (Filters.entity(URL |Filters.entity(TEXT_LINK)))) + >>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK))) If you want to create your own filters create a class inheriting from this class and implement a `filter` method that returns a boolean: `True` if the message should be handled, `False` @@ -56,7 +56,13 @@ class BaseFilter(object): class MergedFilter(BaseFilter): - """Represents a filter consisting of two other filters.""" + """Represents a filter consisting of two other filters. + + Args: + base_filter: Filter 1 of the merged filter + and_filter: Optional filter to "and" with base_filter. Mutually exclusive with or_filter. + or_filter: Optional filter to "or" with base_filter. Mutually exclusive with and_filter. + """ def __init__(self, base_filter, and_filter=None, or_filter=None): self.base_filter = base_filter From c626044a3033432c778984d879467c3acc461c39 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 15 Oct 2016 22:58:55 +0200 Subject: [PATCH 08/12] Add "all" filter Since and empty list cannot (in the future, currently only deprecated) be used. --- telegram/ext/filters.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/telegram/ext/filters.py b/telegram/ext/filters.py index 2a56bfb65..4b4338107 100644 --- a/telegram/ext/filters.py +++ b/telegram/ext/filters.py @@ -88,6 +88,13 @@ class Filters(object): Predefined filters for use with the `filter` argument of :class:`telegram.ext.MessageHandler`. """ + class _All(BaseFilter): + + def filter(self, message): + return True + + all = _All() + class _Text(BaseFilter): def filter(self, message): From f99b2f8f3b7215c95ff10921a23e7a0bb704421d Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 15 Oct 2016 22:59:41 +0200 Subject: [PATCH 09/12] Inprove coverage --- tests/test_filters.py | 20 +++++++++++++++++++- tests/test_updater.py | 7 +++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index a7208968e..8a6056699 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -28,7 +28,7 @@ import functools sys.path.append('.') from telegram import Message, User, Chat, MessageEntity -from telegram.ext import Filters +from telegram.ext import Filters, BaseFilter from tests.base import BaseTest @@ -206,6 +206,24 @@ class FiltersTest(BaseTest, unittest.TestCase): self.assertTrue((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)) )(self.message)) + self.assertRegex( + str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), + r" and " + r" or " + r">>") + + def test_faulty_custom_filter(self): + + class _CustomFilter(BaseFilter): + pass + + custom = _CustomFilter() + + with self.assertRaises(NotImplementedError): + (custom & Filters.text)(self.message) + if __name__ == '__main__': unittest.main() diff --git a/tests/test_updater.py b/tests/test_updater.py index a269378eb..cbc2a69bc 100644 --- a/tests/test_updater.py +++ b/tests/test_updater.py @@ -182,7 +182,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): self._setup_updater('Test', edited=True) d = self.updater.dispatcher from telegram.ext import Filters - handler = MessageHandler([Filters.text], self.telegramHandlerEditedTest, allow_edited=True) + handler = MessageHandler(Filters.text, self.telegramHandlerEditedTest, allow_edited=True) d.addHandler(handler) self.updater.start_polling(0.01) sleep(.1) @@ -190,8 +190,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): # Remove handler d.removeHandler(handler) - handler = MessageHandler( - [Filters.text], self.telegramHandlerEditedTest, allow_edited=False) + handler = MessageHandler(Filters.text, self.telegramHandlerEditedTest, allow_edited=False) d.addHandler(handler) self.reset() @@ -201,7 +200,7 @@ class UpdaterTest(BaseTest, unittest.TestCase): def test_addTelegramMessageHandlerMultipleMessages(self): self._setup_updater('Multiple', 100) - self.updater.dispatcher.add_handler(MessageHandler([], self.telegramHandlerTest)) + self.updater.dispatcher.add_handler(MessageHandler(Filters.all, self.telegramHandlerTest)) self.updater.start_polling(0.0) sleep(2) self.assertEqual(self.received_message, 'Multiple') From 29e0cc64e99c94ed00266e1a71189d8a6aaa5d9c Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 15 Oct 2016 23:29:46 +0200 Subject: [PATCH 10/12] Fix py2 compat --- tests/test_filters.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index 8a6056699..22b18e0be 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -206,13 +206,22 @@ class FiltersTest(BaseTest, unittest.TestCase): self.assertTrue((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)) )(self.message)) - self.assertRegex( - str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), - r" and " - r" or " - r">>") + try: + self.assertRegex( + str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), + r" and " + r" or " + r">>") + except AttributeError: + self.assertRegexpMatches( + str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), + r" and " + r" or " + r">>") def test_faulty_custom_filter(self): From 5408c23e3306bb253428d6de644b6247359d753b Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 16 Oct 2016 12:48:45 +0200 Subject: [PATCH 11/12] assertRegexMatches still exists for now. Use it. --- tests/test_filters.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index 22b18e0be..a648e02df 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -206,22 +206,13 @@ class FiltersTest(BaseTest, unittest.TestCase): self.assertTrue((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)) )(self.message)) - try: - self.assertRegex( - str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), - r" and " - r" or " - r">>") - except AttributeError: - self.assertRegexpMatches( - str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), - r" and " - r" or " - r">>") + self.assertRegexpMatches( + str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), + r" and " + r" or " + r">>") def test_faulty_custom_filter(self): From 39832d2f6b848c3a9fc9ea3c85615e11e7c89808 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 16 Oct 2016 13:19:42 +0200 Subject: [PATCH 12/12] __str__ behaves differntly on py2 apparently. --- tests/test_filters.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_filters.py b/tests/test_filters.py index a648e02df..c1963ada2 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -208,11 +208,10 @@ class FiltersTest(BaseTest, unittest.TestCase): self.assertRegexpMatches( str((Filters.text & (Filters.forwarded | Filters.entity(MessageEntity.MENTION)))), - r" and " - r" or " - r">>") + r" and or " + r">>") def test_faulty_custom_filter(self):