Skip to content

Added prefix argument to CommandHandler #1021

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions telegram/ext/commandhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@

from future.utils import string_types

from .handler import Handler
from telegram import Update
from .handler import Handler


class CommandHandler(Handler):
Expand All @@ -39,6 +39,8 @@ class CommandHandler(Handler):
Filters.
allow_edited (:obj:`bool`): Optional. Determines Whether the handler should also accept
edited messages.
prefix (:obj:`bool`): Optional. Denotes the leading character to commands with this
handler.
pass_args (:obj:`bool`): Optional. Determines whether the handler should be passed
``args``.
pass_update_queue (:obj:`bool`): Optional. Determines whether ``update_queue`` will be
Expand Down Expand Up @@ -68,6 +70,8 @@ class CommandHandler(Handler):
operators (& for and, | for or, ~ for not).
allow_edited (:obj:`bool`, optional): Determines whether the handler should also accept
edited messages. Default is ``False``.
prefix (:obj:`str`, optional): Denotes the leading character to commands with this handler.
Must be exactly one character or an empty string. Default is ``/``.
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
Expand All @@ -92,6 +96,7 @@ def __init__(self,
callback,
filters=None,
allow_edited=False,
prefix='/',
pass_args=False,
pass_update_queue=False,
pass_job_queue=False,
Expand All @@ -110,6 +115,15 @@ def __init__(self,
self.command = [x.lower() for x in command]
self.filters = filters
self.allow_edited = allow_edited
if prefix is None or not isinstance(prefix, string_types) or len(prefix) > 1:
raise ValueError("The prefix argument to CommandHandler must be a single character or "
"an empty string.")
if prefix == ' ':
# A leading space character is stripped away in Telegram messages, so we can consider
# it as an empty string prefix.
self.prefix = ''
else:
self.prefix = prefix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I don't think prefix should be limited to a single char. Same as you accept an empty string you can accept any string longer than 1 char.

  • Instead of calculating (over and over) the empty_leader during check_update, you can calculate self.leader_len here (at the c'tor) and simplify everything else later on.

  • Calling len(prefix) here will also make the check for None redundant. It will be the one to raise TypeError if necessary.

self.pass_args = pass_args

# We put this up here instead of with the rest of checking code
Expand All @@ -133,10 +147,16 @@ def check_update(self, update):
and (update.message or update.edited_message and self.allow_edited)):
message = update.message or update.edited_message

if message.text and message.text.startswith('/') and len(message.text) > 1:
command = message.text[1:].split(None, 1)[0].split('@')
command.append(
message.bot.username) # in case the command was send without a username
empty_leader = self.prefix == ''
command_text_nonempty = message.text and len(message.text) > 1
is_valid_command = command_text_nonempty and (empty_leader or
message.text.startswith(self.prefix))
if is_valid_command:
leader_stripped = message.text if empty_leader else message.text[1:]
command = leader_stripped.split(None, 1)[0].split('@')

# Append the bot's username in any case
command.append(message.bot.username)

if self.filters is None:
res = True
Expand Down
196 changes: 116 additions & 80 deletions tests/test_commandhandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ def message(bot):
return Message(1, None, None, None, bot=bot)


@pytest.fixture(scope='function')
def prefixes():
return ['/', '#', '', ' ']


class TestCommandHandler(object):
test_flag = False

Expand All @@ -75,62 +80,72 @@ def callback_queue_1(self, bot, update, job_queue=None, update_queue=None):
def callback_queue_2(self, bot, update, job_queue=None, update_queue=None):
self.test_flag = (job_queue is not None) and (update_queue is not None)

def ch_callback_args(self, bot, update, args):
if update.message.text == '/test':
def ch_callback_args(self, bot, update, args, prefix):
if update.message.text == '{}test'.format(prefix):
self.test_flag = len(args) == 0
elif update.message.text == '/test@{}'.format(bot.username):
elif update.message.text == '{}test@{}'.format(prefix, bot.username):
self.test_flag = len(args) == 0
else:
self.test_flag = args == ['one', 'two']

def test_basic(self, dp, message):
handler = CommandHandler('test', self.callback_basic)
dp.add_handler(handler)
def test_basic(self, dp, message, prefixes):
for p in prefixes:
handler = CommandHandler('test', self.callback_basic, prefix=p)
dp.add_handler(handler)

message.text = '/test'
assert handler.check_update(Update(0, message))
dp.process_update(Update(0, message))
assert self.test_flag
message.text = '{}test'.format(p)
assert handler.check_update(Update(0, message))
dp.process_update(Update(0, message))
assert self.test_flag

message.text = '/nottest'
assert not handler.check_update(Update(0, message))
message.text = '{}nottest'.format(p)
assert not handler.check_update(Update(0, message))

message.text = 'test'
assert not handler.check_update(Update(0, message))
message.text = 'test'
result = handler.check_update(Update(0, message))
if p in ['', ' ']:
assert result
else:
assert not result

message.text = 'not /test at start'
assert not handler.check_update(Update(0, message))
message.text = 'not {}test at start'.format(p)
assert not handler.check_update(Update(0, message))

def test_command_list(self, message):
handler = CommandHandler(['test', 'start'], self.callback_basic)
dp.remove_handler(handler)

message.text = '/test'
assert handler.check_update(Update(0, message))
def test_command_list(self, message, prefixes):
for p in prefixes:
handler = CommandHandler(['test', 'start'], self.callback_basic, prefix=p)

message.text = '/start'
assert handler.check_update(Update(0, message))
message.text = '{}test'.format(p)
assert handler.check_update(Update(0, message))

message.text = '/stop'
assert not handler.check_update(Update(0, message))
message.text = '{}start'.format(p)
assert handler.check_update(Update(0, message))

def test_edited(self, message):
handler = CommandHandler('test', self.callback_basic, allow_edited=False)
message.text = '{}stop'.format(p)
assert not handler.check_update(Update(0, message))

message.text = '/test'
assert handler.check_update(Update(0, message))
assert not handler.check_update(Update(0, edited_message=message))
handler.allow_edited = True
assert handler.check_update(Update(0, message))
assert handler.check_update(Update(0, edited_message=message))
def test_edited(self, message, prefixes):
for p in prefixes:
handler = CommandHandler('test', self.callback_basic, allow_edited=False, prefix=p)

def test_directed_commands(self, message):
handler = CommandHandler('test', self.callback_basic)
message.text = '{}test'.format(p)
assert handler.check_update(Update(0, message))
assert not handler.check_update(Update(0, edited_message=message))
handler.allow_edited = True
assert handler.check_update(Update(0, message))
assert handler.check_update(Update(0, edited_message=message))

message.text = '/test@{}'.format(message.bot.username)
assert handler.check_update(Update(0, message))
def test_directed_commands(self, message, prefixes):
for p in prefixes:
handler = CommandHandler('test', self.callback_basic, prefix=p)

message.text = '/test@otherbot'
assert not handler.check_update(Update(0, message))
message.text = '{}test@{}'.format(p, message.bot.username)
assert handler.check_update(Update(0, message))

message.text = '{}test@otherbot'.format(p)
assert not handler.check_update(Update(0, message))

def test_with_filter(self, message):
handler = CommandHandler('test', self.callback_basic, Filters.group)
Expand All @@ -142,53 +157,74 @@ def test_with_filter(self, message):
message.chat = Chat(23, 'private')
assert not handler.check_update(Update(0, message))

def test_pass_args(self, dp, message):
handler = CommandHandler('test', self.ch_callback_args, pass_args=True)
dp.add_handler(handler)

message.text = '/test'
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '/test@{}'.format(message.bot.username)
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '/test one two'
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '/test@{} one two'.format(message.bot.username)
dp.process_update(Update(0, message=message))
assert self.test_flag

def test_newline(self, dp, message):
handler = CommandHandler('test', self.callback_basic)
dp.add_handler(handler)
def test_pass_args(self, dp, message, prefixes):
for p in prefixes:
handler = CommandHandler(
'test',
lambda bot, update, args: self.ch_callback_args(bot, update, args, p),
pass_args=True,
prefix=p
)
dp.add_handler(handler)

self.test_flag = False
message.text = '{}test'.format(p)
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '{}test@{}'.format(p, message.bot.username)
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '{}test one two'.format(p)
dp.process_update(Update(0, message=message))
assert self.test_flag

self.test_flag = False
message.text = '{}test@{} one two'.format(p, message.bot.username)
dp.process_update(Update(0, message=message))
assert self.test_flag

dp.remove_handler(handler)

def test_newline(self, dp, message, prefixes):
for p in prefixes:
handler = CommandHandler('test', self.callback_basic, prefix=p)
dp.add_handler(handler)

message.text = '{}test\nfoobar'.format(p)
assert handler.check_update(Update(0, message))
dp.process_update(Update(0, message))
assert self.test_flag

dp.remove_handler(handler)

def test_single_char(self, message):
# Regression test for
# https://github.com/python-telegram-bot/python-telegram-bot/issues/871

message.text = '/test\nfoobar'
assert handler.check_update(Update(0, message))
dp.process_update(Update(0, message))
assert self.test_flag
message.text = 'a'

def test_single_char(self, dp, message):
# Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/871
handler = CommandHandler('test', self.callback_basic)
dp.add_handler(handler)
normal_handler = CommandHandler('test', self.callback_basic)
assert not normal_handler.check_update(Update(0, message))

message.text = 'a'
assert not handler.check_update(Update(0, message))
empty_prefix_handler = CommandHandler('test', self.callback_basic, prefix='')
assert not empty_prefix_handler.check_update(Update(0, message))

def test_single_slash(self, dp, message):
# Regression test for https://github.com/python-telegram-bot/python-telegram-bot/issues/871
handler = CommandHandler('test', self.callback_basic)
dp.add_handler(handler)
def test_single_prefix(self, dp, message, prefixes):
for p in prefixes:
# Regression test for
# https://github.com/python-telegram-bot/python-telegram-bot/issues/871
handler = CommandHandler('test', self.callback_basic, prefix=p)
dp.add_handler(handler)

message.text = '/'
assert not handler.check_update(Update(0, message))
# Note: In praxis, it is not possible to send empty messages.
# We will test this case nonetheless
message.text = p
assert not handler.check_update(Update(0, message))
dp.remove_handler(handler)

def test_pass_user_or_chat_data(self, dp, message):
handler = CommandHandler('test', self.callback_data_1, pass_user_data=True)
Expand Down
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy