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+
2022from future .utils import string_types
2123
22- from telegram import Update
24+ from telegram import Update , MessageEntity
2325from .handler import Handler
2426
2527
2628class 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 :]
0 commit comments