-
Notifications
You must be signed in to change notification settings - Fork 5.9k
rework CommandHandler #1114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rework CommandHandler #1114
Changes from all commits
3ce9363
714d96d
32a2453
5738f00
0580ae3
bad8f35
7e2fb8d
d133dd4
5bfaca0
bfec5ea
da26227
c450a4e
35c07dd
b325cc1
945a5ec
07747f3
a54cfac
30712c5
e87a705
c69378f
397ab77
46f2a26
fe27912
0f44eed
b54f7e5
f26bf62
8184902
5dc53a5
eeb2816
256a9d1
63f8a07
1fb2e6e
cf85625
860155e
5f238d2
4fd74d9
eea3a6b
72adb72
7e1642c
ae10983
8840c2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| telegram.ext.PrefixHandler | ||
| =========================== | ||
|
|
||
| .. autoclass:: telegram.ext.PrefixHandler | ||
| :members: | ||
| :show-inheritance: |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,22 +16,27 @@ | |
| # | ||
| # 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 CommandHandler class.""" | ||
| """This module contains the CommandHandler and PrefixHandler classes.""" | ||
| import re | ||
|
|
||
| from future.utils import string_types | ||
|
|
||
| from telegram import Update | ||
| from telegram import Update, MessageEntity | ||
| from .handler import Handler | ||
|
|
||
|
|
||
| class CommandHandler(Handler): | ||
| """Handler class to handle Telegram commands. | ||
|
|
||
| Commands are Telegram messages that start with ``/``, optionally followed by an ``@`` and the | ||
| bot's name and/or some additional text. | ||
| bot's name and/or some additional text. The handler will add a ``list`` to the | ||
| :class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings, | ||
| which is the text following the command split on single or consecutive whitespace characters. | ||
|
|
||
| Attributes: | ||
| command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler | ||
| should listen for. | ||
| should listen for. Limitations are the same as described here | ||
| https://core.telegram.org/bots#commands | ||
| callback (:obj:`callable`): The callback function for this handler. | ||
| filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these | ||
| Filters. | ||
|
|
@@ -50,7 +55,7 @@ class CommandHandler(Handler): | |
|
|
||
| Note: | ||
| :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you | ||
| can use to keep any data in will be sent to the :attr:`callback` function.. Related to | ||
| can use to keep any data in will be sent to the :attr:`callback` function. Related to | ||
| either the user or the chat that the update was sent in. For each update from the same user | ||
| or in the same chat, it will be the same ``dict``. | ||
|
|
||
|
|
@@ -59,7 +64,8 @@ class CommandHandler(Handler): | |
|
|
||
| Args: | ||
| command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler | ||
| should listen for. | ||
| should listen for. Limitations are the same as described here | ||
| https://core.telegram.org/bots#commands | ||
| callback (:obj:`callable`): The callback function for this handler. Will be called when | ||
| :attr:`check_update` has determined that an update should be processed by this handler. | ||
| Callback signature for context based API: | ||
|
|
@@ -96,6 +102,8 @@ class CommandHandler(Handler): | |
| ``chat_data`` will be passed to the callback function. Default is ``False``. | ||
| DEPRECATED: Please switch to context based callbacks. | ||
|
|
||
| Raises: | ||
| ValueError - when command is too long or has illegal chars. | ||
| """ | ||
|
|
||
| def __init__(self, | ||
|
|
@@ -119,6 +127,10 @@ def __init__(self, | |
| self.command = [command.lower()] | ||
| else: | ||
| self.command = [x.lower() for x in command] | ||
| for comm in self.command: | ||
| if not re.match(r'^[\da-z_]{1,32}$', comm): | ||
| raise ValueError('Command is not a valid bot command') | ||
|
|
||
| self.filters = filters | ||
| self.allow_edited = allow_edited | ||
| self.pass_args = pass_args | ||
|
|
@@ -135,21 +147,21 @@ def check_update(self, update): | |
| """ | ||
| if (isinstance(update, Update) and | ||
| (update.message or update.edited_message and self.allow_edited)): | ||
| message = update.message or update.edited_message | ||
| message = update.effective_message | ||
|
|
||
| if message.text and message.text.startswith('/') and len(message.text) > 1: | ||
| first_word = message.text_html.split(None, 1)[0] | ||
| if len(first_word) > 1 and first_word.startswith('/'): | ||
| command = first_word[1:].split('@') | ||
| command.append( | ||
| message.bot.username) # in case the command was sent without a username | ||
| if (message.entities and message.entities[0].type == MessageEntity.BOT_COMMAND and | ||
| message.entities[0].offset == 0): | ||
| command = message.text[1:message.entities[0].length] | ||
| args = message.text.split()[1:] | ||
| command = command.split('@') | ||
| command.append(message.bot.username) | ||
|
|
||
| if not (command[0].lower() in self.command | ||
| and command[1].lower() == message.bot.username.lower()): | ||
| return None | ||
| if not (command[0].lower() in self.command and | ||
| command[1].lower() == message.bot.username.lower()): | ||
| return None | ||
|
|
||
| if self.filters is None or self.filters(message): | ||
| return message.text.split()[1:] | ||
| if self.filters is None or self.filters(message): | ||
| return args | ||
|
|
||
| def collect_optional_args(self, dispatcher, update=None, check_result=None): | ||
| 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): | |
|
|
||
| def collect_additional_context(self, context, update, dispatcher, check_result): | ||
| context.args = check_result | ||
|
|
||
|
|
||
| class PrefixHandler(CommandHandler): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we add a docstring note about arguments being availible on the CallbackContext object? (applies to CommandHandler too I suppose) |
||
| """Handler class to handle custom prefix commands | ||
|
|
||
| This is a intermediate handler between :class:`MessageHandler` and :class:`CommandHandler`. | ||
| It supports configurable commands with the same options as CommandHandler. It will respond to | ||
| every combination of :attr:`prefix` and :attr:`command`. It will add a ``list`` to the | ||
| :class:`CallbackContext` named :attr:`CallbackContext.args`. It will contain a list of strings, | ||
| which is the text following the command split on single or consecutive whitespace characters. | ||
|
|
||
| Examples:: | ||
|
|
||
| Single prefix and command: | ||
|
|
||
| PrefixHandler('!', 'test', callback) will respond to '!test'. | ||
|
|
||
| Multiple prefixes, single command: | ||
|
|
||
| PrefixHandler(['!', '#'], 'test', callback) will respond to '!test' and | ||
| '#test'. | ||
|
|
||
| Miltiple prefixes and commands: | ||
|
|
||
| PrefixHandler(['!', '#'], ['test', 'help`], callback) will respond to '!test', | ||
| '#test', '!help' and '#help'. | ||
|
|
||
| Attributes: | ||
| prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`. | ||
| command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a full stop (.)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? On the next line at the end of the sentence...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh derp, sorry :P |
||
| should listen for. | ||
| callback (:obj:`callable`): The callback function for this handler. | ||
| filters (:class:`telegram.ext.BaseFilter`): Optional. Only allow updates with these | ||
| Filters. | ||
| allow_edited (:obj:`bool`): Determines Whether the handler should also accept | ||
| edited messages. | ||
| pass_args (:obj:`bool`): Determines whether the handler should be passed | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there really a need to have the pass_ stuff on this new handler?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmm, In my mind It would be best to keep all handlers compatible to the same model for v11.0 You disagree?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kinda, but mostly from a "we shouldn't be writing code that's already deprecated" standpoint, but since you've already written it. I think it's fine :D |
||
| ``args``. | ||
| pass_update_queue (:obj:`bool`): Determines whether ``update_queue`` will be | ||
| passed to the callback function. | ||
| pass_job_queue (:obj:`bool`): Determines whether ``job_queue`` will be passed to | ||
| the callback function. | ||
| pass_user_data (:obj:`bool`): Determines whether ``user_data`` will be passed to | ||
| the callback function. | ||
| pass_chat_data (:obj:`bool`): Determines whether ``chat_data`` will be passed to | ||
| the callback function. | ||
|
|
||
| Note: | ||
| :attr:`pass_user_data` and :attr:`pass_chat_data` determine whether a ``dict`` you | ||
| can use to keep any data in will be sent to the :attr:`callback` function. Related to | ||
| either the user or the chat that the update was sent in. For each update from the same user | ||
| or in the same chat, it will be the same ``dict``. | ||
|
|
||
| Note that this is DEPRECATED, and you should use context based callbacks. See | ||
| https://git.io/vp113 for more info. | ||
|
|
||
| Args: | ||
| prefix (:obj:`str` | List[:obj:`str`]): The prefix(es) that will precede :attr:`command`. | ||
| command (:obj:`str` | List[:obj:`str`]): The command or list of commands this handler | ||
| should listen for. | ||
| callback (:obj:`callable`): The callback function for this handler. Will be called when | ||
| :attr:`check_update` has determined that an update should be processed by this handler. | ||
| Callback signature for context based API: | ||
|
|
||
| ``def callback(update: Update, context: CallbackContext)`` | ||
|
|
||
| The return value of the callback is usually ignored except for the special case of | ||
| :class:`telegram.ext.ConversationHandler`. | ||
| filters (:class:`telegram.ext.BaseFilter`, optional): A filter inheriting from | ||
| :class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in | ||
| :class:`telegram.ext.filters.Filters`. Filters can be combined using bitwise | ||
| operators (& for and, | for or, ~ for not). | ||
| allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept | ||
| edited messages. Default is ``False``. | ||
| pass_args (:obj:`bool`, optional): Determines whether the handler should be passed the | ||
| arguments passed to the command as a keyword argument called ``args``. It will contain | ||
| a list of strings, which is the text following the command split on single or | ||
| consecutive whitespace characters. Default is ``False`` | ||
| DEPRECATED: Please switch to context based callbacks. | ||
| pass_update_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called | ||
| ``update_queue`` will be passed to the callback function. It will be the ``Queue`` | ||
| instance used by the :class:`telegram.ext.Updater` and :class:`telegram.ext.Dispatcher` | ||
| that contains new updates which can be used to insert updates. Default is ``False``. | ||
| DEPRECATED: Please switch to context based callbacks. | ||
| pass_job_queue (:obj:`bool`, optional): If set to ``True``, a keyword argument called | ||
| ``job_queue`` will be passed to the callback function. It will be a | ||
| :class:`telegram.ext.JobQueue` instance created by the :class:`telegram.ext.Updater` | ||
| which can be used to schedule new jobs. Default is ``False``. | ||
| DEPRECATED: Please switch to context based callbacks. | ||
| pass_user_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called | ||
| ``user_data`` will be passed to the callback function. Default is ``False``. | ||
| DEPRECATED: Please switch to context based callbacks. | ||
| pass_chat_data (:obj:`bool`, optional): If set to ``True``, a keyword argument called | ||
| ``chat_data`` will be passed to the callback function. Default is ``False``. | ||
| DEPRECATED: Please switch to context based callbacks. | ||
|
|
||
| """ | ||
|
|
||
| def __init__(self, | ||
| prefix, | ||
| command, | ||
| callback, | ||
| filters=None, | ||
| allow_edited=False, | ||
| pass_args=False, | ||
| pass_update_queue=False, | ||
| pass_job_queue=False, | ||
| pass_user_data=False, | ||
| pass_chat_data=False): | ||
|
|
||
| super(PrefixHandler, self).__init__( | ||
| 'nocommand', callback, filters=filters, allow_edited=allow_edited, pass_args=pass_args, | ||
| pass_update_queue=pass_update_queue, | ||
| pass_job_queue=pass_job_queue, | ||
| pass_user_data=pass_user_data, | ||
| pass_chat_data=pass_chat_data) | ||
|
|
||
| if isinstance(prefix, string_types): | ||
| self.prefix = [prefix.lower()] | ||
| else: | ||
| self.prefix = prefix | ||
| if isinstance(command, string_types): | ||
| self.command = [command.lower()] | ||
| else: | ||
| self.command = command | ||
| self.command = [x.lower() + y.lower() for x in self.prefix for y in self.command] | ||
|
|
||
| def check_update(self, update): | ||
| """Determines whether an update should be passed to this handlers :attr:`callback`. | ||
|
|
||
| Args: | ||
| update (:class:`telegram.Update`): Incoming telegram update. | ||
|
|
||
| Returns: | ||
| :obj:`bool` | ||
|
|
||
| """ | ||
| if (isinstance(update, Update) and | ||
| (update.message or update.edited_message and self.allow_edited)): | ||
| message = update.effective_message | ||
|
|
||
| text_list = message.text.split() | ||
| if text_list[0].lower() not in self.command: | ||
| return None | ||
| if self.filters is None or self.filters(message): | ||
| return text_list[1:] | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that 32 chars is a hard limit, dispite the docs... Do we care?
Both my phone and my desktop app, highlights commands that are longer than 32 chars at least.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a discrepancy here. @Botfather won't let you add too long commands for suggestion, but they are recognised as entities.
I vote to stay with the docs and enforce 32 chars