python-telegram-bot/telegram/ext/_handlers/commandhandler.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

224 lines
9.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
2024-02-19 20:06:25 +01:00
# Copyright (C) 2015-2024
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# 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 CommandHandler class."""
import re
from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union
from telegram import MessageEntity, Update
from telegram._utils.defaultvalue import DEFAULT_TRUE
2023-02-02 18:55:07 +01:00
from telegram._utils.types import SCT, DVType
from telegram.ext import filters as filters_module
from telegram.ext._handlers.basehandler import BaseHandler
2023-02-02 18:55:07 +01:00
from telegram.ext._utils.types import CCT, FilterDataDict, HandlerCallback
2020-10-06 19:28:40 +02:00
if TYPE_CHECKING:
from telegram.ext import Application
2020-10-06 19:28:40 +02:00
RT = TypeVar("RT")
2024-09-14 08:16:39 +02:00
class CommandHandler(BaseHandler[Update, CCT, RT]):
"""Handler class to handle Telegram commands.
2017-09-01 08:43:08 +02:00
Commands are Telegram messages that start with a :attr:`telegram.MessageEntity.BOT_COMMAND`
(so with ``/``, optionally followed by an ``@`` and the bot's name and/or some additional
text). The handler will add a :obj:`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.
2016-04-16 16:36:12 +02:00
By default, the handler listens to messages as well as edited messages. To change this behavior
use :attr:`~filters.UpdateType.EDITED_MESSAGE <telegram.ext.filters.UpdateType.EDITED_MESSAGE>`
in the filter argument.
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
Documentation Improvements (#2008) * Minor doc updates, following official API docs * Fix spelling in Defaults docstrings * Clarify Changelog of v12.7 about aware dates * Fix typo in CHANGES.rst (#2024) * Fix PicklePersistence.flush() with only bot_data (#2017) * Update pylint in pre-commit to fix CI (#2018) * Add Filters.via_bot (#2009) * feat: via_bot filter also fixing a small mistake in the empty parameter of the user filter and improve docs slightly * fix: forgot to set via_bot to None * fix: redoing subclassing to copy paste solution * Cosmetic changes Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de> * Update CHANGES.rst Fixed Typo Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> * Update downloads badge, add info on IRC Channel to Getting Help section * Remove RegexHandler from ConversationHandlers Docs (#1973) Replaced RegexHandler with MessageHandler, since the former is deprecated * Fix Filters.via_bot docstrings * Add notes on Markdown v1 being legacy mode * Fixed typo in the Regex doc.. (#2036) * Typo: Spelling * Minor cleanup from #2043 * Document CommandHandler ignoring channel posts * Doc fixes for a few telegram.ext classes * Doc fixes for most `telegram` classes. * pep-8 forgot the hard wrap is at 99 chars, not 100! fixed a few spelling mistakes too. * Address review and made rendering of booleans consistent True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes. * Few doc fixes for inline* classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram/files classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram.Game Mostly just added hyperlinks. And fixed message length doc. As usual, docs were cross-checked with official tg api docs. * Very minor doc fix for passportfile.py and passportelementerrors.py Didn't bother changing too much since this seems to be a custom implementation. * Doc fixes for telegram.payments As usual, cross-checked with official bot api docs. * Address review 2 Few tiny other fixes too. * Changed from ``True/False/None`` to :obj:`True/False/None` project-wide. Few tiny other doc fixes too. Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com> Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
Note:
:class:`CommandHandler` does *not* handle (edited) channel posts and does *not* handle
commands that are part of a caption. Please use :class:`~telegram.ext.MessageHandler`
with a suitable combination of filters (e.g.
:attr:`telegram.ext.filters.UpdateType.CHANNEL_POSTS`,
:attr:`telegram.ext.filters.CAPTION` and :class:`telegram.ext.filters.Regex`) to handle
those messages.
Note:
If you want to support a different entity in the beginning, e.g. if a command message is
wrapped in a :attr:`telegram.MessageEntity.CODE`, use the
:class:`telegram.ext.PrefixHandler`.
2020-10-04 17:20:33 +02:00
Warning:
When setting :paramref:`block` to :obj:`False`, you cannot rely on adding custom
2020-10-04 17:20:33 +02:00
attributes to :class:`telegram.ext.CallbackContext`. See its docs for more info.
Examples:
* :any:`Timer Bot <examples.timerbot>`
* :any:`Error Handler Bot <examples.errorhandlerbot>`
.. versionchanged:: 20.0
* Renamed the attribute ``command`` to :attr:`commands`, which now is always a
:class:`frozenset`
* Updating the commands this handler listens to is no longer possible.
2016-04-16 16:36:12 +02:00
Args:
command (:obj:`str` | Collection[:obj:`str`]):
The command or list of commands this handler should listen for. Case-insensitive.
Limitations are the same as for :attr:`telegram.BotCommand.command`.
callback (:term:`coroutine function`): The callback function for this handler. Will be
called when :meth:`check_update` has determined that an update should be processed by
this handler. Callback signature::
async 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.filters.BaseFilter`, optional): A filter inheriting from
:class:`telegram.ext.filters.BaseFilter`. Standard filters can be found in
2021-11-20 11:36:18 +01:00
:mod:`telegram.ext.filters`. Filters can be combined using bitwise
operators (``&`` for :keyword:`and`, ``|`` for :keyword:`or`, ``~`` for :keyword:`not`)
block (:obj:`bool`, optional): Determines whether the return value of the callback should
be awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`. Defaults to :obj:`True`.
2017-09-01 08:43:08 +02:00
.. seealso:: :wiki:`Concurrency`
has_args (:obj:`bool` | :obj:`int`, optional):
Determines whether the command handler should process the update or not.
If :obj:`True`, the handler will process any non-zero number of args.
If :obj:`False`, the handler will only process if there are no args.
if :obj:`int`, the handler will only process if there are exactly that many args.
Defaults to :obj:`None`, which means the handler will process any or no args.
2023-09-03 14:46:28 +02:00
.. versionadded:: 20.5
Raises:
:exc:`ValueError`: When the command is too long or has illegal chars.
Attributes:
commands (frozenset[:obj:`str`]): The set of commands this handler should listen for.
callback (:term:`coroutine function`): The callback function for this handler.
filters (:class:`telegram.ext.filters.BaseFilter`): Optional. Only allow updates with these
filters.
block (:obj:`bool`): Determines whether the return value of the callback should be
awaited before processing the next handler in
:meth:`telegram.ext.Application.process_update`.
has_args (:obj:`bool` | :obj:`int` | None):
Optional argument, otherwise all implementations of :class:`CommandHandler` will break.
Defaults to :obj:`None`, which means the handler will process any args or no args.
2023-09-03 14:46:28 +02:00
.. versionadded:: 20.5
2016-04-16 16:36:12 +02:00
"""
__slots__ = ("commands", "filters", "has_args")
def __init__(
2024-09-14 08:16:39 +02:00
self: "CommandHandler[CCT, RT]",
command: SCT[str],
callback: HandlerCallback[Update, CCT, RT],
filters: Optional[filters_module.BaseFilter] = None,
2023-02-02 18:55:07 +01:00
block: DVType[bool] = DEFAULT_TRUE,
has_args: Optional[Union[bool, int]] = None,
2020-10-06 19:28:40 +02:00
):
super().__init__(callback, block=block)
if isinstance(command, str):
commands = frozenset({command.lower()})
else:
commands = frozenset(x.lower() for x in command)
for comm in commands:
if not re.match(r"^[\da-z_]{1,32}$", comm):
raise ValueError(f"Command `{comm}` is not a valid bot command")
self.commands: frozenset[str] = commands
2023-02-02 18:55:07 +01:00
self.filters: filters_module.BaseFilter = (
filters if filters is not None else filters_module.UpdateType.MESSAGES
)
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
self.has_args: Optional[Union[bool, int]] = has_args
if (isinstance(self.has_args, int)) and (self.has_args < 0):
raise ValueError("CommandHandler argument has_args cannot be a negative integer")
def _check_correct_args(self, args: list[str]) -> Optional[bool]:
"""Determines whether the args are correct for this handler. Implemented in check_update().
Args:
args (:obj:`list`): The args for the handler.
Returns:
:obj:`bool`: Whether the args are valid for this handler.
"""
return bool(
(self.has_args is None)
or (self.has_args is True and args)
or (self.has_args is False and not args)
or (isinstance(self.has_args, int) and len(args) == self.has_args)
)
2020-10-06 19:28:40 +02:00
def check_update(
self, update: object
) -> Optional[Union[bool, tuple[list[str], Optional[Union[bool, FilterDataDict]]]]]:
"""Determines whether an update should be passed to this handler's :attr:`callback`.
Args:
update (:class:`telegram.Update` | :obj:`object`): Incoming update.
Returns:
Documentation Improvements (#2008) * Minor doc updates, following official API docs * Fix spelling in Defaults docstrings * Clarify Changelog of v12.7 about aware dates * Fix typo in CHANGES.rst (#2024) * Fix PicklePersistence.flush() with only bot_data (#2017) * Update pylint in pre-commit to fix CI (#2018) * Add Filters.via_bot (#2009) * feat: via_bot filter also fixing a small mistake in the empty parameter of the user filter and improve docs slightly * fix: forgot to set via_bot to None * fix: redoing subclassing to copy paste solution * Cosmetic changes Co-authored-by: Hinrich Mahler <hinrich.mahler@freenet.de> * Update CHANGES.rst Fixed Typo Co-authored-by: Bibo-Joshi <hinrich.mahler@freenet.de> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> * Update downloads badge, add info on IRC Channel to Getting Help section * Remove RegexHandler from ConversationHandlers Docs (#1973) Replaced RegexHandler with MessageHandler, since the former is deprecated * Fix Filters.via_bot docstrings * Add notes on Markdown v1 being legacy mode * Fixed typo in the Regex doc.. (#2036) * Typo: Spelling * Minor cleanup from #2043 * Document CommandHandler ignoring channel posts * Doc fixes for a few telegram.ext classes * Doc fixes for most `telegram` classes. * pep-8 forgot the hard wrap is at 99 chars, not 100! fixed a few spelling mistakes too. * Address review and made rendering of booleans consistent True, False, None are now rendered with ``bool`` wherever they weren't in telegram and telegram.ext classes. * Few doc fixes for inline* classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram/files classes As usual, docs were cross-checked with official tg api docs. * Doc fixes for telegram.Game Mostly just added hyperlinks. And fixed message length doc. As usual, docs were cross-checked with official tg api docs. * Very minor doc fix for passportfile.py and passportelementerrors.py Didn't bother changing too much since this seems to be a custom implementation. * Doc fixes for telegram.payments As usual, cross-checked with official bot api docs. * Address review 2 Few tiny other fixes too. * Changed from ``True/False/None`` to :obj:`True/False/None` project-wide. Few tiny other doc fixes too. Co-authored-by: Robert Geislinger <mitachundkrach@gmail.com> Co-authored-by: Poolitzer <25934244+Poolitzer@users.noreply.github.com> Co-authored-by: GauthamramRavichandran <30320759+GauthamramRavichandran@users.noreply.github.com> Co-authored-by: Mahesh19 <maheshvagicherla99438@gmail.com> Co-authored-by: hoppingturtles <ilovebhagwan@gmail.com>
2020-08-24 19:35:57 +02:00
:obj:`list`: The list of args for the handler.
2017-09-01 08:43:08 +02:00
"""
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
if isinstance(update, Update) and update.effective_message:
message = update.effective_message
2019-02-08 20:55:40 +01:00
if (
message.entities
and message.entities[0].type == MessageEntity.BOT_COMMAND
2020-10-06 19:28:40 +02:00
and message.entities[0].offset == 0
and message.text
and message.get_bot()
2020-10-06 19:28:40 +02:00
):
command = message.text[1 : message.entities[0].length]
args = message.text.split()[1:]
2020-10-06 19:28:40 +02:00
command_parts = command.split("@")
command_parts.append(message.get_bot().username)
2020-10-06 19:28:40 +02:00
if not (
command_parts[0].lower() in self.commands
and command_parts[1].lower() == message.get_bot().username.lower()
2020-10-06 19:28:40 +02:00
):
return None
if not self._check_correct_args(args):
return None
2021-11-20 11:36:18 +01:00
filter_result = self.filters.check_update(update)
Update Filters, CommandHandler and MessageHandler (#1221) * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * update_filter attribute on filters Makes it possible to have filters work on an update instead of message, while keeping behavior for current filters * add update_type filter * Messagehandler rework - remove allow_edited (deprecated for a while) - set deprecated defaults to None - Raise deprecation warning when they're used - add sensible defaults for filters. - rework tests * Commandhandler rework * Remove deprecation test from new handler * Some tweaks per CR - rename update_types -> updates - added some clarification to docstrings * run webhook set test only on 3.6 on appveyor * Changes per CR * Update travis to build v12 * small doc update * try to make ci build version branches * doc for BaseFilter * Modify regexfilter and mergedfilter Now returns a list of match objects for every regexfilter * Change callbackcontext (+ docs) * integrate in CommandHandler and PrefixHandler * integrate in MessageHandler * cbqhandler, iqhandler and srhandler * make regexhandler a shell over MessageHandler And raise deprecationWarning on creation * clean up code and add some comments * Rework based on internal group feedback - use data_filter instead of regex_filter on BaseFilter - have these filters return a dict that is then updated onto CallbackContext instead of using a list is before - Add a .match property on CallbackContext that returns .matches[0] or None * Fix and add test for callbackcontext.match * Lots of documentation fixes and improvements [ci skip]
2019-02-13 12:07:25 +01:00
if filter_result:
return args, filter_result
return False
2020-10-06 19:28:40 +02:00
return None
def collect_additional_context(
self,
context: CCT,
2024-05-12 09:13:20 +02:00
update: Update, # noqa: ARG002
application: "Application[Any, CCT, Any, Any, Any, Any]", # noqa: ARG002
check_result: Optional[Union[bool, tuple[list[str], Optional[bool]]]],
2020-10-06 19:28:40 +02:00
) -> None:
"""Add text after the command to :attr:`CallbackContext.args` as list, split on single
whitespaces and add output of data filters to :attr:`CallbackContext` as well.
"""
2020-10-06 19:28:40 +02:00
if isinstance(check_result, tuple):
context.args = check_result[0]
if isinstance(check_result[1], dict):
context.update(check_result[1])