Skip to content

Commit 87afd98

Browse files
authored
CommandHandler overhaul and PrefixHandler added (python-telegram-bot#1114)
* Commandhandler reworked * Make CommandHandler strict Only register valid botcommands, else raise ValueError * Add PrefixHandler * declare encoding on test_commandhandler * Fix some tests dependend on CommandHandler * CR changes * small docfix. * Test all possibilities for PrefixHandler
1 parent 247577b commit 87afd98

16 files changed

+557
-79
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
telegram.ext.PrefixHandler
2+
===========================
3+
4+
.. autoclass:: telegram.ext.PrefixHandler
5+
:members:
6+
:show-inheritance:

docs/source/telegram.ext.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Handlers
2525
telegram.ext.inlinequeryhandler
2626
telegram.ext.messagehandler
2727
telegram.ext.precheckoutqueryhandler
28+
telegram.ext.prefixhandler
2829
telegram.ext.regexhandler
2930
telegram.ext.shippingqueryhandler
3031
telegram.ext.stringcommandhandler

telegram/ext/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from .updater import Updater
2626
from .callbackqueryhandler import CallbackQueryHandler
2727
from .choseninlineresulthandler import ChosenInlineResultHandler
28-
from .commandhandler import CommandHandler
28+
from .commandhandler import CommandHandler, PrefixHandler
2929
from .inlinequeryhandler import InlineQueryHandler
3030
from .messagehandler import MessageHandler
3131
from .filters import BaseFilter, Filters
@@ -44,4 +44,4 @@
4444
'MessageHandler', 'BaseFilter', 'Filters', 'RegexHandler', 'StringCommandHandler',
4545
'StringRegexHandler', 'TypeHandler', 'ConversationHandler',
4646
'PreCheckoutQueryHandler', 'ShippingQueryHandler', 'MessageQueue', 'DelayQueue',
47-
'DispatcherHandlerStop', 'run_async', 'CallbackContext')
47+
'DispatcherHandlerStop', 'run_async', 'CallbackContext', 'PrefixHandler')

telegram/ext/callbackcontext.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ class CallbackContext(object):
3737
regex-supported handler, this will contain the object returned from
3838
``re.match(pattern, string)``.
3939
args (List[:obj:`str`], optional): Arguments passed to a command if the associated update
40-
is handled by :class:`telegram.ext.CommandHandler` or
41-
:class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the text
42-
after the command, using any whitespace string as a delimiter.
40+
is handled by :class:`telegram.ext.CommandHandler`, :class:`telegram.ext.PrefixHandler`
41+
or :class:`telegram.ext.StringCommandHandler`. It contains a list of the words in the
42+
text after the command, using any whitespace string as a delimiter.
4343
error (:class:`telegram.TelegramError`, optional): The Telegram error that was raised.
4444
Only present when passed to a error handler registered with
4545
:attr:`telegram.ext.Dispatcher.add_error_handler`.

telegram/ext/callbackqueryhandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class CallbackQueryHandler(Handler):
5050
5151
Note:
5252
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
53-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
53+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
5454
either the user or the chat that the update was sent in. For each update from the same user
5555
or in the same chat, it will be the same ``dict``.
5656

telegram/ext/choseninlineresulthandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ChosenInlineResultHandler(Handler):
3838
3939
Note:
4040
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
41-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
41+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
4242
either the user or the chat that the update was sent in. For each update from the same user
4343
or in the same chat, it will be the same ``dict``.
4444

telegram/ext/commandhandler.py

Lines changed: 176 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,27 @@
1616
#
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/].
19-
"""This module contains the CommandHandler class."""
19+
"""This module contains the CommandHandler and PrefixHandler classes."""
20+
import re
21+
2022
from future.utils import string_types
2123

22-
from telegram import Update
24+
from telegram import Update, MessageEntity
2325
from .handler import Handler
2426

2527

2628
class CommandHandler(Handler):
2729
"""Handler class to handle Telegram commands.
2830
2931
Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the
30-
bot's name and/or some additional text.
32+
bot's name and/or some additional text. The handler will add a ``list`` to the
33+
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
34+
which is the text following the command split on single or consecutive whitespace characters.
3135
3236
Attributes:
3337
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
34-
should listen for.
38+
should listen for. Limitations are the same as described here
39+
https://core.telegram.org/bots#commands
3540
callback (:obj:`callable`): The callback function for this handler.
3641
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
3742
Filters.
@@ -50,7 +55,7 @@ class CommandHandler(Handler):
5055
5156
Note:
5257
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
53-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
58+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
5459
either the user or the chat that the update was sent in. For each update from the same user
5560
or in the same chat, it will be the same ``dict``.
5661
@@ -59,7 +64,8 @@ class CommandHandler(Handler):
5964
6065
Args:
6166
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
62-
should listen for.
67+
should listen for. Limitations are the same as described here
68+
https://core.telegram.org/bots#commands
6369
callback (:obj:`callable`): The callback function for this handler. Will be called when
6470
:attr:`check_update` has determined that an update should be processed by this handler.
6571
Callback signature for context based API:
@@ -96,6 +102,8 @@ class CommandHandler(Handler):
96102
``chat_data`` will be passed to the callback function. Default is ``False``.
97103
DEPRECATED: Please switch to context based callbacks.
98104
105+
Raises:
106+
ValueError - when command is too long or has illegal chars.
99107
"""
100108

101109
def __init__(self,
@@ -119,6 +127,10 @@ def __init__(self,
119127
self.command = [command.lower()]
120128
else:
121129
self.command = [x.lower() for x in command]
130+
for comm in self.command:
131+
if not re.match(r'^[\da-z_]{1,32}$', comm):
132+
raise ValueError('Command is not a valid bot command')
133+
122134
self.filters = filters
123135
self.allow_edited = allow_edited
124136
self.pass_args = pass_args
@@ -135,21 +147,21 @@ def check_update(self, update):
135147
"""
136148
if (isinstance(update, Update) and
137149
(update.message or update.edited_message and self.allow_edited)):
138-
message = update.message or update.edited_message
150+
message = update.effective_message
139151

140-
if message.text and message.text.startswith('/') and len(message.text) > 1:
141-
first_word = message.text_html.split(None, 1)[0]
142-
if len(first_word) > 1 and first_word.startswith('/'):
143-
command = first_word[1:].split('@')
144-
command.append(
145-
message.bot.username) # in case the command was sent without a username
152+
if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND and
153+
message.entities[0].offset == 0):
154+
command = message.text[1:message.entities[0].length]
155+
args = message.text.split()[1:]
156+
command = command.split('@')
157+
command.append(message.bot.username)
146158

147-
if not (command[0].lower() in self.command
148-
and command[1].lower() == message.bot.username.lower()):
149-
return None
159+
if not (command[0].lower() in self.command and
160+
command[1].lower() == message.bot.username.lower()):
161+
return None
150162

151-
if self.filters is None or self.filters(message):
152-
return message.text.split()[1:]
163+
if self.filters is None or self.filters(message):
164+
return args
153165

154166
def collect_optional_args(self, dispatcher, update=None, check_result=None):
155167
optional_args = super(CommandHandler, self).collect_optional_args(dispatcher, update)
@@ -159,3 +171,149 @@ def collect_optional_args(self, dispatcher, update=None, check_result=None):
159171

160172
def collect_additional_context(self, context, update, dispatcher, check_result):
161173
context.args = check_result
174+
175+
176+
class PrefixHandler(CommandHandler):
177+
"""Handler class to handle custom prefix commands
178+
179+
This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`.
180+
It supports configurable commands with the same options as CommandHandler. It will respond to
181+
every combination of :attr:`prefix` and :attr:`command`. It will add a ``list`` to the
182+
:class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings,
183+
which is the text following the command split on single or consecutive whitespace characters.
184+
185+
Examples::
186+
187+
Single prefix and command:
188+
189+
PrefixHandler('!', 'test', callback) will respond to '!test'.
190+
191+
Multiple prefixes, single command:
192+
193+
PrefixHandler(['!', '#'], 'test', callback) will respond to '!test' and
194+
'#test'.
195+
196+
Miltiple prefixes and commands:
197+
198+
PrefixHandler(['!', '#'], ['test', 'help`], callback) will respond to '!test',
199+
'#test', '!help' and '#help'.
200+
201+
Attributes:
202+
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
203+
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
204+
should listen for.
205+
callback (:obj:`callable`): The callback function for this handler.
206+
filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these
207+
Filters.
208+
allow_edited (:obj:`bool`): Determines Whether the handler should also accept
209+
edited messages.
210+
pass_args (:obj:`bool`): Determines whether the handler should be passed
211+
``args``.
212+
pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be
213+
passed to the callback function.
214+
pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to
215+
the callback function.
216+
pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to
217+
the callback function.
218+
pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to
219+
the callback function.
220+
221+
Note:
222+
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
223+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
224+
either the user or the chat that the update was sent in. For each update from the same user
225+
or in the same chat, it will be the same ``dict``.
226+
227+
Note that this is DEPRECATED, and you should use context based callbacks. See
228+
https://git.io/vp113 for more info.
229+
230+
Args:
231+
prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`.
232+
command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler
233+
should listen for.
234+
callback (:obj:`callable`): The callback function for this handler. Will be called when
235+
:attr:`check_update` has determined that an update should be processed by this handler.
236+
Callback signature for context based API:
237+
238+
``def callback(update: Update, context: CallbackContext)``
239+
240+
The return value of the callback is usually ignored except for the special case of
241+
:class:`telegram.ext.ConversationHandler`.
242+
filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from
243+
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
244+
:class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise
245+
operators (& for and, | for or, ~ for not).
246+
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
247+
edited messages. Default is ``False``.
248+
pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the
249+
arguments passed to the command as a keyword argument called ``args``. It will contain
250+
a list of strings, which is the text following the command split on single or
251+
consecutive whitespace characters. Default is ``False``
252+
DEPRECATED: Please switch to context based callbacks.
253+
pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
254+
``update_queue`` will be passed to the callback function. It will be the ``Queue``
255+
instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher`
256+
that contains new updates which can be used to insert updates. Default is ``False``.
257+
DEPRECATED: Please switch to context based callbacks.
258+
pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called
259+
``job_queue`` will be passed to the callback function. It will be a
260+
:class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater`
261+
which can be used to schedule new jobs. Default is ``False``.
262+
DEPRECATED: Please switch to context based callbacks.
263+
pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
264+
``user_data`` will be passed to the callback function. Default is ``False``.
265+
DEPRECATED: Please switch to context based callbacks.
266+
pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called
267+
``chat_data`` will be passed to the callback function. Default is ``False``.
268+
DEPRECATED: Please switch to context based callbacks.
269+
270+
"""
271+
272+
def __init__(self,
273+
prefix,
274+
command,
275+
callback,
276+
filters=None,
277+
allow_edited=False,
278+
pass_args=False,
279+
pass_update_queue=False,
280+
pass_job_queue=False,
281+
pass_user_data=False,
282+
pass_chat_data=False):
283+
284+
super(PrefixHandler, self).__init__(
285+
'nocommand', callback, filters=filters, allow_edited=allow_edited, pass_args=pass_args,
286+
pass_update_queue=pass_update_queue,
287+
pass_job_queue=pass_job_queue,
288+
pass_user_data=pass_user_data,
289+
pass_chat_data=pass_chat_data)
290+
291+
if isinstance(prefix, string_types):
292+
self.prefix = [prefix.lower()]
293+
else:
294+
self.prefix = prefix
295+
if isinstance(command, string_types):
296+
self.command = [command.lower()]
297+
else:
298+
self.command = command
299+
self.command = [x.lower() + y.lower() for x in self.prefix for y in self.command]
300+
301+
def check_update(self, update):
302+
"""Determines whether an update should be passed to this handlers :attr:`callback`.
303+
304+
Args:
305+
update (:class:`telegram.Update`): Incoming telegram update.
306+
307+
Returns:
308+
:obj:`bool`
309+
310+
"""
311+
if (isinstance(update, Update) and
312+
(update.message or update.edited_message and self.allow_edited)):
313+
message = update.effective_message
314+
315+
text_list = message.text.split()
316+
if text_list[0].lower() not in self.command:
317+
return None
318+
if self.filters is None or self.filters(message):
319+
return text_list[1:]

telegram/ext/handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Handler(object):
3636
3737
Note:
3838
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
39-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
39+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
4040
either the user or the chat that the update was sent in. For each update from the same user
4141
or in the same chat, it will be the same ``dict``.
4242

telegram/ext/inlinequeryhandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class InlineQueryHandler(Handler):
4949
5050
Note:
5151
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
52-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
52+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
5353
either the user or the chat that the update was sent in. For each update from the same user
5454
or in the same chat, it will be the same ``dict``.
5555

telegram/ext/messagehandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class MessageHandler(Handler):
5050
5151
Note:
5252
:attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you
53-
can use to keep any data in will be sent to the :attr:`callback` function.. Related to
53+
can use to keep any data in will be sent to the :attr:`callback` function. Related to
5454
either the user or the chat that the update was sent in. For each update from the same user
5555
or in the same chat, it will be the same ``dict``.
5656

0 commit comments

Comments
 (0)