X Tutup
Skip to content

Commit 4e5f458

Browse files
authored
Merge pull request python-telegram-bot#411 from python-telegram-bot/bitwise-filters
Make filters and/or-able using bitwise operators.
2 parents 225bc24 + 39832d2 commit 4e5f458

File tree

7 files changed

+303
-100
lines changed

7 files changed

+303
-100
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
telegram.ext.filters module
2+
===========================
3+
4+
.. automodule:: telegram.ext.filters
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

docs/source/telegram.ext.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Submodules
1515
telegram.ext.commandhandler
1616
telegram.ext.inlinequeryhandler
1717
telegram.ext.messagehandler
18+
telegram.ext.filters
1819
telegram.ext.regexhandler
1920
telegram.ext.stringcommandhandler
2021
telegram.ext.stringregexhandler

telegram/ext/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
from .commandhandler import CommandHandler
2727
from .handler import Handler
2828
from .inlinequeryhandler import InlineQueryHandler
29-
from .messagehandler import MessageHandler, Filters
29+
from .messagehandler import MessageHandler
30+
from .filters import BaseFilter, Filters
3031
from .regexhandler import RegexHandler
3132
from .stringcommandhandler import StringCommandHandler
3233
from .stringregexhandler import StringRegexHandler
@@ -35,5 +36,5 @@
3536

3637
__all__ = ('Dispatcher', 'JobQueue', 'Job', 'Updater', 'CallbackQueryHandler',
3738
'ChosenInlineResultHandler', 'CommandHandler', 'Handler', 'InlineQueryHandler',
38-
'MessageHandler', 'Filters', 'RegexHandler', 'StringCommandHandler',
39+
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
3940
'StringRegexHandler', 'TypeHandler', 'ConversationHandler')

telegram/ext/filters.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env python
2+
#
3+
# A library that provides a Python interface to the Telegram Bot API
4+
# Copyright (C) 2015-2016
5+
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
6+
#
7+
# This program is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
#
12+
# This program is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser Public License for more details.
16+
#
17+
# You should have received a copy of the GNU Lesser Public License
18+
# along with this program. If not, see [http://www.gnu.org/licenses/].
19+
""" This module contains the Filters for use with the MessageHandler class """
20+
21+
22+
class BaseFilter(object):
23+
"""Base class for all Message Filters
24+
25+
Subclassing from this class filters to be combined using bitwise operators:
26+
27+
And:
28+
29+
>>> (Filters.text & Filters.entity(MENTION))
30+
31+
Or:
32+
33+
>>> (Filters.audio | Filters.video)
34+
35+
Also works with more than two filters:
36+
37+
>>> (Filters.text & (Filters.entity(URL) | Filters.entity(TEXT_LINK)))
38+
39+
If you want to create your own filters create a class inheriting from this class and implement
40+
a `filter` method that returns a boolean: `True` if the message should be handled, `False`
41+
otherwise. Note that the filters work only as class instances, not actual class objects
42+
(so remember to initialize your filter classes).
43+
"""
44+
45+
def __call__(self, message):
46+
return self.filter(message)
47+
48+
def __and__(self, other):
49+
return MergedFilter(self, and_filter=other)
50+
51+
def __or__(self, other):
52+
return MergedFilter(self, or_filter=other)
53+
54+
def filter(self, message):
55+
raise NotImplementedError
56+
57+
58+
class MergedFilter(BaseFilter):
59+
"""Represents a filter consisting of two other filters.
60+
61+
Args:
62+
base_filter: Filter 1 of the merged filter
63+
and_filter: Optional filter to "and" with base_filter. Mutually exclusive with or_filter.
64+
or_filter: Optional filter to "or" with base_filter. Mutually exclusive with and_filter.
65+
"""
66+
67+
def __init__(self, base_filter, and_filter=None, or_filter=None):
68+
self.base_filter = base_filter
69+
self.and_filter = and_filter
70+
self.or_filter = or_filter
71+
72+
def filter(self, message):
73+
if self.and_filter:
74+
return self.base_filter(message) and self.and_filter(message)
75+
elif self.or_filter:
76+
return self.base_filter(message) or self.or_filter(message)
77+
78+
def __str__(self):
79+
return ("<telegram.ext.filters.MergedFilter consisting of"
80+
" {} {} {}>").format(self.base_filter, "and" if self.and_filter else "or",
81+
self.and_filter or self.or_filter)
82+
83+
__repr__ = __str__
84+
85+
86+
class Filters(object):
87+
"""
88+
Predefined filters for use with the `filter` argument of :class:`telegram.ext.MessageHandler`.
89+
"""
90+
91+
class _All(BaseFilter):
92+
93+
def filter(self, message):
94+
return True
95+
96+
all = _All()
97+
98+
class _Text(BaseFilter):
99+
100+
def filter(self, message):
101+
return bool(message.text and not message.text.startswith('/'))
102+
103+
text = _Text()
104+
105+
class _Command(BaseFilter):
106+
107+
def filter(self, message):
108+
return bool(message.text and message.text.startswith('/'))
109+
110+
command = _Command()
111+
112+
class _Audio(BaseFilter):
113+
114+
def filter(self, message):
115+
return bool(message.audio)
116+
117+
audio = _Audio()
118+
119+
class _Document(BaseFilter):
120+
121+
def filter(self, message):
122+
return bool(message.document)
123+
124+
document = _Document()
125+
126+
class _Photo(BaseFilter):
127+
128+
def filter(self, message):
129+
return bool(message.photo)
130+
131+
photo = _Photo()
132+
133+
class _Sticker(BaseFilter):
134+
135+
def filter(self, message):
136+
return bool(message.sticker)
137+
138+
sticker = _Sticker()
139+
140+
class _Video(BaseFilter):
141+
142+
def filter(self, message):
143+
return bool(message.video)
144+
145+
video = _Video()
146+
147+
class _Voice(BaseFilter):
148+
149+
def filter(self, message):
150+
return bool(message.voice)
151+
152+
voice = _Voice()
153+
154+
class _Contact(BaseFilter):
155+
156+
def filter(self, message):
157+
return bool(message.contact)
158+
159+
contact = _Contact()
160+
161+
class _Location(BaseFilter):
162+
163+
def filter(self, message):
164+
return bool(message.location)
165+
166+
location = _Location()
167+
168+
class _Venue(BaseFilter):
169+
170+
def filter(self, message):
171+
return bool(message.venue)
172+
173+
venue = _Venue()
174+
175+
class _StatusUpdate(BaseFilter):
176+
177+
def filter(self, message):
178+
return bool(message.new_chat_member or message.left_chat_member
179+
or message.new_chat_title or message.new_chat_photo
180+
or message.delete_chat_photo or message.group_chat_created
181+
or message.supergroup_chat_created or message.channel_chat_created
182+
or message.migrate_to_chat_id or message.migrate_from_chat_id
183+
or message.pinned_message)
184+
185+
status_update = _StatusUpdate()
186+
187+
class _Forwarded(BaseFilter):
188+
189+
def filter(self, message):
190+
return bool(message.forward_date)
191+
192+
forwarded = _Forwarded()
193+
194+
class entity(BaseFilter):
195+
"""Filters messages to only allow those which have a :class:`telegram.MessageEntity`
196+
where their `type` matches `entity_type`.
197+
198+
Args:
199+
entity_type: Entity type to check for. All types can be found as constants
200+
in :class:`telegram.MessageEntity`.
201+
202+
Returns: function to use as filter
203+
"""
204+
205+
def __init__(self, entity_type):
206+
self.entity_type = entity_type
207+
208+
def filter(self, message):
209+
return any([entity.type == self.entity_type for entity in message.entities])

telegram/ext/messagehandler.py

Lines changed: 16 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -17,105 +17,24 @@
1717
# You should have received a copy of the GNU Lesser Public License
1818
# along with this program. If not, see [http://www.gnu.org/licenses/].
1919
""" This module contains the MessageHandler class """
20+
import warnings
2021

2122
from .handler import Handler
2223
from telegram import Update
2324
from telegram.utils.deprecate import deprecate
2425

2526

26-
class Filters(object):
27-
"""
28-
Convenient namespace (class) & methods for the filter funcs of the
29-
MessageHandler class.
30-
"""
31-
32-
@staticmethod
33-
def text(message):
34-
return message.text and not message.text.startswith('/')
35-
36-
@staticmethod
37-
def command(message):
38-
return message.text and message.text.startswith('/')
39-
40-
@staticmethod
41-
def audio(message):
42-
return bool(message.audio)
43-
44-
@staticmethod
45-
def document(message):
46-
return bool(message.document)
47-
48-
@staticmethod
49-
def photo(message):
50-
return bool(message.photo)
51-
52-
@staticmethod
53-
def sticker(message):
54-
return bool(message.sticker)
55-
56-
@staticmethod
57-
def video(message):
58-
return bool(message.video)
59-
60-
@staticmethod
61-
def voice(message):
62-
return bool(message.voice)
63-
64-
@staticmethod
65-
def contact(message):
66-
return bool(message.contact)
67-
68-
@staticmethod
69-
def location(message):
70-
return bool(message.location)
71-
72-
@staticmethod
73-
def venue(message):
74-
return bool(message.venue)
75-
76-
@staticmethod
77-
def status_update(message):
78-
return bool(message.new_chat_member or message.left_chat_member or message.new_chat_title
79-
or message.new_chat_photo or message.delete_chat_photo
80-
or message.group_chat_created or message.supergroup_chat_created
81-
or message.channel_chat_created or message.migrate_to_chat_id
82-
or message.migrate_from_chat_id or message.pinned_message)
83-
84-
@staticmethod
85-
def forwarded(message):
86-
return bool(message.forward_date)
87-
88-
@staticmethod
89-
def entity(entity_type):
90-
"""Filters messages to only allow those which have a :class:`telegram.MessageEntity`
91-
where their `type` matches `entity_type`.
92-
93-
Args:
94-
entity_type: Entity type to check for. All types can be found as constants
95-
in :class:`telegram.MessageEntity`.
96-
97-
Returns: function to use as filter
98-
"""
99-
100-
def entities_filter(message):
101-
return any([entity.type == entity_type for entity in message.entities])
102-
103-
return entities_filter
104-
105-
10627
class MessageHandler(Handler):
10728
"""
10829
Handler class to handle telegram messages. Messages are Telegram Updates
10930
that do not contain a command. They might contain text, media or status
11031
updates.
11132
11233
Args:
113-
filters (list[function]): A list of filter functions. Standard filters
114-
can be found in the Filters class above.
115-
| Each `function` takes ``Update`` as arg and returns ``bool``.
116-
| All messages that match at least one of those filters will be
117-
accepted. If ``bool(filters)`` evaluates to ``False``, messages are
118-
not filtered.
34+
filters (telegram.ext.BaseFilter): A filter inheriting from
35+
:class:`telegram.filters.BaseFilter`. Standard filters can be found in
36+
:class:`telegram.filters.Filters`. Filters can be combined using bitwise
37+
operators (& for and, | for or).
11938
callback (function): A function that takes ``bot, update`` as
12039
positional arguments. It will be called when the ``check_update``
12140
has determined that an update should be processed by this handler.
@@ -137,6 +56,13 @@ def __init__(self,
13756
self.filters = filters
13857
self.allow_edited = allow_edited
13958

59+
# We put this up here instead of with the rest of checking code
60+
# in check_update since we don't wanna spam a ton
61+
if isinstance(self.filters, list):
62+
warnings.warn('Using a list of filters in MessageHandler is getting '
63+
'deprecated, please use bitwise operators (& and |) '
64+
'instead. More info: https://git.io/vPTbc.')
65+
14066
def check_update(self, update):
14167
if (isinstance(update, Update)
14268
and (update.message or update.edited_message and self.allow_edited)):
@@ -146,7 +72,10 @@ def check_update(self, update):
14672

14773
else:
14874
message = update.message or update.edited_message
149-
res = any(func(message) for func in self.filters)
75+
if isinstance(self.filters, list):
76+
res = any(func(message) for func in self.filters)
77+
else:
78+
res = self.filters(message)
15079

15180
else:
15281
res = False

0 commit comments

Comments
 (0)
X Tutup