mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-03-17 04:39:55 +01:00
Merge branch 'master' into V12
This commit is contained in:
commit
1b52e6148e
30 changed files with 396 additions and 82 deletions
|
@ -13,7 +13,7 @@
|
|||
args:
|
||||
- --ignore=W605,W503
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
sha: v1.7.1
|
||||
sha: v2.2.2
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^telegram/.*\.py$
|
||||
|
|
|
@ -16,9 +16,11 @@ Contributors
|
|||
The following wonderful people contributed directly or indirectly to this project:
|
||||
|
||||
- `Alateas <https://github.com/alateas>`_
|
||||
- `Ambro17 <https://github.com/Ambro17>`_
|
||||
- `Anton Tagunov <https://github.com/anton-tagunov>`_
|
||||
- `Avanatiker <https://github.com/Avanatiker>`_
|
||||
- `Balduro <https://github.com/Balduro>`_
|
||||
- `Bibo-Joshi <https://github.com/Bibo-Joshi>`_
|
||||
- `bimmlerd <https://github.com/bimmlerd>`_
|
||||
- `d-qoi <https://github.com/d-qoi>`_
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
|
|
2
setup.py
2
setup.py
|
@ -55,6 +55,6 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
|||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6'
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7'
|
||||
],)
|
||||
|
|
|
@ -1755,7 +1755,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
|
||||
Identifier of the sent message.
|
||||
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
|
||||
|
@ -1821,7 +1821,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`, optional): Unique identifier for the target chat or
|
||||
username of the target`channel (in the format @channelusername).
|
||||
username of the target channel (in the format @channelusername).
|
||||
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
|
||||
Identifier of the sent message.
|
||||
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
|
||||
|
@ -1870,7 +1870,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
message_id (:obj:`int`, optional): Required if inline_message_id is not specified.
|
||||
Identifier of the sent message.
|
||||
inline_message_id (:obj:`str`, optional): Required if chat_id and message_id are not
|
||||
|
@ -2104,7 +2104,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2134,7 +2134,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2166,7 +2166,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2194,7 +2194,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2222,7 +2222,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
|
@ -2816,7 +2816,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2847,7 +2847,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
photo (`filelike object`): New chat photo.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
|
@ -2886,7 +2886,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
@ -2921,7 +2921,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
title (:obj:`str`): New chat title, 1-255 characters.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
|
@ -2956,7 +2956,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
description (:obj:`str`): New chat description, 1-255 characters.
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
|
@ -2988,7 +2988,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
message_id (:obj:`int`): Identifier of a message to pin.
|
||||
disable_notification (:obj:`bool`, optional): Pass True, if it is not necessary to send
|
||||
a notification to all group members about the new pinned message.
|
||||
|
@ -3024,7 +3024,7 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target`channel (in the format @channelusername).
|
||||
of the target channel (in the format @channelusername).
|
||||
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
|
||||
the read timeout from the server (instead of the one specified during creation of
|
||||
the connection pool).
|
||||
|
|
|
@ -255,7 +255,7 @@ class ConversationHandler(Handler):
|
|||
state = self.conversations.get(key)
|
||||
|
||||
# Resolve promises
|
||||
if isinstance(state, tuple) and len(state) is 2 and isinstance(state[1], Promise):
|
||||
if isinstance(state, tuple) and len(state) == 2 and isinstance(state[1], Promise):
|
||||
self.logger.debug('waiting for promise...')
|
||||
|
||||
old_state, new_state = state
|
||||
|
|
|
@ -31,7 +31,7 @@ from queue import Queue, Empty
|
|||
|
||||
from future.builtins import range
|
||||
|
||||
from telegram import TelegramError
|
||||
from telegram import TelegramError, Update
|
||||
from telegram.ext.handler import Handler
|
||||
from telegram.ext.callbackcontext import CallbackContext
|
||||
from telegram.utils.deprecate import TelegramDeprecationWarning
|
||||
|
@ -321,7 +321,7 @@ class Dispatcher(object):
|
|||
check = handler.check_update(update)
|
||||
if check is not None and check is not False:
|
||||
handler.handle_update(update, self, check)
|
||||
if self.persistence:
|
||||
if self.persistence and isinstance(update, Update):
|
||||
if self.persistence.store_chat_data and update.effective_chat:
|
||||
chat_id = update.effective_chat.id
|
||||
try:
|
||||
|
|
|
@ -175,29 +175,40 @@ class Filters(object):
|
|||
|
||||
class regex(BaseFilter):
|
||||
"""
|
||||
Filters updates by searching for an occurence of ``pattern`` in the message text.
|
||||
Filters updates by searching for an occurrence of ``pattern`` in the message text.
|
||||
The ``re.search`` function is used to determine whether an update should be filtered.
|
||||
|
||||
Refer to the documentation of the ``re`` module for more information.
|
||||
|
||||
Note: Does not allow passing groups or a groupdict like the ``RegexHandler`` yet,
|
||||
but this will probably be implemented in a future update, gradually phasing out the
|
||||
RegexHandler (see https://github.com/python-telegram-bot/python-telegram-bot/issues/835).
|
||||
Note:
|
||||
Does not allow passing groups or a groupdict like the ``RegexHandler`` yet,
|
||||
but this will probably be implemented in a future update, gradually phasing out the
|
||||
RegexHandler (See `Github Issue
|
||||
<https://github.com/python-telegram-bot/python-telegram-bot/issues/835/>`_).
|
||||
|
||||
Examples:
|
||||
Example ``CommandHandler("start", deep_linked_callback, Filters.regex('parameter'))``
|
||||
Use ``MessageHandler(Filters.regex(r'help'), callback)`` to capture all messages that
|
||||
contain the word help. You can also use
|
||||
``MessageHandler(Filters.regex(re.compile(r'help', re.IGNORECASE), callback)`` if
|
||||
you want your pattern to be case insensitive. This approach is recommended
|
||||
if you need to specify flags on your pattern.
|
||||
|
||||
|
||||
Args:
|
||||
pattern (:obj:`str` | :obj:`Pattern`): The regex pattern.
|
||||
"""
|
||||
|
||||
def __init__(self, pattern):
|
||||
self.pattern = re.compile(pattern)
|
||||
if isinstance(pattern, string_types):
|
||||
pattern = re.compile(pattern)
|
||||
self.pattern = pattern
|
||||
self.name = 'Filters.regex({})'.format(self.pattern)
|
||||
|
||||
# TODO: Once the callback revamp (#1026) is done, the regex filter should be able to pass
|
||||
# the matched groups and groupdict to the context object.
|
||||
|
||||
def filter(self, message):
|
||||
""":obj:`Filter`: Messages that have an occurrence of ``pattern``."""
|
||||
if message.text:
|
||||
return bool(self.pattern.search(message.text))
|
||||
return False
|
||||
|
|
|
@ -77,7 +77,7 @@ class JobQueue(object):
|
|||
raise ValueError('next_t is None')
|
||||
|
||||
if isinstance(next_t, datetime.datetime):
|
||||
next_t = (next_t - datetime.datetime.now()).total_seconds()
|
||||
next_t = (next_t - datetime.datetime.now(next_t.tzinfo)).total_seconds()
|
||||
|
||||
elif isinstance(next_t, datetime.time):
|
||||
next_datetime = datetime.datetime.combine(datetime.date.today(), next_t)
|
||||
|
@ -105,7 +105,7 @@ class JobQueue(object):
|
|||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access it's
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its
|
||||
``job.context`` or change it to a repeating job.
|
||||
when (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta` | \
|
||||
:obj:`datetime.datetime` | :obj:`datetime.time`):
|
||||
|
@ -137,12 +137,12 @@ class JobQueue(object):
|
|||
return job
|
||||
|
||||
def run_repeating(self, callback, interval, first=None, context=None, name=None):
|
||||
"""Creates a new ``Job`` that runs once and adds it to the queue.
|
||||
"""Creates a new ``Job`` that runs at specified intervals and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access it's
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its
|
||||
``Job.context`` or change it to a repeating job.
|
||||
interval (:obj:`int` | :obj:`float` | :obj:`datetime.timedelta`): The interval in which
|
||||
the job will run. If it is an :obj:`int` or a :obj:`float`, it will be interpreted
|
||||
|
@ -183,12 +183,12 @@ class JobQueue(object):
|
|||
return job
|
||||
|
||||
def run_daily(self, callback, time, days=Days.EVERY_DAY, context=None, name=None):
|
||||
"""Creates a new ``Job`` that runs once and adds it to the queue.
|
||||
"""Creates a new ``Job`` that runs on a daily basis and adds it to the queue.
|
||||
|
||||
Args:
|
||||
callback (:obj:`callable`): The callback function that should be executed by the new
|
||||
job. It should take ``bot, job`` as parameters, where ``job`` is the
|
||||
:class:`telegram.ext.Job` instance. It can be used to access it's ``Job.context``
|
||||
:class:`telegram.ext.Job` instance. It can be used to access its ``Job.context``
|
||||
or change it to a repeating job.
|
||||
time (:obj:`datetime.time`): Time of day at which the job should run.
|
||||
days (Tuple[:obj:`int`], optional): Defines on which days of the week the job should
|
||||
|
|
|
@ -48,3 +48,51 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
|||
data['inline_keyboard'].append([x.to_dict() for x in inline_keyboard])
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_button(cls, button, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
InlineKeyboardMarkup([[button]], **kwargs)
|
||||
|
||||
Return an InlineKeyboardMarkup from a single InlineKeyboardButton
|
||||
|
||||
Args:
|
||||
button (:class:`telegram.InlineKeyboardButton`): The button to use in the markup
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
return cls([[button]], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, button_row, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
InlineKeyboardMarkup([button_row], **kwargs)
|
||||
|
||||
Return an InlineKeyboardMarkup from a single row of InlineKeyboardButtons
|
||||
|
||||
Args:
|
||||
button_row (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the
|
||||
markup
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
return cls([button_row], **kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_column(cls, button_column, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
InlineKeyboardMarkup([[button] for button in button_column], **kwargs)
|
||||
|
||||
Return an InlineKeyboardMarkup from a single column of InlineKeyboardButtons
|
||||
|
||||
Args:
|
||||
button_column (List[:class:`telegram.InlineKeyboardButton`]): The button to use in the
|
||||
markup
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
button_grid = [[button] for button in button_column]
|
||||
return cls(button_grid, **kwargs)
|
||||
|
|
|
@ -209,10 +209,11 @@ class Message(TelegramObject):
|
|||
ATTACHMENT_TYPES = ['audio', 'game', 'animation', 'document', 'photo', 'sticker', 'video',
|
||||
'voice', 'video_note', 'contact', 'location', 'venue', 'invoice',
|
||||
'successful_payment']
|
||||
MESSAGE_TYPES = ['text', 'new_chat_members', 'new_chat_title', 'new_chat_photo',
|
||||
'delete_chat_photo', 'group_chat_created', 'supergroup_chat_created',
|
||||
'channel_chat_created', 'migrate_to_chat_id', 'migrate_from_chat_id',
|
||||
'pinned_message', 'passport_data'] + ATTACHMENT_TYPES
|
||||
MESSAGE_TYPES = ['text', 'new_chat_members', 'left_chat_member', 'new_chat_title',
|
||||
'new_chat_photo', 'delete_chat_photo', 'group_chat_created',
|
||||
'supergroup_chat_created', 'channel_chat_created', 'migrate_to_chat_id',
|
||||
'migrate_from_chat_id', 'pinned_message',
|
||||
'passport_data'] + ATTACHMENT_TYPES
|
||||
|
||||
def __init__(self,
|
||||
message_id,
|
||||
|
|
|
@ -85,3 +85,131 @@ class ReplyKeyboardMarkup(ReplyMarkup):
|
|||
r.append(button) # str
|
||||
data['keyboard'].append(r)
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_button(cls,
|
||||
button,
|
||||
resize_keyboard=False,
|
||||
one_time_keyboard=False,
|
||||
selective=False,
|
||||
**kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
ReplyKeyboardMarkup([[button]], **kwargs)
|
||||
|
||||
Return an ReplyKeyboardMarkup from a single KeyboardButton
|
||||
|
||||
Args:
|
||||
button (:class:`telegram.KeyboardButton` | :obj:`str`): The button to use in the markup
|
||||
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
|
||||
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
|
||||
rows of buttons). Defaults to false, in which case the custom keyboard is always of
|
||||
the same height as the app's standard keyboard.
|
||||
Defaults to ``False``
|
||||
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
|
||||
soon as it's been used. The keyboard will still be available, but clients will
|
||||
automatically display the usual letter-keyboard in the chat - the user can press
|
||||
a special button in the input field to see the custom keyboard again.
|
||||
Defaults to ``False``.
|
||||
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
|
||||
to specific users only. Targets:
|
||||
|
||||
1) users that are @mentioned in the text of the Message object
|
||||
2) if the bot's message is a reply (has reply_to_message_id), sender of the
|
||||
original message.
|
||||
|
||||
Defaults to ``False``.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
"""
|
||||
return cls([[button]],
|
||||
resize_keyboard=resize_keyboard,
|
||||
one_time_keyboard=one_time_keyboard,
|
||||
selective=selective,
|
||||
**kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls,
|
||||
button_row,
|
||||
resize_keyboard=False,
|
||||
one_time_keyboard=False,
|
||||
selective=False,
|
||||
**kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
ReplyKeyboardMarkup([button_row], **kwargs)
|
||||
|
||||
Return an ReplyKeyboardMarkup from a single row of KeyboardButtons
|
||||
|
||||
Args:
|
||||
button_row (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use in
|
||||
the markup
|
||||
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
|
||||
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
|
||||
rows of buttons). Defaults to false, in which case the custom keyboard is always of
|
||||
the same height as the app's standard keyboard.
|
||||
Defaults to ``False``
|
||||
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
|
||||
soon as it's been used. The keyboard will still be available, but clients will
|
||||
automatically display the usual letter-keyboard in the chat - the user can press
|
||||
a special button in the input field to see the custom keyboard again.
|
||||
Defaults to ``False``.
|
||||
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
|
||||
to specific users only. Targets:
|
||||
|
||||
1) users that are @mentioned in the text of the Message object
|
||||
2) if the bot's message is a reply (has reply_to_message_id), sender of the
|
||||
original message.
|
||||
|
||||
Defaults to ``False``.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
return cls([button_row],
|
||||
resize_keyboard=resize_keyboard,
|
||||
one_time_keyboard=one_time_keyboard,
|
||||
selective=selective,
|
||||
**kwargs)
|
||||
|
||||
@classmethod
|
||||
def from_column(cls,
|
||||
button_column,
|
||||
resize_keyboard=False,
|
||||
one_time_keyboard=False,
|
||||
selective=False,
|
||||
**kwargs):
|
||||
"""Shortcut for::
|
||||
|
||||
ReplyKeyboardMarkup([[button] for button in button_column], **kwargs)
|
||||
|
||||
Return an ReplyKeyboardMarkup from a single column of KeyboardButtons
|
||||
|
||||
Args:
|
||||
button_column (List[:class:`telegram.KeyboardButton` | :obj:`str`]): The button to use
|
||||
in the markup
|
||||
resize_keyboard (:obj:`bool`, optional): Requests clients to resize the keyboard
|
||||
vertically for optimal fit (e.g., make the keyboard smaller if there are just two
|
||||
rows of buttons). Defaults to false, in which case the custom keyboard is always of
|
||||
the same height as the app's standard keyboard.
|
||||
Defaults to ``False``
|
||||
one_time_keyboard (:obj:`bool`, optional): Requests clients to hide the keyboard as
|
||||
soon as it's been used. The keyboard will still be available, but clients will
|
||||
automatically display the usual letter-keyboard in the chat - the user can press
|
||||
a special button in the input field to see the custom keyboard again.
|
||||
Defaults to ``False``.
|
||||
selective (:obj:`bool`, optional): Use this parameter if you want to show the keyboard
|
||||
to specific users only. Targets:
|
||||
|
||||
1) users that are @mentioned in the text of the Message object
|
||||
2) if the bot's message is a reply (has reply_to_message_id), sender of the
|
||||
original message.
|
||||
|
||||
Defaults to ``False``.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
button_grid = [[button] for button in button_column]
|
||||
return cls(button_grid,
|
||||
resize_keyboard=resize_keyboard,
|
||||
one_time_keyboard=one_time_keyboard,
|
||||
selective=selective,
|
||||
**kwargs)
|
||||
|
|
|
@ -323,7 +323,8 @@ class Request(object):
|
|||
else:
|
||||
result = self._request_wrapper('POST', url,
|
||||
body=json.dumps(data).encode('utf-8'),
|
||||
headers={'Content-Type': 'application/json'})
|
||||
headers={'Content-Type': 'application/json'},
|
||||
**urlopen_kwargs)
|
||||
|
||||
return self._parse(result)
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ def dp(_dp):
|
|||
_dp.update_queue.get(False)
|
||||
_dp.chat_data = defaultdict(dict)
|
||||
_dp.user_data = defaultdict(dict)
|
||||
_dp.persistence = None
|
||||
_dp.handlers = {}
|
||||
_dp.groups = []
|
||||
_dp.error_handlers = []
|
||||
|
|
|
@ -50,7 +50,7 @@ class TestAnimation(object):
|
|||
def test_creation(self, animation):
|
||||
assert isinstance(animation, Animation)
|
||||
assert isinstance(animation.file_id, str)
|
||||
assert animation.file_id is not ''
|
||||
assert animation.file_id != ''
|
||||
|
||||
def test_expected_values(self, animation):
|
||||
assert animation.file_size == self.file_size
|
||||
|
@ -72,8 +72,8 @@ class TestAnimation(object):
|
|||
assert message.animation.file_name == animation.file_name
|
||||
assert message.animation.mime_type == animation.mime_type
|
||||
assert message.animation.file_size == animation.file_size
|
||||
assert message.animation.thumb.width == 50
|
||||
assert message.animation.thumb.height == 50
|
||||
assert message.animation.thumb.width == 320
|
||||
assert message.animation.thumb.height == 180
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_resend(self, bot, chat_id, animation):
|
||||
|
|
|
@ -48,7 +48,7 @@ class TestAudio(object):
|
|||
audio_file_url = 'https://goo.gl/3En24v'
|
||||
mime_type = 'audio/mpeg'
|
||||
file_size = 122920
|
||||
thumb_file_size = 2744
|
||||
thumb_file_size = 1427
|
||||
thumb_width = 50
|
||||
thumb_height = 50
|
||||
|
||||
|
@ -56,7 +56,7 @@ class TestAudio(object):
|
|||
# Make sure file has been uploaded.
|
||||
assert isinstance(audio, Audio)
|
||||
assert isinstance(audio.file_id, str)
|
||||
assert audio.file_id is not ''
|
||||
assert audio.file_id != ''
|
||||
|
||||
def test_expected_values(self, audio):
|
||||
assert audio.duration == self.duration
|
||||
|
|
|
@ -618,16 +618,27 @@ class TestBot(object):
|
|||
# test_sticker module.
|
||||
|
||||
def test_timeout_propagation(self, monkeypatch, bot, chat_id):
|
||||
|
||||
from telegram.vendor.ptb_urllib3.urllib3.util.timeout import Timeout
|
||||
|
||||
class OkException(Exception):
|
||||
pass
|
||||
|
||||
timeout = 500
|
||||
TIMEOUT = 500
|
||||
|
||||
def post(*args, **kwargs):
|
||||
if kwargs.get('timeout') == 500:
|
||||
def request_wrapper(*args, **kwargs):
|
||||
obj = kwargs.get('timeout')
|
||||
if isinstance(obj, Timeout) and obj._read == TIMEOUT:
|
||||
raise OkException
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', post)
|
||||
return b'{"ok": true, "result": []}'
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request._request_wrapper', request_wrapper)
|
||||
|
||||
# Test file uploading
|
||||
with pytest.raises(OkException):
|
||||
bot.send_photo(chat_id, open('tests/data/telegram.jpg', 'rb'), timeout=timeout)
|
||||
bot.send_photo(chat_id, open('tests/data/telegram.jpg', 'rb'), timeout=TIMEOUT)
|
||||
|
||||
# Test JSON submition
|
||||
with pytest.raises(OkException):
|
||||
bot.get_chat_administrators(chat_id, timeout=TIMEOUT)
|
||||
|
|
|
@ -42,7 +42,10 @@ class TestDispatcher(object):
|
|||
received = None
|
||||
count = 0
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@pytest.fixture(autouse=True, name='reset')
|
||||
def reset_fixture(self):
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
self.received = None
|
||||
self.count = 0
|
||||
|
|
|
@ -43,14 +43,14 @@ class TestDocument(object):
|
|||
file_size = 12948
|
||||
mime_type = 'image/png'
|
||||
file_name = 'telegram.png'
|
||||
thumb_file_size = 2364
|
||||
thumb_width = 90
|
||||
thumb_height = 90
|
||||
thumb_file_size = 8090
|
||||
thumb_width = 300
|
||||
thumb_height = 300
|
||||
|
||||
def test_creation(self, document):
|
||||
assert isinstance(document, Document)
|
||||
assert isinstance(document.file_id, str)
|
||||
assert document.file_id is not ''
|
||||
assert document.file_id != ''
|
||||
|
||||
def test_expected_values(self, document):
|
||||
assert document.file_size == self.file_size
|
||||
|
@ -75,8 +75,8 @@ class TestDocument(object):
|
|||
assert message.document.mime_type == document.mime_type
|
||||
assert message.document.file_size == document.file_size
|
||||
assert message.caption == self.caption.replace('*', '')
|
||||
assert message.document.thumb.width == 50
|
||||
assert message.document.thumb.height == 50
|
||||
assert message.document.thumb.width == self.thumb_width
|
||||
assert message.document.thumb.height == self.thumb_height
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
|
|
@ -64,6 +64,7 @@ class TestFilters(object):
|
|||
assert not Filters.regex(r'fail')(message)
|
||||
assert Filters.regex(r'test')(message)
|
||||
assert Filters.regex(re.compile(r'test'))(message)
|
||||
assert Filters.regex(re.compile(r'TEST', re.IGNORECASE))(message)
|
||||
|
||||
message.text = 'i love python'
|
||||
assert Filters.regex(r'.\b[lo]{2}ve python')(message)
|
||||
|
|
|
@ -32,24 +32,33 @@ class TestHelpers(object):
|
|||
assert expected_str == helpers.escape_markdown(test_str)
|
||||
|
||||
def test_effective_message_type(self):
|
||||
test_message = Message(message_id=1,
|
||||
from_user=None,
|
||||
date=None,
|
||||
chat=None)
|
||||
|
||||
test_message.text = 'Test'
|
||||
def build_test_message(**kwargs):
|
||||
config = dict(
|
||||
message_id=1,
|
||||
from_user=None,
|
||||
date=None,
|
||||
chat=None,
|
||||
)
|
||||
config.update(**kwargs)
|
||||
return Message(**config)
|
||||
|
||||
test_message = build_test_message(text='Test')
|
||||
assert helpers.effective_message_type(test_message) == 'text'
|
||||
test_message.text = None
|
||||
|
||||
test_message.sticker = Sticker('sticker_id', 50, 50)
|
||||
test_message = build_test_message(sticker=Sticker('sticker_id', 50, 50))
|
||||
assert helpers.effective_message_type(test_message) == 'sticker'
|
||||
test_message.sticker = None
|
||||
|
||||
test_message.new_chat_members = [User(55, 'new_user', False)]
|
||||
test_message = build_test_message(new_chat_members=[User(55, 'new_user', False)])
|
||||
assert helpers.effective_message_type(test_message) == 'new_chat_members'
|
||||
|
||||
test_message = build_test_message(left_chat_member=[User(55, 'new_user', False)])
|
||||
assert helpers.effective_message_type(test_message) == 'left_chat_member'
|
||||
|
||||
test_update = Update(1)
|
||||
test_message.text = 'Test'
|
||||
test_message = build_test_message(text='Test')
|
||||
test_update.message = test_message
|
||||
assert helpers.effective_message_type(test_update) == 'text'
|
||||
|
||||
|
|
|
@ -44,6 +44,27 @@ class TestInlineKeyboardMarkup(object):
|
|||
|
||||
assert message.text == 'Testing InlineKeyboardMarkup'
|
||||
|
||||
def test_from_button(self):
|
||||
inline_keyboard_markup = InlineKeyboardMarkup.from_button(
|
||||
InlineKeyboardButton(text='button1', callback_data='data1')).inline_keyboard
|
||||
assert len(inline_keyboard_markup) == 1
|
||||
assert len(inline_keyboard_markup[0]) == 1
|
||||
|
||||
def test_from_row(self):
|
||||
inline_keyboard_markup = InlineKeyboardMarkup.from_row([
|
||||
InlineKeyboardButton(text='button1', callback_data='data1'),
|
||||
InlineKeyboardButton(text='button1', callback_data='data1')]).inline_keyboard
|
||||
assert len(inline_keyboard_markup) == 1
|
||||
assert len(inline_keyboard_markup[0]) == 2
|
||||
|
||||
def test_from_column(self):
|
||||
inline_keyboard_markup = InlineKeyboardMarkup.from_column([
|
||||
InlineKeyboardButton(text='button1', callback_data='data1'),
|
||||
InlineKeyboardButton(text='button1', callback_data='data1')]).inline_keyboard
|
||||
assert len(inline_keyboard_markup) == 2
|
||||
assert len(inline_keyboard_markup[0]) == 1
|
||||
assert len(inline_keyboard_markup[1]) == 1
|
||||
|
||||
def test_expected_values(self, inline_keyboard_markup):
|
||||
assert inline_keyboard_markup.inline_keyboard == self.inline_keyboard
|
||||
|
||||
|
|
|
@ -290,7 +290,7 @@ class TestInputMediaDocument(object):
|
|||
|
||||
|
||||
@pytest.fixture(scope='function') # noqa: F811
|
||||
def media_group(photo, thumb):
|
||||
def media_group(photo, thumb): # noqa: F811
|
||||
return [InputMediaPhoto(photo, caption='photo `1`', parse_mode='Markdown'),
|
||||
InputMediaPhoto(thumb, caption='<b>photo</b> 2', parse_mode='HTML')]
|
||||
|
||||
|
|
|
@ -198,6 +198,24 @@ class TestJobQueue(object):
|
|||
sleep(0.06)
|
||||
assert pytest.approx(self.job_time) == expected_time
|
||||
|
||||
def test_datetime_with_timezone_job_run_once(self, job_queue):
|
||||
# Test that run_once jobs work with timezone aware datetimes.
|
||||
offset = datetime.timedelta(hours=-3)
|
||||
when = datetime.datetime.now(datetime.timezone(offset))
|
||||
|
||||
job_queue.run_once(self.job_run_once, when)
|
||||
sleep(0.01)
|
||||
assert self.result == 1
|
||||
|
||||
def test_datetime_with_timezone_job_run_repeating(self, job_queue):
|
||||
# Test that run_repeating jobs work with timezone aware datetimes.
|
||||
offset = datetime.timedelta(hours=5)
|
||||
now_with_offset = datetime.datetime.now(datetime.timezone(offset))
|
||||
|
||||
job_queue.run_repeating(self.job_run_once, interval=0.01, first=now_with_offset)
|
||||
sleep(0.015)
|
||||
assert self.result == 2
|
||||
|
||||
def test_time_unit_dt_time_today(self, job_queue):
|
||||
# Testing running at a specific time today
|
||||
delta = 0.05
|
||||
|
|
|
@ -54,7 +54,7 @@ class TestDelayQueue(object):
|
|||
dsp.stop()
|
||||
assert dsp.is_alive() is False
|
||||
|
||||
assert self.testtimes or self.N == 0 is True
|
||||
assert self.testtimes or self.N == 0
|
||||
passes, fails = [], []
|
||||
delta = (self.time_limit_ms - self.margin_ms) / 1000
|
||||
for start, stop in enumerate(range(self.burst_limit + 1, len(self.testtimes))):
|
||||
|
|
|
@ -31,7 +31,7 @@ import pytest
|
|||
|
||||
from telegram import Update, Message, User, Chat, MessageEntity
|
||||
from telegram.ext import BasePersistence, Updater, ConversationHandler, MessageHandler, Filters, \
|
||||
PicklePersistence, CommandHandler, DictPersistence
|
||||
PicklePersistence, CommandHandler, DictPersistence, TypeHandler
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
|
@ -201,6 +201,21 @@ class TestBasePersistence(object):
|
|||
assert dp.user_data[54321][1] == 'test7'
|
||||
assert dp.chat_data[-987654][2] == 'test8'
|
||||
|
||||
def test_persistence_dispatcher_arbitrary_update_types(self, dp, base_persistence, caplog):
|
||||
# Updates used with TypeHandler doesn't necessarily have the proper attributes for
|
||||
# persistence, makes sure it works anyways
|
||||
|
||||
dp.persistence = base_persistence
|
||||
|
||||
class MyUpdate(object):
|
||||
pass
|
||||
|
||||
dp.add_handler(TypeHandler(MyUpdate, lambda *_: None))
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
dp.process_update(MyUpdate())
|
||||
assert 'An uncaught error was raised while processing the update' not in caplog.text
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def pickle_persistence():
|
||||
|
|
|
@ -59,11 +59,11 @@ class TestPhoto(object):
|
|||
# Make sure file has been uploaded.
|
||||
assert isinstance(photo, PhotoSize)
|
||||
assert isinstance(photo.file_id, str)
|
||||
assert photo.file_id is not ''
|
||||
assert photo.file_id != ''
|
||||
|
||||
assert isinstance(thumb, PhotoSize)
|
||||
assert isinstance(thumb.file_id, str)
|
||||
assert thumb.file_id is not ''
|
||||
assert thumb.file_id != ''
|
||||
|
||||
def test_expected_values(self, photo, thumb):
|
||||
assert photo.width == self.width
|
||||
|
|
|
@ -51,6 +51,40 @@ class TestReplyKeyboardMarkup(object):
|
|||
|
||||
assert message.text == 'text 2'
|
||||
|
||||
def test_from_button(self):
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_button(
|
||||
KeyboardButton(text='button1')).keyboard
|
||||
assert len(reply_keyboard_markup) == 1
|
||||
assert len(reply_keyboard_markup[0]) == 1
|
||||
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_button('button1').keyboard
|
||||
assert len(reply_keyboard_markup) == 1
|
||||
assert len(reply_keyboard_markup[0]) == 1
|
||||
|
||||
def test_from_row(self):
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_row([
|
||||
KeyboardButton(text='button1'),
|
||||
KeyboardButton(text='button2')]).keyboard
|
||||
assert len(reply_keyboard_markup) == 1
|
||||
assert len(reply_keyboard_markup[0]) == 2
|
||||
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_row(['button1', 'button2']).keyboard
|
||||
assert len(reply_keyboard_markup) == 1
|
||||
assert len(reply_keyboard_markup[0]) == 2
|
||||
|
||||
def test_from_column(self):
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_column([
|
||||
KeyboardButton(text='button1'),
|
||||
KeyboardButton(text='button2')]).keyboard
|
||||
assert len(reply_keyboard_markup) == 2
|
||||
assert len(reply_keyboard_markup[0]) == 1
|
||||
assert len(reply_keyboard_markup[1]) == 1
|
||||
|
||||
reply_keyboard_markup = ReplyKeyboardMarkup.from_column(['button1', 'button2']).keyboard
|
||||
assert len(reply_keyboard_markup) == 2
|
||||
assert len(reply_keyboard_markup[0]) == 1
|
||||
assert len(reply_keyboard_markup[1]) == 1
|
||||
|
||||
def test_expected_values(self, reply_keyboard_markup):
|
||||
assert isinstance(reply_keyboard_markup.keyboard, list)
|
||||
assert isinstance(reply_keyboard_markup.keyboard[0][0], KeyboardButton)
|
||||
|
|
|
@ -50,9 +50,9 @@ class TestSticker(object):
|
|||
width = 510
|
||||
height = 512
|
||||
file_size = 39518
|
||||
thumb_width = 90
|
||||
thumb_heigth = 90
|
||||
thumb_file_size = 3672
|
||||
thumb_width = 319
|
||||
thumb_height = 320
|
||||
thumb_file_size = 21472
|
||||
|
||||
def test_creation(self, sticker):
|
||||
# Make sure file has been uploaded.
|
||||
|
@ -68,7 +68,7 @@ class TestSticker(object):
|
|||
assert sticker.height == self.height
|
||||
assert sticker.file_size == self.file_size
|
||||
assert sticker.thumb.width == self.thumb_width
|
||||
assert sticker.thumb.height == self.thumb_heigth
|
||||
assert sticker.thumb.height == self.thumb_height
|
||||
assert sticker.thumb.file_size == self.thumb_file_size
|
||||
|
||||
@flaky(3, 1)
|
||||
|
|
|
@ -45,6 +45,10 @@ class TestVideo(object):
|
|||
mime_type = 'video/mp4'
|
||||
supports_streaming = True
|
||||
|
||||
thumb_width = 180
|
||||
thumb_height = 320
|
||||
thumb_file_size = 1767
|
||||
|
||||
caption = u'<b>VideoTest</b> - *Caption*'
|
||||
video_file_url = 'https://python-telegram-bot.org/static/testfiles/telegram.mp4'
|
||||
|
||||
|
@ -52,11 +56,11 @@ class TestVideo(object):
|
|||
# Make sure file has been uploaded.
|
||||
assert isinstance(video, Video)
|
||||
assert isinstance(video.file_id, str)
|
||||
assert video.file_id is not ''
|
||||
assert video.file_id != ''
|
||||
|
||||
assert isinstance(video.thumb, PhotoSize)
|
||||
assert isinstance(video.thumb.file_id, str)
|
||||
assert video.thumb.file_id is not ''
|
||||
assert video.thumb.file_id != ''
|
||||
|
||||
def test_expected_values(self, video):
|
||||
assert video.width == self.width
|
||||
|
@ -83,8 +87,9 @@ class TestVideo(object):
|
|||
|
||||
assert message.caption == self.caption.replace('*', '')
|
||||
|
||||
assert message.video.thumb.width == 50
|
||||
assert message.video.thumb.height == 50
|
||||
assert message.video.thumb.file_size == self.thumb_file_size
|
||||
assert message.video.thumb.width == self.thumb_width
|
||||
assert message.video.thumb.height == self.thumb_height
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
@ -115,9 +120,9 @@ class TestVideo(object):
|
|||
assert isinstance(message.video.thumb, PhotoSize)
|
||||
assert isinstance(message.video.thumb.file_id, str)
|
||||
assert message.video.thumb.file_id != ''
|
||||
assert message.video.thumb.width == video.thumb.width
|
||||
assert message.video.thumb.height == video.thumb.height
|
||||
assert message.video.thumb.file_size == video.thumb.file_size
|
||||
assert message.video.thumb.width == 51 # This seems odd that it's not self.thumb_width
|
||||
assert message.video.thumb.height == 90 # Ditto
|
||||
assert message.video.thumb.file_size == 645 # same
|
||||
|
||||
assert message.caption == self.caption
|
||||
|
||||
|
|
|
@ -42,17 +42,21 @@ class TestVideoNote(object):
|
|||
duration = 3
|
||||
file_size = 132084
|
||||
|
||||
thumb_width = 240
|
||||
thumb_height = 240
|
||||
thumb_file_size = 11547
|
||||
|
||||
caption = u'VideoNoteTest - Caption'
|
||||
|
||||
def test_creation(self, video_note):
|
||||
# Make sure file has been uploaded.
|
||||
assert isinstance(video_note, VideoNote)
|
||||
assert isinstance(video_note.file_id, str)
|
||||
assert video_note.file_id is not ''
|
||||
assert video_note.file_id != ''
|
||||
|
||||
assert isinstance(video_note.thumb, PhotoSize)
|
||||
assert isinstance(video_note.thumb.file_id, str)
|
||||
assert video_note.thumb.file_id is not ''
|
||||
assert video_note.thumb.file_id != ''
|
||||
|
||||
def test_expected_values(self, video_note):
|
||||
assert video_note.length == self.length
|
||||
|
@ -73,8 +77,9 @@ class TestVideoNote(object):
|
|||
assert message.video_note.duration == video_note.duration
|
||||
assert message.video_note.file_size == video_note.file_size
|
||||
|
||||
assert message.video_note.thumb.width == 50
|
||||
assert message.video_note.thumb.height == 50
|
||||
assert message.video_note.thumb.file_size == self.thumb_file_size
|
||||
assert message.video_note.thumb.width == self.thumb_width
|
||||
assert message.video_note.thumb.height == self.thumb_height
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
|
Loading…
Add table
Reference in a new issue