mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2025-03-15 03:58:35 +01:00
commit
2954ca2bad
30 changed files with 622 additions and 228 deletions
|
@ -4,6 +4,7 @@ python:
|
|||
- "3.3"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "pypy"
|
||||
- "pypy3"
|
||||
branches:
|
||||
|
|
|
@ -12,6 +12,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
|||
- `Anton Tagunov <https://github.com/anton-tagunov>`_
|
||||
- `Balduro <https://github.com/Balduro>`_
|
||||
- `bimmlerd <https://github.com/bimmlerd>`_
|
||||
- `daimajia <https://github.com/daimajia>`_
|
||||
- `Eli Gao <https://github.com/eligao>`_
|
||||
- `ErgoZ Riftbit Vaper <https://github.com/ergoz>`_
|
||||
- `Eugene Lisitsky <https://github.com/lisitsky>`_
|
||||
|
|
|
@ -149,7 +149,7 @@ def main():
|
|||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
|
|
@ -142,7 +142,7 @@ def main():
|
|||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
|
|
@ -64,7 +64,7 @@ def main():
|
|||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Run the bot until the you presses Ctrl-C or the process receives SIGINT,
|
||||
# Run the bot until you press Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
|
|
@ -98,9 +98,9 @@ def main():
|
|||
# Start the Bot
|
||||
updater.start_polling()
|
||||
|
||||
# Block until the you presses Ctrl-C or the process receives SIGINT,
|
||||
# SIGTERM or SIGABRT. This should be used most of the time, since
|
||||
# start_polling() is non-blocking and will stop the bot gracefully.
|
||||
# Block until you press Ctrl-C or the process receives SIGINT, SIGTERM or
|
||||
# SIGABRT. This should be used most of the time, since start_polling() is
|
||||
# non-blocking and will stop the bot gracefully.
|
||||
updater.idle()
|
||||
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -36,6 +36,7 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
|||
install_requires=requirements(),
|
||||
extras_require={
|
||||
'json': 'ujson',
|
||||
'socks': 'PySocks'
|
||||
},
|
||||
include_package_data=True,
|
||||
classifiers=[
|
||||
|
@ -53,4 +54,5 @@ with codecs.open('README.rst', 'r', 'utf-8') as fd:
|
|||
'Programming Language :: Python :: 3.3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6'
|
||||
],)
|
||||
|
|
223
telegram/bot.py
223
telegram/bot.py
|
@ -158,9 +158,14 @@ class Bot(TelegramObject):
|
|||
return decorator
|
||||
|
||||
@log
|
||||
def getMe(self, **kwargs):
|
||||
def getMe(self, timeout=None, **kwargs):
|
||||
"""A simple method for testing your bot's auth token.
|
||||
|
||||
Args:
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.User`: A :class:`telegram.User` instance representing that bot if the
|
||||
credentials are valid, `None` otherwise.
|
||||
|
@ -171,7 +176,7 @@ class Bot(TelegramObject):
|
|||
"""
|
||||
url = '{0}/getMe'.format(self.base_url)
|
||||
|
||||
result = self._request.get(url)
|
||||
result = self._request.get(url, timeout=timeout)
|
||||
|
||||
self.bot = User.de_json(result, self)
|
||||
|
||||
|
@ -211,8 +216,9 @@ class Bot(TelegramObject):
|
|||
interface options. A JSON-serialized object for an inline
|
||||
keyboard, custom reply keyboard, instructions to remove reply
|
||||
keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -251,8 +257,9 @@ class Bot(TelegramObject):
|
|||
message_id: Unique message identifier.
|
||||
disable_notification (Optional[bool]): Sends the message silently. iOS users will not
|
||||
receive a notification, Android users will receive a notification with no sound.
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -301,8 +308,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -362,8 +370,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -406,7 +415,7 @@ class Bot(TelegramObject):
|
|||
chat_id: Unique identifier for the message recipient - Chat id.
|
||||
document: File to send. You can either pass a file_id as String to resend a file that
|
||||
is already on the Telegram servers, or upload a new file using multipart/form-data.
|
||||
filename (Optional[str]): File name that shows in telegram message (it is usefull when
|
||||
filename (Optional[str]): File name that shows in telegram message (it is useful when
|
||||
you send file generated by temp module, for example).
|
||||
caption (Optional[str]): Document caption (may also be used when resending documents by
|
||||
file_id), 0-200 characters.
|
||||
|
@ -417,8 +426,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -463,8 +473,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -510,8 +521,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, instance representing the message posted.
|
||||
|
@ -563,8 +575,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -609,8 +622,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -658,8 +672,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -712,8 +727,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -758,8 +774,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options.
|
||||
A JSON-serialized object for an inline keyboard, custom reply keyboard,
|
||||
instructions to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success, the sent message is returned.
|
||||
|
@ -776,7 +793,7 @@ class Bot(TelegramObject):
|
|||
|
||||
@log
|
||||
@message
|
||||
def sendChatAction(self, chat_id, action, **kwargs):
|
||||
def sendChatAction(self, chat_id, action, timeout=None, **kwargs):
|
||||
"""Use this method when you need to tell the user that something is happening on the bot's
|
||||
side. The status is set for 5 seconds or less (when a message arrives from your bot,
|
||||
Telegram clients clear its typing status).
|
||||
|
@ -792,6 +809,9 @@ class Bot(TelegramObject):
|
|||
- ChatAction.UPLOAD_AUDIO for audio files,
|
||||
- ChatAction.UPLOAD_DOCUMENT for general files,
|
||||
- ChatAction.FIND_LOCATION for location data.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
@ -833,8 +853,9 @@ class Bot(TelegramObject):
|
|||
start message with the parameter switch_pm_parameter.
|
||||
switch_pm_parameter (Optional[str]): Parameter for the start message sent to the bot
|
||||
when user presses the switch button.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -875,8 +896,9 @@ class Bot(TelegramObject):
|
|||
default, all photos are returned.
|
||||
limit (Optional[int]): Limits the number of photos to be retrieved. Values between
|
||||
1-100 are accepted. Defaults to 100.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -907,8 +929,9 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
file_id: File identifier to get info about.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -941,8 +964,9 @@ class Bot(TelegramObject):
|
|||
chat_id: Unique identifier for the target group or username of the target supergroup
|
||||
(in the format @supergroupusername).
|
||||
user_id: Unique identifier of the target user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -970,8 +994,9 @@ class Bot(TelegramObject):
|
|||
chat_id: Unique identifier for the target group or username of the target supergroup
|
||||
(in the format @supergroupusername).
|
||||
user_id: Unique identifier of the target user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1012,8 +1037,9 @@ class Bot(TelegramObject):
|
|||
cache_time (Optional[int]): The maximum amount of time in seconds that the result of
|
||||
the callback query may be cached client-side. Telegram apps will support caching
|
||||
starting in version 3.14. Defaults to 0.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1069,8 +1095,9 @@ class Bot(TelegramObject):
|
|||
reply_markup (Optional[:class:`telegram.ReplyMarkup`]): Additional interface options. A
|
||||
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
|
||||
to remove reply keyboard or to force a reply from the user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1122,8 +1149,9 @@ class Bot(TelegramObject):
|
|||
caption (Optional[str]): New caption of the message.
|
||||
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized
|
||||
object for an inline keyboard.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1176,8 +1204,9 @@ class Bot(TelegramObject):
|
|||
specified. Identifier of the inline message.
|
||||
reply_markup (Optional[:class:`telegram.InlineKeyboardMarkup`]): A JSON-serialized
|
||||
object for an inline keyboard.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1208,7 +1237,13 @@ class Bot(TelegramObject):
|
|||
return url, data
|
||||
|
||||
@log
|
||||
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=5., **kwargs):
|
||||
def getUpdates(self,
|
||||
offset=None,
|
||||
limit=100,
|
||||
timeout=0,
|
||||
network_delay=None,
|
||||
read_latency=2.,
|
||||
**kwargs):
|
||||
"""Use this method to receive incoming updates using long polling.
|
||||
|
||||
Args:
|
||||
|
@ -1220,13 +1255,25 @@ class Bot(TelegramObject):
|
|||
limit (Optional[int]): Limits the number of updates to be retrieved. Values between
|
||||
1-100 are accepted. Defaults to 100.
|
||||
timeout (Optional[int]): Timeout in seconds for long polling. Defaults to 0, i.e. usual
|
||||
short polling.
|
||||
network_delay (Optional[float]): Additional timeout in seconds to allow the response
|
||||
from Telegram servers. This should cover network latency around the globe, SSL
|
||||
handshake and slowness of the Telegram servers (which unfortunately happens a lot
|
||||
recently - 2016-05-28). Defaults to 5.
|
||||
short polling. Be careful not to set this timeout too high, as the connection might
|
||||
be dropped and there's no way of knowing it immediately (so most likely the failure
|
||||
will be detected after the timeout had passed).
|
||||
network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be
|
||||
removed in the future.
|
||||
read_latency (Optional[float|int]): Grace time in seconds for receiving the reply from
|
||||
server. Will be added to the `timeout` value and used as the read timeout from
|
||||
server (Default: 2).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Notes:
|
||||
The main problem with long polling is that a connection will be dropped and we won't
|
||||
be getting the notification in time for it. For that, we need to use long polling, but
|
||||
not too long as well read latency which is short, but not too short.
|
||||
Long polling improves performance, but if it's too long and the connection is dropped
|
||||
on many cases we won't know the connection dropped before the long polling timeout and
|
||||
the read latency time had passed. If you experience connection timeouts, you should
|
||||
tune these settings.
|
||||
|
||||
Returns:
|
||||
list[:class:`telegram.Update`]
|
||||
|
||||
|
@ -1236,6 +1283,10 @@ class Bot(TelegramObject):
|
|||
"""
|
||||
url = '{0}/getUpdates'.format(self.base_url)
|
||||
|
||||
if network_delay is not None:
|
||||
warnings.warn('network_delay is deprecated, use read_latency instead')
|
||||
read_latency = network_delay
|
||||
|
||||
data = {'timeout': timeout}
|
||||
|
||||
if offset:
|
||||
|
@ -1243,9 +1294,12 @@ class Bot(TelegramObject):
|
|||
if limit:
|
||||
data['limit'] = limit
|
||||
|
||||
urlopen_timeout = timeout + network_delay
|
||||
|
||||
result = self._request.post(url, data, timeout=urlopen_timeout)
|
||||
# Ideally we'd use an aggressive read timeout for the polling. However,
|
||||
# * Short polling should return within 2 seconds.
|
||||
# * Long polling poses a different problem: the connection might have been dropped while
|
||||
# waiting for the server to return and there's no way of knowing the connection had been
|
||||
# dropped in real time.
|
||||
result = self._request.post(url, data, timeout=float(read_latency) + float(timeout))
|
||||
|
||||
if result:
|
||||
self.logger.debug('Getting updates: %s', [u['update_id'] for u in result])
|
||||
|
@ -1263,9 +1317,10 @@ class Bot(TelegramObject):
|
|||
|
||||
Args:
|
||||
webhook_url: HTTPS url to send updates to. Use an empty string to remove webhook
|
||||
integration
|
||||
timeout (Optional[float]): If this value is specified, use it as
|
||||
the definitive timeout (in seconds) for urlopen() operations.
|
||||
integration.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1295,8 +1350,9 @@ class Bot(TelegramObject):
|
|||
Args:
|
||||
chat_id: Unique identifier for the target chat or username of the target channel (in
|
||||
the format @channelusername).
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1322,8 +1378,9 @@ class Bot(TelegramObject):
|
|||
Args:
|
||||
chat_id: Unique identifier for the target chat or username of the target channel (in
|
||||
the format @channelusername).
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1352,8 +1409,9 @@ class Bot(TelegramObject):
|
|||
Args:
|
||||
chat_id: Unique identifier for the target chat or username of the target channel (in
|
||||
the format @channelusername).
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1378,8 +1436,9 @@ class Bot(TelegramObject):
|
|||
Args:
|
||||
chat_id: Unique identifier for the target chat or username of the target channel (in
|
||||
the format @channelusername).
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1405,8 +1464,9 @@ class Bot(TelegramObject):
|
|||
chat_id: Unique identifier for the target chat or username of the target channel (in
|
||||
the format @channelusername).
|
||||
user_id: Unique identifier of the target user.
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
**kwargs (dict): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
|
@ -1424,11 +1484,16 @@ class Bot(TelegramObject):
|
|||
|
||||
return ChatMember.de_json(result, self)
|
||||
|
||||
def getWebhookInfo(self, **kwargs):
|
||||
def getWebhookInfo(self, timeout=None, **kwargs):
|
||||
"""Use this method to get current webhook status.
|
||||
|
||||
If the bot is using getUpdates, will return an object with the url field empty.
|
||||
|
||||
Args:
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
:class: `telegram.WebhookInfo`
|
||||
|
||||
|
@ -1437,7 +1502,7 @@ class Bot(TelegramObject):
|
|||
|
||||
data = {}
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return WebhookInfo.de_json(result, self)
|
||||
|
||||
|
@ -1450,6 +1515,7 @@ class Bot(TelegramObject):
|
|||
edit_message=None,
|
||||
force=None,
|
||||
disable_edit_message=None,
|
||||
timeout=None,
|
||||
**kwargs):
|
||||
"""Use this method to set the score of the specified user in a game.
|
||||
|
||||
|
@ -1469,10 +1535,9 @@ class Bot(TelegramObject):
|
|||
automatically edited to include the current scoreboard.
|
||||
edit_message (Optional[bool]): Deprecated. Has the opposite logic for
|
||||
`disable_edit_message`.
|
||||
|
||||
Keyword Args:
|
||||
timeout (Optional[float]): If this value is specified, use it as the definitive timeout
|
||||
(in seconds) for urlopen() operations.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message` or True: The edited message, or if the
|
||||
|
@ -1500,7 +1565,7 @@ class Bot(TelegramObject):
|
|||
else:
|
||||
warnings.warn('edit_message is ignored when disable_edit_message is used')
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
if result is True:
|
||||
return result
|
||||
else:
|
||||
|
@ -1511,9 +1576,15 @@ class Bot(TelegramObject):
|
|||
chat_id=None,
|
||||
message_id=None,
|
||||
inline_message_id=None,
|
||||
timeout=None,
|
||||
**kwargs):
|
||||
"""Use this method to get data for high score tables.
|
||||
|
||||
Args:
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
list[:class:`telegram.GameHighScore`]: Scores of the specified user and several of his
|
||||
neighbors in a game.
|
||||
|
@ -1530,7 +1601,7 @@ class Bot(TelegramObject):
|
|||
if inline_message_id:
|
||||
data['inline_message_id'] = inline_message_id
|
||||
|
||||
result = self._request.post(url, data, timeout=kwargs.get('timeout'))
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return [GameHighScore.de_json(hs, self) for hs in result]
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ class Chat(TelegramObject):
|
|||
username (str): Username, for private chats and channels if available
|
||||
first_name (str): First name of the other party in a private chat
|
||||
last_name (str): Last name of the other party in a private chat
|
||||
all_members_are_admins (bool): True if a group has 'All Members Are Admins' enabled.
|
||||
all_members_are_administrators (bool): True if group has 'All Members Are Administrators'
|
||||
|
||||
Args:
|
||||
id (int):
|
||||
|
@ -57,7 +57,7 @@ class Chat(TelegramObject):
|
|||
username=None,
|
||||
first_name=None,
|
||||
last_name=None,
|
||||
all_members_are_admins=None,
|
||||
all_members_are_administrators=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
|
@ -68,7 +68,7 @@ class Chat(TelegramObject):
|
|||
self.username = username
|
||||
self.first_name = first_name
|
||||
self.last_name = last_name
|
||||
self.all_members_are_admins = all_members_are_admins
|
||||
self.all_members_are_administrators = all_members_are_administrators
|
||||
|
||||
self.bot = bot
|
||||
|
||||
|
|
|
@ -83,8 +83,14 @@ class CommandHandler(Handler):
|
|||
and (update.message or update.edited_message and self.allow_edited)):
|
||||
message = update.message or update.edited_message
|
||||
|
||||
return (message.text and message.text.startswith('/')
|
||||
and message.text[1:].split(' ')[0].split('@')[0] == self.command)
|
||||
if message.text:
|
||||
command = message.text[1:].split(' ')[0].split('@')
|
||||
command.append(
|
||||
update.message.bot.username) # in case the command was send without a username
|
||||
return (message.text.startswith('/') and command[0] == self.command
|
||||
and command[1] == update.message.bot.username)
|
||||
else:
|
||||
return False
|
||||
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -22,7 +22,6 @@ import logging
|
|||
|
||||
from telegram import Update
|
||||
from telegram.ext import Handler
|
||||
from telegram.utils.helpers import extract_chat_and_user
|
||||
from telegram.utils.promise import Promise
|
||||
|
||||
|
||||
|
@ -119,7 +118,7 @@ class ConversationHandler(Handler):
|
|||
if not isinstance(update, Update) or update.channel_post:
|
||||
return False
|
||||
|
||||
chat, user = extract_chat_and_user(update)
|
||||
chat, user = update.extract_chat_and_user()
|
||||
|
||||
key = (chat.id, user.id) if chat else (None, user.id)
|
||||
state = self.conversations.get(key)
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
Dispatcher """
|
||||
|
||||
from telegram.utils.deprecate import deprecate
|
||||
from telegram.utils.helpers import extract_chat_and_user
|
||||
|
||||
|
||||
class Handler(object):
|
||||
|
@ -105,7 +104,7 @@ class Handler(object):
|
|||
if self.pass_job_queue:
|
||||
optional_args['job_queue'] = dispatcher.job_queue
|
||||
if self.pass_user_data or self.pass_chat_data:
|
||||
chat, user = extract_chat_and_user(update)
|
||||
chat, user = update.extract_chat_and_user()
|
||||
|
||||
if self.pass_user_data:
|
||||
optional_args['user_data'] = dispatcher.user_data[user.id]
|
||||
|
|
|
@ -22,6 +22,7 @@ Telegram bots intuitive."""
|
|||
import logging
|
||||
import os
|
||||
import ssl
|
||||
import warnings
|
||||
from threading import Thread, Lock, current_thread, Event
|
||||
from time import sleep
|
||||
import subprocess
|
||||
|
@ -61,6 +62,8 @@ class Updater(object):
|
|||
bot (Optional[Bot]): A pre-initialized bot instance. If a pre-initizlied bot is used, it is
|
||||
the user's responsibility to create it using a `Request` instance with a large enough
|
||||
connection pool.
|
||||
request_kwargs (Optional[dict]): Keyword args to control the creation of a request object
|
||||
(ignored if `bot` argument is used).
|
||||
|
||||
Raises:
|
||||
ValueError: If both `token` and `bot` are passed or none of them.
|
||||
|
@ -68,7 +71,7 @@ class Updater(object):
|
|||
"""
|
||||
_request = None
|
||||
|
||||
def __init__(self, token=None, base_url=None, workers=4, bot=None):
|
||||
def __init__(self, token=None, base_url=None, workers=4, bot=None, request_kwargs=None):
|
||||
if (token is None) and (bot is None):
|
||||
raise ValueError('`token` or `bot` must be passed')
|
||||
if (token is not None) and (bot is not None):
|
||||
|
@ -83,7 +86,11 @@ class Updater(object):
|
|||
# * 1 for polling Updater (even if webhook is used, we can spare a connection)
|
||||
# * 1 for JobQueue
|
||||
# * 1 for main thread
|
||||
self._request = Request(con_pool_size=workers + 4)
|
||||
if request_kwargs is None:
|
||||
request_kwargs = {}
|
||||
if 'con_pool_size' not in request_kwargs:
|
||||
request_kwargs['con_pool_size'] = workers + 4
|
||||
self._request = Request(**request_kwargs)
|
||||
self.bot = Bot(token, base_url, request=self._request)
|
||||
self.update_queue = Queue()
|
||||
self.job_queue = JobQueue(self.bot)
|
||||
|
@ -122,9 +129,10 @@ class Updater(object):
|
|||
def start_polling(self,
|
||||
poll_interval=0.0,
|
||||
timeout=10,
|
||||
network_delay=5.,
|
||||
network_delay=None,
|
||||
clean=False,
|
||||
bootstrap_retries=0):
|
||||
bootstrap_retries=0,
|
||||
read_latency=2.):
|
||||
"""
|
||||
Starts polling updates from Telegram.
|
||||
|
||||
|
@ -134,7 +142,8 @@ class Updater(object):
|
|||
|
||||
timeout (Optional[float]): Passed to Bot.getUpdates
|
||||
|
||||
network_delay (Optional[float]): Passed to Bot.getUpdates
|
||||
network_delay: Deprecated. Will be honoured as `read_latency` for a while but will be
|
||||
removed in the future.
|
||||
|
||||
clean (Optional[bool]): Whether to clean any pending updates on Telegram servers before
|
||||
actually starting to poll. Default is False.
|
||||
|
@ -143,13 +152,22 @@ class Updater(object):
|
|||
will retry on failures on the Telegram server.
|
||||
|
||||
| < 0 - retry indefinitely
|
||||
| 0 - no retries (default)
|
||||
| 0 - no retries (default)
|
||||
| > 0 - retry up to X times
|
||||
|
||||
read_latency (Optional[float|int]): Grace time in seconds for receiving the reply from
|
||||
server. Will be added to the `timeout` value and used as the read timeout from
|
||||
server (Default: 2).
|
||||
|
||||
|
||||
Returns:
|
||||
Queue: The update queue that can be filled from the main thread
|
||||
|
||||
"""
|
||||
if network_delay is not None:
|
||||
warnings.warn('network_delay is deprecated, use read_latency instead')
|
||||
read_latency = network_delay
|
||||
|
||||
with self.__lock:
|
||||
if not self.running:
|
||||
self.running = True
|
||||
|
@ -158,7 +176,7 @@ class Updater(object):
|
|||
self.job_queue.start()
|
||||
self._init_thread(self.dispatcher.start, "dispatcher")
|
||||
self._init_thread(self._start_polling, "updater", poll_interval, timeout,
|
||||
network_delay, bootstrap_retries, clean)
|
||||
read_latency, bootstrap_retries, clean)
|
||||
|
||||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
@ -215,7 +233,7 @@ class Updater(object):
|
|||
# Return the update queue so the main thread can insert updates
|
||||
return self.update_queue
|
||||
|
||||
def _start_polling(self, poll_interval, timeout, network_delay, bootstrap_retries, clean):
|
||||
def _start_polling(self, poll_interval, timeout, read_latency, bootstrap_retries, clean):
|
||||
"""
|
||||
Thread target of thread 'updater'. Runs in background, pulls
|
||||
updates from Telegram and inserts them in the update queue of the
|
||||
|
@ -230,7 +248,7 @@ class Updater(object):
|
|||
while self.running:
|
||||
try:
|
||||
updates = self.bot.getUpdates(
|
||||
self.last_update_id, timeout=timeout, network_delay=network_delay)
|
||||
self.last_update_id, timeout=timeout, read_latency=read_latency)
|
||||
except RetryAfter as e:
|
||||
self.logger.info(str(e))
|
||||
cur_interval = 0.5 + e.retry_after
|
||||
|
|
|
@ -66,20 +66,24 @@ class File(TelegramObject):
|
|||
|
||||
return File(bot=bot, **data)
|
||||
|
||||
def download(self, custom_path=None, out=None):
|
||||
def download(self, custom_path=None, out=None, timeout=None):
|
||||
"""
|
||||
Download this file. By default, the file is saved in the current working directory with its
|
||||
original filename as reported by Telegram. If a ``custom_path`` is supplied, it will be
|
||||
saved to that path instead. If ``out`` is defined, the file contents will be saved to that
|
||||
object using the ``out.write`` method. ``custom_path`` and ``out`` are mutually exclusive.
|
||||
|
||||
Keyword Args:
|
||||
Args:
|
||||
custom_path (Optional[str]): Custom path.
|
||||
out (Optional[object]): A file-like object. Must be opened in binary mode, if
|
||||
applicable.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Raises:
|
||||
ValueError: If both ``custom_path`` and ``out`` are passed.
|
||||
|
||||
"""
|
||||
|
||||
if custom_path is not None and out is not None:
|
||||
|
@ -97,4 +101,4 @@ class File(TelegramObject):
|
|||
else:
|
||||
filename = basename(url)
|
||||
|
||||
self.bot.request.download(url, filename)
|
||||
self.bot.request.download(url, filename, timeout=timeout)
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
# 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 an object that represents a Telegram Message."""
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from time import mktime
|
||||
|
@ -586,6 +585,7 @@ class Message(TelegramObject):
|
|||
Returns:
|
||||
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
|
||||
text that belongs to them, calculated based on UTF-16 codepoints.
|
||||
|
||||
"""
|
||||
if types is None:
|
||||
types = MessageEntity.ALL_TYPES
|
||||
|
@ -594,3 +594,80 @@ class Message(TelegramObject):
|
|||
entity: self.parse_entity(entity)
|
||||
for entity in self.entities if entity.type in types
|
||||
}
|
||||
|
||||
@property
|
||||
def text_html(self):
|
||||
"""
|
||||
Creates an html-formatted string from the markup entities found in the message
|
||||
(uses ``parse_entities``).
|
||||
|
||||
Use this if you want to retrieve the original string sent by the bot, as opposed to the
|
||||
plain text with corresponding markup entities.
|
||||
|
||||
Returns:
|
||||
str
|
||||
|
||||
"""
|
||||
entities = self.parse_entities()
|
||||
message_text = self.text
|
||||
markdown_text = ''
|
||||
last_offset = 0
|
||||
|
||||
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
insert = '<a href="{}">{}</a>'.format(entity.url, text)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '<b>' + text + '</b>'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '<i>' + text + '</i>'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
insert = '<code>' + text + '</code>'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
insert = '<pre>' + text + '</pre>'
|
||||
else:
|
||||
insert = text
|
||||
|
||||
markdown_text += message_text[last_offset:entity.offset] + insert
|
||||
last_offset = entity.offset + entity.length
|
||||
|
||||
markdown_text += message_text[last_offset:]
|
||||
return markdown_text
|
||||
|
||||
@property
|
||||
def text_markdown(self):
|
||||
"""
|
||||
Creates a markdown-formatted string from the markup entities found in the message
|
||||
(uses ``parse_entities``).
|
||||
|
||||
Use this if you want to retrieve the original string sent by the bot, as opposed to the
|
||||
plain text with corresponding markup entities.
|
||||
|
||||
Returns:
|
||||
str
|
||||
"""
|
||||
entities = self.parse_entities()
|
||||
message_text = self.text
|
||||
markdown_text = ''
|
||||
last_offset = 0
|
||||
|
||||
for entity, text in sorted(entities.items(), key=(lambda item: item[0].offset)):
|
||||
|
||||
if entity.type == MessageEntity.TEXT_LINK:
|
||||
insert = '[{}]({})'.format(text, entity.url)
|
||||
elif entity.type == MessageEntity.BOLD:
|
||||
insert = '*' + text + '*'
|
||||
elif entity.type == MessageEntity.ITALIC:
|
||||
insert = '_' + text + '_'
|
||||
elif entity.type == MessageEntity.CODE:
|
||||
insert = '`' + text + '`'
|
||||
elif entity.type == MessageEntity.PRE:
|
||||
insert = '```' + text + '```'
|
||||
else:
|
||||
insert = text
|
||||
|
||||
markdown_text += message_text[last_offset:entity.offset] + insert
|
||||
last_offset = entity.offset + entity.length
|
||||
|
||||
markdown_text += message_text[last_offset:]
|
||||
return markdown_text
|
||||
|
|
|
@ -98,3 +98,80 @@ class Update(TelegramObject):
|
|||
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
|
||||
|
||||
return Update(**data)
|
||||
|
||||
def extract_chat_and_user(self):
|
||||
"""
|
||||
Helper method to get the sender's chat and user objects from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
tuple: of (chat, user), with None-values if no object could not be found.
|
||||
"""
|
||||
user = None
|
||||
chat = None
|
||||
|
||||
if self.message:
|
||||
user = self.message.from_user
|
||||
chat = self.message.chat
|
||||
|
||||
elif self.edited_message:
|
||||
user = self.edited_message.from_user
|
||||
chat = self.edited_message.chat
|
||||
|
||||
elif self.inline_query:
|
||||
user = self.inline_query.from_user
|
||||
|
||||
elif self.chosen_inline_result:
|
||||
user = self.chosen_inline_result.from_user
|
||||
|
||||
elif self.callback_query:
|
||||
user = self.callback_query.from_user
|
||||
chat = self.callback_query.message.chat if self.callback_query.message else None
|
||||
|
||||
return chat, user
|
||||
|
||||
def extract_message_text(self):
|
||||
"""
|
||||
Helper method to get the message text from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
str: The extracted message text
|
||||
|
||||
Raises:
|
||||
ValueError: If no message text was found in the update
|
||||
|
||||
"""
|
||||
if self.message:
|
||||
return self.message.text
|
||||
elif self.edited_message:
|
||||
return self.edited_message.text
|
||||
elif self.callback_query:
|
||||
return self.callback_query.message.text
|
||||
else:
|
||||
raise ValueError("Update contains no message text.")
|
||||
|
||||
def extract_entities(self):
|
||||
"""
|
||||
Helper method to get parsed entities from an arbitrary update.
|
||||
Depending on the type of update, one of the available attributes ``message``,
|
||||
``edited_message`` or ``callback_query`` is used to determine the result.
|
||||
|
||||
Returns:
|
||||
dict[:class:`telegram.MessageEntity`, ``str``]: A dictionary of entities mapped to the
|
||||
text that belongs to them, calculated based on UTF-16 codepoints.
|
||||
|
||||
Raises:
|
||||
ValueError: If no entities were found in the update
|
||||
|
||||
"""
|
||||
if self.message:
|
||||
return self.message.parse_entities()
|
||||
elif self.edited_message:
|
||||
return self.edited_message.parse_entities()
|
||||
elif self.callback_query:
|
||||
return self.callback_query.message.parse_entities()
|
||||
else:
|
||||
raise ValueError("No message object found in self, therefore no entities available.")
|
||||
|
|
|
@ -18,27 +18,10 @@
|
|||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
""" This module contains helper functions """
|
||||
|
||||
import re
|
||||
|
||||
def extract_chat_and_user(update):
|
||||
user = None
|
||||
chat = None
|
||||
|
||||
if update.message:
|
||||
user = update.message.from_user
|
||||
chat = update.message.chat
|
||||
|
||||
elif update.edited_message:
|
||||
user = update.edited_message.from_user
|
||||
chat = update.edited_message.chat
|
||||
|
||||
elif update.inline_query:
|
||||
user = update.inline_query.from_user
|
||||
|
||||
elif update.chosen_inline_result:
|
||||
user = update.chosen_inline_result.from_user
|
||||
|
||||
elif update.callback_query:
|
||||
user = update.callback_query.from_user
|
||||
chat = update.callback_query.message.chat if update.callback_query.message else None
|
||||
|
||||
return chat, user
|
||||
def escape_markdown(text):
|
||||
"""Helper function to escape telegram markup symbols"""
|
||||
escape_chars = '\*_`\['
|
||||
return re.sub(r'([%s])' % escape_chars, r'\\\1', text)
|
||||
|
|
|
@ -17,18 +17,23 @@
|
|||
# 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 methods to make POST and GET requests"""
|
||||
import os
|
||||
import socket
|
||||
import logging
|
||||
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import logging
|
||||
|
||||
import certifi
|
||||
import urllib3
|
||||
from urllib3.connection import HTTPConnection
|
||||
from urllib3.util.timeout import Timeout
|
||||
try:
|
||||
from urllib3.contrib.socks import SOCKSProxyManager
|
||||
except ImportError:
|
||||
SOCKSProxyManager = None
|
||||
|
||||
from telegram import (InputFile, TelegramError)
|
||||
from telegram.error import (Unauthorized, NetworkError, TimedOut, BadRequest, ChatMigrated,
|
||||
|
@ -43,23 +48,40 @@ class Request(object):
|
|||
telegram servers.
|
||||
|
||||
Args:
|
||||
con_pool_size (int): Number of connections to keep in the connection pool.
|
||||
proxy_url (str): The URL to the proxy server. For example: `http://127.0.0.1:3128`.
|
||||
urllib3_proxy_kwargs (dict): Arbitrary arguments passed as-is to `urllib3.ProxyManager`.
|
||||
This value will be ignored if proxy_url is not set.
|
||||
connect_timeout (int|float): The maximum amount of time (in seconds) to wait for a
|
||||
connection attempt to a server to succeed. None will set an infinite timeout for
|
||||
connection attempts. (default: 5.)
|
||||
read_timeout (int|float): The maximum amount of time (in seconds) to wait between
|
||||
consecutive read operations for a response from the server. None will set an infinite
|
||||
timeout. This value is usually overridden by the various ``telegram.Bot`` methods.
|
||||
(default: 5.)
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, con_pool_size=1, proxy_url=None, urllib3_proxy_kwargs=None):
|
||||
def __init__(self,
|
||||
con_pool_size=1,
|
||||
proxy_url=None,
|
||||
urllib3_proxy_kwargs=None,
|
||||
connect_timeout=5.,
|
||||
read_timeout=5.):
|
||||
if urllib3_proxy_kwargs is None:
|
||||
urllib3_proxy_kwargs = dict()
|
||||
|
||||
self._connect_timeout = connect_timeout
|
||||
|
||||
kwargs = dict(
|
||||
maxsize=con_pool_size,
|
||||
cert_reqs='CERT_REQUIRED',
|
||||
ca_certs=certifi.where(),
|
||||
socket_options=HTTPConnection.default_socket_options + [
|
||||
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
|
||||
])
|
||||
],
|
||||
timeout=urllib3.Timeout(
|
||||
connect=self._connect_timeout, read=read_timeout),)
|
||||
|
||||
# Set a proxy according to the following order:
|
||||
# * proxy defined in proxy_url (+ urllib3_proxy_kwargs)
|
||||
|
@ -74,11 +96,16 @@ class Request(object):
|
|||
mgr = urllib3.PoolManager(**kwargs)
|
||||
else:
|
||||
kwargs.update(urllib3_proxy_kwargs)
|
||||
mgr = urllib3.proxy_from_url(proxy_url, **kwargs)
|
||||
if mgr.proxy.auth:
|
||||
# TODO: what about other auth types?
|
||||
auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth)
|
||||
mgr.proxy_headers.update(auth_hdrs)
|
||||
if proxy_url.startswith('socks'):
|
||||
if not SOCKSProxyManager:
|
||||
raise RuntimeError('PySocks is missing')
|
||||
mgr = SOCKSProxyManager(proxy_url, **kwargs)
|
||||
else:
|
||||
mgr = urllib3.proxy_from_url(proxy_url, **kwargs)
|
||||
if mgr.proxy.auth:
|
||||
# TODO: what about other auth types?
|
||||
auth_hdrs = urllib3.make_headers(proxy_basic_auth=mgr.proxy.auth)
|
||||
mgr.proxy_headers.update(auth_hdrs)
|
||||
|
||||
self._con_pool = mgr
|
||||
|
||||
|
@ -128,6 +155,12 @@ class Request(object):
|
|||
TelegramError
|
||||
|
||||
"""
|
||||
# Make sure to hint Telegram servers that we reuse connections by sending
|
||||
# "Connection: keep-alive" in the HTTP headers.
|
||||
if 'headers' not in kwargs:
|
||||
kwargs['headers'] = {}
|
||||
kwargs['headers']['connection'] = 'keep-alive'
|
||||
|
||||
try:
|
||||
resp = self._con_pool.request(*args, **kwargs)
|
||||
except urllib3.exceptions.TimeoutError:
|
||||
|
@ -149,7 +182,7 @@ class Request(object):
|
|||
if resp.status in (401, 403):
|
||||
raise Unauthorized()
|
||||
elif resp.status == 400:
|
||||
raise BadRequest(repr(message))
|
||||
raise BadRequest(message)
|
||||
elif resp.status == 404:
|
||||
raise InvalidToken()
|
||||
elif resp.status == 502:
|
||||
|
@ -157,33 +190,14 @@ class Request(object):
|
|||
else:
|
||||
raise NetworkError('{0} ({1})'.format(message, resp.status))
|
||||
|
||||
def get(self, url):
|
||||
def get(self, url, timeout=None):
|
||||
"""Request an URL.
|
||||
|
||||
Args:
|
||||
url:
|
||||
The web location we want to retrieve.
|
||||
|
||||
Returns:
|
||||
A JSON object.
|
||||
|
||||
"""
|
||||
result = self._request_wrapper('GET', url)
|
||||
return self._parse(result)
|
||||
|
||||
def post(self, url, data, timeout=None):
|
||||
"""Request an URL.
|
||||
Args:
|
||||
url:
|
||||
The web location we want to retrieve.
|
||||
data:
|
||||
A dict of (str, unicode) key/value pairs.
|
||||
timeout:
|
||||
float. If this value is specified, use it as the definitive timeout (in
|
||||
seconds) for urlopen() operations. [Optional]
|
||||
|
||||
Notes:
|
||||
If neither `timeout` nor `data['timeout']` is specified. The underlying
|
||||
defaults are used.
|
||||
url (str): The web location we want to retrieve.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
A JSON object.
|
||||
|
@ -192,15 +206,33 @@ class Request(object):
|
|||
urlopen_kwargs = {}
|
||||
|
||||
if timeout is not None:
|
||||
urlopen_kwargs['timeout'] = timeout
|
||||
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
|
||||
|
||||
result = self._request_wrapper('GET', url, **urlopen_kwargs)
|
||||
return self._parse(result)
|
||||
|
||||
def post(self, url, data, timeout=None):
|
||||
"""Request an URL.
|
||||
Args:
|
||||
url (str): The web location we want to retrieve.
|
||||
data (dict[str, str]): A dict of key/value pairs. Note: On py2.7 value is unicode.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
Returns:
|
||||
A JSON object.
|
||||
|
||||
"""
|
||||
urlopen_kwargs = {}
|
||||
|
||||
if timeout is not None:
|
||||
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
|
||||
|
||||
if InputFile.is_inputfile(data):
|
||||
data = InputFile(data)
|
||||
result = self._request_wrapper('POST',
|
||||
url,
|
||||
body=data.to_form(),
|
||||
headers=data.headers,
|
||||
**urlopen_kwargs)
|
||||
result = self._request_wrapper(
|
||||
'POST', url, body=data.to_form(), headers=data.headers, **urlopen_kwargs)
|
||||
else:
|
||||
data = json.dumps(data)
|
||||
result = self._request_wrapper(
|
||||
|
@ -212,24 +244,34 @@ class Request(object):
|
|||
|
||||
return self._parse(result)
|
||||
|
||||
def retrieve(self, url):
|
||||
def retrieve(self, url, timeout=None):
|
||||
"""Retrieve the contents of a file by its URL.
|
||||
Args:
|
||||
url:
|
||||
The web location we want to retrieve.
|
||||
"""
|
||||
return self._request_wrapper('GET', url)
|
||||
|
||||
def download(self, url, filename):
|
||||
Args:
|
||||
url (str): The web location we want to retrieve.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
"""
|
||||
urlopen_kwargs = {}
|
||||
if timeout is not None:
|
||||
urlopen_kwargs['timeout'] = Timeout(read=timeout, connect=self._connect_timeout)
|
||||
|
||||
return self._request_wrapper('GET', url, **urlopen_kwargs)
|
||||
|
||||
def download(self, url, filename, timeout=None):
|
||||
"""Download a file by its URL.
|
||||
Args:
|
||||
url:
|
||||
The web location we want to retrieve.
|
||||
url (str): The web location we want to retrieve.
|
||||
timeout (Optional[int|float]): 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).
|
||||
|
||||
filename:
|
||||
The filename within the path to download the file.
|
||||
|
||||
"""
|
||||
buf = self.retrieve(url)
|
||||
buf = self.retrieve(url, timeout=timeout)
|
||||
with open(filename, 'wb') as fobj:
|
||||
fobj.write(buf)
|
||||
|
|
5
tests/README.md
Normal file
5
tests/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
tests
|
||||
=====
|
||||
|
||||
Some tests fail because of weird behaviour of the Telegram API. We comment these
|
||||
out and mark them with a `TODO` comment.
|
|
@ -19,8 +19,8 @@
|
|||
"""This module contains an object that represents a Base class for tests"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from nose.tools import make_decorator
|
||||
|
||||
|
@ -78,13 +78,21 @@ def timeout(time_limit):
|
|||
raise TestTimedOut(time_limit, frame)
|
||||
|
||||
def newfunc(*args, **kwargs):
|
||||
orig_handler = signal.signal(signal.SIGALRM, timed_out)
|
||||
signal.alarm(time_limit)
|
||||
try:
|
||||
# Will only work on unix systems
|
||||
orig_handler = signal.signal(signal.SIGALRM, timed_out)
|
||||
signal.alarm(time_limit)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
rc = func(*args, **kwargs)
|
||||
finally:
|
||||
signal.alarm(0)
|
||||
signal.signal(signal.SIGALRM, orig_handler)
|
||||
try:
|
||||
# Will only work on unix systems
|
||||
signal.alarm(0)
|
||||
signal.signal(signal.SIGALRM, orig_handler)
|
||||
except AttributeError:
|
||||
pass
|
||||
return rc
|
||||
|
||||
newfunc = make_decorator(func)(newfunc)
|
||||
|
|
|
@ -35,7 +35,7 @@ class AudioTest(BaseTest, unittest.TestCase):
|
|||
|
||||
def setUp(self):
|
||||
self.audio_file = open('tests/data/telegram.mp3', 'rb')
|
||||
self.audio_file_id = 'BQADAQADDwADHyP1B6PSPq2HjX8kAg'
|
||||
self.audio_file_id = 'CQADAQADDwADHyP1B6PSPq2HjX8kAg'
|
||||
self.audio_file_url = 'https://raw.githubusercontent.com/python-telegram-bot/python-telegram-bot/master/tests/data/telegram.mp3'
|
||||
self.duration = 4
|
||||
self.performer = 'Leandro Toledo'
|
||||
|
|
|
@ -182,7 +182,7 @@ class BotTest(BaseTest, unittest.TestCase):
|
|||
|
||||
self.assertTrue(self.is_json(message.to_json()))
|
||||
self.assertEqual(message.game.description, 'This is a test game for python-telegram-bot.')
|
||||
self.assertEqual(message.game.animation.file_id, 'BQADAQADKwIAAvjAuQABozciVqhFDO0C')
|
||||
self.assertEqual(message.game.animation.file_id, 'CgADAQADKwIAAvjAuQABozciVqhFDO0C')
|
||||
self.assertEqual(message.game.photo[0].file_size, 851)
|
||||
|
||||
@flaky(3, 1)
|
||||
|
@ -365,7 +365,7 @@ class BotTest(BaseTest, unittest.TestCase):
|
|||
chat_id=game.chat_id,
|
||||
message_id=game.message_id)
|
||||
|
||||
self.assertTrue('BOT_SCORE_NOT_MODIFIED' in cm.exception.message)
|
||||
self.assertTrue('BOT_SCORE_NOT_MODIFIED' in str(cm.exception.message).upper())
|
||||
|
||||
@flaky(3, 1)
|
||||
@timeout(10)
|
||||
|
|
|
@ -36,13 +36,13 @@ class ChatTest(BaseTest, unittest.TestCase):
|
|||
self.id = -28767330
|
||||
self.title = 'ToledosPalaceBot - Group'
|
||||
self.type = 'group'
|
||||
self.all_members_are_admins = False
|
||||
self.all_members_are_administrators = False
|
||||
|
||||
self.json_dict = {
|
||||
'id': self.id,
|
||||
'title': self.title,
|
||||
'type': self.type,
|
||||
'all_members_are_admins': self.all_members_are_admins
|
||||
'all_members_are_administrators': self.all_members_are_administrators
|
||||
}
|
||||
|
||||
def test_group_chat_de_json_empty_json(self):
|
||||
|
@ -56,7 +56,8 @@ class ChatTest(BaseTest, unittest.TestCase):
|
|||
self.assertEqual(group_chat.id, self.id)
|
||||
self.assertEqual(group_chat.title, self.title)
|
||||
self.assertEqual(group_chat.type, self.type)
|
||||
self.assertEqual(group_chat.all_members_are_admins, self.all_members_are_admins)
|
||||
self.assertEqual(group_chat.all_members_are_administrators,
|
||||
self.all_members_are_administrators)
|
||||
|
||||
def test_group_chat_to_json(self):
|
||||
group_chat = telegram.Chat.de_json(self.json_dict, self._bot)
|
||||
|
@ -70,7 +71,8 @@ class ChatTest(BaseTest, unittest.TestCase):
|
|||
self.assertEqual(group_chat['id'], self.id)
|
||||
self.assertEqual(group_chat['title'], self.title)
|
||||
self.assertEqual(group_chat['type'], self.type)
|
||||
self.assertEqual(group_chat['all_members_are_admins'], self.all_members_are_admins)
|
||||
self.assertEqual(group_chat['all_members_are_administrators'],
|
||||
self.all_members_are_administrators)
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_send_action(self):
|
||||
|
|
|
@ -80,8 +80,8 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
|
|||
self.fallbacks = [CommandHandler('eat', self.start)]
|
||||
|
||||
def _setup_updater(self, *args, **kwargs):
|
||||
bot = MockBot(*args, **kwargs)
|
||||
self.updater = Updater(workers=2, bot=bot)
|
||||
self.bot = MockBot(*args, **kwargs)
|
||||
self.updater = Updater(workers=2, bot=self.bot)
|
||||
|
||||
def tearDown(self):
|
||||
if self.updater is not None:
|
||||
|
@ -137,32 +137,32 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
|
|||
queue = self.updater.start_polling(0.01)
|
||||
|
||||
# User one, starts the state machine.
|
||||
message = Message(0, user, None, None, text="/start")
|
||||
message = Message(0, user, None, None, text="/start", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.THIRSTY)
|
||||
|
||||
# The user is thirsty and wants to brew coffee.
|
||||
message = Message(0, user, None, None, text="/brew")
|
||||
message = Message(0, user, None, None, text="/brew", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.BREWING)
|
||||
|
||||
# Lets see if an invalid command makes sure, no state is changed.
|
||||
message = Message(0, user, None, None, text="/nothing")
|
||||
message = Message(0, user, None, None, text="/nothing", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.BREWING)
|
||||
|
||||
# Lets see if the state machine still works by pouring coffee.
|
||||
message = Message(0, user, None, None, text="/pourCoffee")
|
||||
message = Message(0, user, None, None, text="/pourCoffee", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(self.current_state[user.id] == self.DRINKING)
|
||||
|
||||
# Let's now verify that for another user, who did not start yet,
|
||||
# the state has not been changed.
|
||||
message = Message(0, second_user, None, None, text="/brew")
|
||||
message = Message(0, second_user, None, None, text="/brew", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertRaises(KeyError, self._get_state, user_id=second_user.id)
|
||||
|
@ -197,13 +197,13 @@ class ConversationHandlerTest(BaseTest, unittest.TestCase):
|
|||
|
||||
# User starts the state machine with an async function that immediately ends the
|
||||
# conversation. Async results are resolved when the users state is queried next time.
|
||||
message = Message(0, user, None, None, text="/start")
|
||||
message = Message(0, user, None, None, text="/start", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
# Assert that the Promise has been accepted as the new state
|
||||
self.assertEquals(len(handler.conversations), 1)
|
||||
|
||||
message = Message(0, user, None, None, text="resolve promise pls")
|
||||
message = Message(0, user, None, None, text="resolve promise pls", bot=self.bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
# Assert that the Promise has been resolved and the conversation ended.
|
||||
|
|
42
tests/test_helpers.py
Normal file
42
tests/test_helpers.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2016
|
||||
# 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 General 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents Tests for Telegram
|
||||
MessageEntity"""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from telegram.utils import helpers
|
||||
|
||||
sys.path.append('.')
|
||||
|
||||
from tests.base import BaseTest
|
||||
|
||||
|
||||
class HelpersTest(BaseTest, unittest.TestCase):
|
||||
"""This object represents Tests for the Helpers Module"""
|
||||
|
||||
def test_escape_markdown(self):
|
||||
test_str = "*bold*, _italic_, `code`, [text_link](http://github.com/)"
|
||||
expected_str = "\*bold\*, \_italic\_, \`code\`, \[text\_link](http://github.com/)"
|
||||
self.assertEquals(expected_str, helpers.escape_markdown(test_str))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
|
@ -208,9 +208,7 @@ class JobQueueTest(BaseTest, unittest.TestCase):
|
|||
def test_time_unit_dt_time_today(self):
|
||||
# Testing running at a specific time today
|
||||
delta = 2
|
||||
current_time = datetime.datetime.now().time()
|
||||
next_t = datetime.time(current_time.hour, current_time.minute, current_time.second + delta,
|
||||
current_time.microsecond)
|
||||
next_t = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
|
||||
expected_time = time.time() + delta
|
||||
|
||||
self.jq.put(Job(self.job5, repeat=False), next_t=next_t)
|
||||
|
@ -221,9 +219,7 @@ class JobQueueTest(BaseTest, unittest.TestCase):
|
|||
# Testing running at a specific time that has passed today. Since we can't wait a day, we
|
||||
# test if the jobs next_t has been calculated correctly
|
||||
delta = -2
|
||||
current_time = datetime.datetime.now().time()
|
||||
next_t = datetime.time(current_time.hour, current_time.minute, current_time.second + delta,
|
||||
current_time.microsecond)
|
||||
next_t = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
|
||||
expected_time = time.time() + delta + 60 * 60 * 24
|
||||
|
||||
self.jq.put(Job(self.job5, repeat=False), next_t=next_t)
|
||||
|
@ -247,10 +243,7 @@ class JobQueueTest(BaseTest, unittest.TestCase):
|
|||
|
||||
def test_run_daily(self):
|
||||
delta = 1
|
||||
current_time = datetime.datetime.now().time()
|
||||
time_of_day = datetime.time(current_time.hour, current_time.minute,
|
||||
current_time.second + delta, current_time.microsecond)
|
||||
|
||||
time_of_day = (datetime.datetime.now() + datetime.timedelta(seconds=delta)).time()
|
||||
expected_time = time.time() + 60 * 60 * 24 + delta
|
||||
|
||||
self.jq.run_daily(self.job1, time_of_day)
|
||||
|
|
|
@ -33,6 +33,45 @@ from tests.base import BaseTest
|
|||
class MessageTest(BaseTest, unittest.TestCase):
|
||||
"""This object represents Tests for Telegram MessageTest."""
|
||||
|
||||
def setUp(self):
|
||||
self.test_entities = [
|
||||
{
|
||||
'length': 4,
|
||||
'offset': 9,
|
||||
'type': 'bold'
|
||||
},
|
||||
{
|
||||
'length': 6,
|
||||
'offset': 15,
|
||||
'type': 'italic'
|
||||
},
|
||||
{
|
||||
'length': 4,
|
||||
'offset': 23,
|
||||
'type': 'code'
|
||||
},
|
||||
{
|
||||
'length': 5,
|
||||
'offset': 29,
|
||||
'type': 'text_link',
|
||||
'url': 'http://github.com/'
|
||||
},
|
||||
{
|
||||
'length': 3,
|
||||
'offset': 39,
|
||||
'type': 'pre'
|
||||
},
|
||||
]
|
||||
|
||||
self.test_text = 'Test for bold, italic, code, links and pre.'
|
||||
self.test_message = telegram.Message(
|
||||
message_id=1,
|
||||
from_user=None,
|
||||
date=None,
|
||||
chat=None,
|
||||
text=self.test_text,
|
||||
entities=[telegram.MessageEntity(**e) for e in self.test_entities])
|
||||
|
||||
def test_parse_entity(self):
|
||||
text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
|
||||
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
|
||||
|
@ -59,6 +98,17 @@ class MessageTest(BaseTest, unittest.TestCase):
|
|||
{entity: 'http://google.com',
|
||||
entity_2: 'h'})
|
||||
|
||||
def test_text_html(self):
|
||||
test_html_string = 'Test for <b>bold</b>, <i>italic</i>, <code>code</code>, ' \
|
||||
'<a href="http://github.com/">links</a> and <pre>pre</pre>.'
|
||||
text_html = self.test_message.text_html
|
||||
self.assertEquals(test_html_string, text_html)
|
||||
|
||||
def test_text_markdown(self):
|
||||
test_md_string = 'Test for *bold*, _italic_, `code`, [links](http://github.com/) and ```pre```.'
|
||||
text_markdown = self.test_message.text_markdown
|
||||
self.assertEquals(test_md_string, text_markdown)
|
||||
|
||||
@flaky(3, 1)
|
||||
def test_reply_text(self):
|
||||
"""Test for Message.reply_text"""
|
||||
|
|
|
@ -35,7 +35,7 @@ class StickerTest(BaseTest, unittest.TestCase):
|
|||
"""This object represents Tests for Telegram Sticker."""
|
||||
|
||||
def setUp(self):
|
||||
self.sticker_file_id = 'BQADAQADHAADyIsGAAFZfq1bphjqlgI'
|
||||
self.sticker_file_id = 'CAADAQADHAADyIsGAAFZfq1bphjqlgI'
|
||||
self.width = 510
|
||||
self.height = 512
|
||||
self.thumb = {
|
||||
|
@ -76,7 +76,7 @@ class StickerTest(BaseTest, unittest.TestCase):
|
|||
self.assertEqual(sticker.emoji, self.emoji.decode('utf-8'))
|
||||
else:
|
||||
self.assertEqual(sticker.emoji, self.emoji)
|
||||
self.assertEqual(sticker.file_size, self.file_size)
|
||||
# self.assertEqual(sticker.file_size, self.file_size) # TODO
|
||||
|
||||
def test_sticker_de_json(self):
|
||||
sticker = telegram.Sticker.de_json(self.json_dict, self._bot)
|
||||
|
|
|
@ -76,6 +76,17 @@ class UpdateTest(BaseTest, unittest.TestCase):
|
|||
self.assertEqual(update['update_id'], self.update_id)
|
||||
self.assertTrue(isinstance(update['message'], telegram.Message))
|
||||
|
||||
def test_extract_chat_and_user(self):
|
||||
update = telegram.Update.de_json(self.json_dict, self._bot)
|
||||
chat, user = update.extract_chat_and_user()
|
||||
self.assertEqual(update.message.chat, chat)
|
||||
self.assertEqual(update.message.from_user, user)
|
||||
|
||||
def test_extract_message_text(self):
|
||||
update = telegram.Update.de_json(self.json_dict, self._bot)
|
||||
text = update.extract_message_text()
|
||||
self.assertEqual(update.message.text, text)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -231,31 +231,33 @@ class UpdaterTest(BaseTest, unittest.TestCase):
|
|||
self.assertTrue(None is self.received_message)
|
||||
|
||||
def test_addRemoveTelegramCommandHandler(self):
|
||||
self._setup_updater('/test')
|
||||
self._setup_updater('', messages=0)
|
||||
d = self.updater.dispatcher
|
||||
handler = CommandHandler('test', self.telegramHandlerTest)
|
||||
self.updater.dispatcher.add_handler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
user = User(first_name="singelton", id=404)
|
||||
bot = self.updater.bot
|
||||
queue = self.updater.start_polling(0.01)
|
||||
|
||||
# regular use
|
||||
message = Message(0, user, None, None, text="/test", bot=bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test')
|
||||
|
||||
# Remove handler
|
||||
d.remove_handler(handler)
|
||||
self.reset()
|
||||
# assigned use
|
||||
message = Message(0, user, None, None, text="/test@MockBot", bot=bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test@MockBot')
|
||||
|
||||
self.updater.bot.send_messages = 1
|
||||
# directed at other bot
|
||||
self.reset()
|
||||
message = Message(0, user, None, None, text="/test@OtherBot", bot=bot)
|
||||
queue.put(Update(update_id=0, message=message))
|
||||
sleep(.1)
|
||||
self.assertTrue(None is self.received_message)
|
||||
|
||||
def test_editedCommandHandler(self):
|
||||
self._setup_updater('/test', edited=True)
|
||||
d = self.updater.dispatcher
|
||||
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=True)
|
||||
d.addHandler(handler)
|
||||
self.updater.start_polling(0.01)
|
||||
sleep(.1)
|
||||
self.assertEqual(self.received_message, '/test')
|
||||
|
||||
# Remove handler
|
||||
d.removeHandler(handler)
|
||||
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=False)
|
||||
|
@ -788,9 +790,10 @@ class MockBot(object):
|
|||
self.bootstrap_attempts = 0
|
||||
self.bootstrap_err = bootstrap_err
|
||||
self.edited = edited
|
||||
self.username = "MockBot"
|
||||
|
||||
def mockUpdate(self, text):
|
||||
message = Message(0, User(0, 'Testuser'), None, Chat(0, Chat.GROUP))
|
||||
message = Message(0, User(0, 'Testuser'), None, Chat(0, Chat.GROUP), bot=self)
|
||||
message.text = text
|
||||
update = Update(0)
|
||||
|
||||
|
@ -809,7 +812,7 @@ class MockBot(object):
|
|||
self.bootstrap_attempts += 1
|
||||
raise self.bootstrap_err
|
||||
|
||||
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=2.):
|
||||
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=None, read_latency=2.):
|
||||
|
||||
if self.raise_error:
|
||||
raise TelegramError('Test Error 2')
|
||||
|
|
Loading…
Add table
Reference in a new issue