mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +01:00
support 3.4 API (#865)
This commit is contained in:
parent
8a8b1215c8
commit
bfad2fa1f3
13 changed files with 403 additions and 35 deletions
|
@ -96,7 +96,7 @@ make the development of bots easy and straightforward. These classes are contain
|
|||
Telegram API support
|
||||
====================
|
||||
|
||||
As of **23. July 2017**, all types and methods of the Telegram Bot API 3.2 are supported.
|
||||
All types and methods of the Telegram Bot API 3.4 are supported.
|
||||
|
||||
==========
|
||||
Installing
|
||||
|
|
178
telegram/bot.py
178
telegram/bot.py
|
@ -768,6 +768,7 @@ class Bot(TelegramObject):
|
|||
reply_markup=None,
|
||||
timeout=None,
|
||||
location=None,
|
||||
live_period=None,
|
||||
**kwargs):
|
||||
"""Use this method to send point on the map.
|
||||
|
||||
|
@ -780,6 +781,8 @@ class Bot(TelegramObject):
|
|||
latitude (:obj:`float`, optional): Latitude of location.
|
||||
longitude (:obj:`float`, optional): Longitude of location.
|
||||
location (:class:`telegram.Location`, optional): The location to send.
|
||||
live_period (:obj:`int`, optional): Period in seconds for which the location will be
|
||||
updated, should be between 60 and 86400.
|
||||
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
|
||||
receive a notification with no sound.
|
||||
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
|
||||
|
@ -803,7 +806,11 @@ class Bot(TelegramObject):
|
|||
|
||||
if not (all([latitude, longitude]) or location):
|
||||
raise ValueError("Either location or latitude and longitude must be passed as"
|
||||
"argument")
|
||||
"argument.")
|
||||
|
||||
if not ((latitude is not None or longitude is not None) ^ bool(location)):
|
||||
raise ValueError("Either location or latitude and longitude must be passed as"
|
||||
"argument. Not both.")
|
||||
|
||||
if isinstance(location, Location):
|
||||
latitude = location.latitude
|
||||
|
@ -811,6 +818,114 @@ class Bot(TelegramObject):
|
|||
|
||||
data = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
|
||||
|
||||
if live_period:
|
||||
data['live_period'] = live_period
|
||||
|
||||
return url, data
|
||||
|
||||
@log
|
||||
@message
|
||||
def edit_message_live_location(self,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
inline_message_id=None,
|
||||
latitude=None,
|
||||
longitude=None,
|
||||
location=None,
|
||||
reply_markup=None,
|
||||
**kwargs):
|
||||
"""Use this method to edit live location messages sent by the bot or via the bot
|
||||
(for inline bots). A location can be edited until its :attr:`live_period` expires or
|
||||
editing is explicitly disabled by a call to :attr:`stop_message_live_location`.
|
||||
|
||||
Note:
|
||||
You can either supply a :obj:`latitude` and :obj:`longitude` or a :obj:`location`.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or 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
|
||||
specified. Identifier of the inline message.
|
||||
latitude (:obj:`float`, optional): Latitude of location.
|
||||
longitude (:obj:`float`, optional): Longitude of location.
|
||||
location (:class:`telegram.Location`, optional): The location to send.
|
||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): 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 (: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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success the edited message.
|
||||
"""
|
||||
|
||||
url = '{0}/editMessageLiveLocation'.format(self.base_url)
|
||||
|
||||
if not (all([latitude, longitude]) or location):
|
||||
raise ValueError("Either location or latitude and longitude must be passed as"
|
||||
"argument.")
|
||||
if not ((latitude is not None or longitude is not None) ^ bool(location)):
|
||||
raise ValueError("Either location or latitude and longitude must be passed as"
|
||||
"argument. Not both.")
|
||||
|
||||
if isinstance(location, Location):
|
||||
latitude = location.latitude
|
||||
longitude = location.longitude
|
||||
|
||||
data = {'latitude': latitude, 'longitude': longitude}
|
||||
|
||||
if chat_id:
|
||||
data['chat_id'] = chat_id
|
||||
if message_id:
|
||||
data['message_id'] = message_id
|
||||
if inline_message_id:
|
||||
data['inline_message_id'] = inline_message_id
|
||||
|
||||
return url, data
|
||||
|
||||
@log
|
||||
@message
|
||||
def stop_message_live_location(self,
|
||||
chat_id=None,
|
||||
message_id=None,
|
||||
inline_message_id=None,
|
||||
reply_markup=None,
|
||||
**kwargs):
|
||||
"""Use this method to stop updating a live location message sent by the bot or via the bot
|
||||
(for inline bots) before live_period expires.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or 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
|
||||
specified. Identifier of the inline message.
|
||||
reply_markup (:class:`telegram.ReplyMarkup`, optional): 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 (: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).
|
||||
|
||||
Returns:
|
||||
:class:`telegram.Message`: On success the edited message.
|
||||
"""
|
||||
|
||||
url = '{0}/stopMessageLiveLocation'.format(self.base_url)
|
||||
|
||||
data = {}
|
||||
|
||||
if chat_id:
|
||||
data['chat_id'] = chat_id
|
||||
if message_id:
|
||||
data['message_id'] = message_id
|
||||
if inline_message_id:
|
||||
data['inline_message_id'] = inline_message_id
|
||||
|
||||
return url, data
|
||||
|
||||
@log
|
||||
|
@ -1825,6 +1940,63 @@ class Bot(TelegramObject):
|
|||
|
||||
return ChatMember.de_json(result, self)
|
||||
|
||||
@log
|
||||
def set_chat_sticker_set(self, chat_id, sticker_set_name, timeout=None, **kwargs):
|
||||
"""Use this method to set a new group sticker set for a supergroup.
|
||||
The bot must be an administrator in the chat for this to work and must have the appropriate
|
||||
admin rights. Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned
|
||||
in :attr:`get_chat` requests to check if the bot can use this method.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target supergroup (in the format @supergroupusername).
|
||||
sticker_set_name (:obj:`str`): Name of the sticker set to be set as the group
|
||||
sticker set.
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: True on success.
|
||||
"""
|
||||
|
||||
url = '{0}/setChatStickerSet'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id, 'sticker_set_name': sticker_set_name}
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return result
|
||||
|
||||
@log
|
||||
def delete_chat_sticker_set(self, chat_id, timeout=None, **kwargs):
|
||||
"""Use this method to delete a group sticker set from a supergroup. The bot must be an
|
||||
administrator in the chat for this to work and must have the appropriate admin rights.
|
||||
Use the field :attr:`telegram.Chat.can_set_sticker_set` optionally returned in
|
||||
:attr:`get_chat` requests to check if the bot can use this method.
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
of the target supergroup (in the format @supergroupusername).
|
||||
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).
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: True on success.
|
||||
"""
|
||||
|
||||
url = '{0}/deleteChatStickerSet'.format(self.base_url)
|
||||
|
||||
data = {'chat_id': chat_id}
|
||||
|
||||
result = self._request.post(url, data, timeout=timeout)
|
||||
|
||||
return result
|
||||
|
||||
def get_webhook_info(self, timeout=None, **kwargs):
|
||||
"""Use this method to get current webhook status. Requires no parameters.
|
||||
|
||||
|
@ -2794,6 +2966,8 @@ class Bot(TelegramObject):
|
|||
sendVoice = send_voice
|
||||
sendVideoNote = send_video_note
|
||||
sendLocation = send_location
|
||||
editMessageLiveLocation = edit_message_live_location
|
||||
stopMessageLiveLocation = stop_message_live_location
|
||||
sendVenue = send_venue
|
||||
sendContact = send_contact
|
||||
sendGame = send_game
|
||||
|
@ -2814,6 +2988,8 @@ class Bot(TelegramObject):
|
|||
getChat = get_chat
|
||||
getChatAdministrators = get_chat_administrators
|
||||
getChatMember = get_chat_member
|
||||
setChatStickerSet = set_chat_sticker_set
|
||||
deleteChatStickerSet = delete_chat_sticker_set
|
||||
getChatMembersCount = get_chat_members_count
|
||||
getWebhookInfo = get_webhook_info
|
||||
setGameScore = set_game_score
|
||||
|
|
|
@ -38,6 +38,9 @@ class Chat(TelegramObject):
|
|||
invite_link (:obj:`str`): Optional. Chat invite link, for supergroups and channel chats.
|
||||
pinned_message (:class:`telegram.Message`): Optional. Pinned message, for supergroups.
|
||||
Returned only in get_chat.
|
||||
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
|
||||
can_set_sticker_set (:obj:`bool`): Optional. ``True``, if the bot can change group the
|
||||
sticker set.
|
||||
|
||||
Args:
|
||||
id (:obj:`int`): Unique identifier for this chat. This number may be greater than 32 bits
|
||||
|
@ -61,6 +64,10 @@ class Chat(TelegramObject):
|
|||
pinned_message (:class:`telegram.Message`, optional): Pinned message, for supergroups.
|
||||
Returned only in get_chat.
|
||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
||||
sticker_set_name (:obj:`str`, optional): For supergroups, name of Group sticker set.
|
||||
Returned only in get_chat.
|
||||
can_set_sticker_set (:obj:`bool`, optional): ``True``, if the bot can change group the
|
||||
sticker set. Returned only in get_chat.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
@ -87,6 +94,8 @@ class Chat(TelegramObject):
|
|||
description=None,
|
||||
invite_link=None,
|
||||
pinned_message=None,
|
||||
sticker_set_name=None,
|
||||
can_set_sticker_set=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.id = int(id)
|
||||
|
@ -101,6 +110,8 @@ class Chat(TelegramObject):
|
|||
self.description = description
|
||||
self.invite_link = invite_link
|
||||
self.pinned_message = pinned_message
|
||||
self.sticker_set_name = sticker_set_name
|
||||
self.can_set_sticker_set = can_set_sticker_set
|
||||
|
||||
self.bot = bot
|
||||
self._id_attrs = (self.id,)
|
||||
|
|
|
@ -33,6 +33,8 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
latitude (:obj:`float`): Location latitude in degrees.
|
||||
longitude (:obj:`float`): Location longitude in degrees.
|
||||
title (:obj:`str`): Location title.
|
||||
live_period (:obj:`int`): Optional. Period in seconds for which the location can be
|
||||
updated, should be between 60 and 86400.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`): Optional. Content of the
|
||||
|
@ -46,6 +48,8 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
latitude (:obj:`float`): Location latitude in degrees.
|
||||
longitude (:obj:`float`): Location longitude in degrees.
|
||||
title (:obj:`str`): Location title.
|
||||
live_period (:obj:`int`, optional): Period in seconds for which the location can be
|
||||
updated, should be between 60 and 86400.
|
||||
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
|
||||
to the message.
|
||||
input_message_content (:class:`telegram.InputMessageContent`, optional): Content of the
|
||||
|
@ -62,6 +66,7 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
latitude,
|
||||
longitude,
|
||||
title,
|
||||
live_period=None,
|
||||
reply_markup=None,
|
||||
input_message_content=None,
|
||||
thumb_url=None,
|
||||
|
@ -75,6 +80,8 @@ class InlineQueryResultLocation(InlineQueryResult):
|
|||
self.title = title
|
||||
|
||||
# Optionals
|
||||
if live_period:
|
||||
self.live_period = live_period
|
||||
if reply_markup:
|
||||
self.reply_markup = reply_markup
|
||||
if input_message_content:
|
||||
|
|
|
@ -32,11 +32,14 @@ class InputLocationMessageContent(InputMessageContent):
|
|||
Args:
|
||||
latitude (:obj:`float`): Latitude of the location in degrees.
|
||||
longitude (:obj:`float`): Longitude of the location in degrees.
|
||||
live_period (:obj:`int`, optional): Period in seconds for which the location can be
|
||||
updated, should be between 60 and 86400.
|
||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, latitude, longitude, **kwargs):
|
||||
def __init__(self, latitude, longitude, live_period=None, **kwargs):
|
||||
# Required
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.live_period = live_period
|
||||
|
|
|
@ -26,7 +26,6 @@ from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Stick
|
|||
from telegram.utils.deprecate import warn_deprecate_obj
|
||||
from telegram.utils.helpers import escape_html, escape_markdown, to_timestamp, from_timestamp
|
||||
|
||||
|
||||
_UNDEFINED = object()
|
||||
|
||||
|
||||
|
@ -54,6 +53,10 @@ class Message(TelegramObject):
|
|||
usernames, URLs, bot commands, etc. that appear in the text. See
|
||||
:attr:`Message.parse_entity` and :attr:`parse_entities` methods for how to use
|
||||
properly.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. Special entities like
|
||||
usernames, URLs, bot commands, etc. that appear in the caption. See
|
||||
:attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities` methods for how
|
||||
to use properly.
|
||||
audio (:class:`telegram.Audio`): Optional. Information about the file.
|
||||
document (:class:`telegram.Document`): Optional. Information about the file.
|
||||
game (:class:`telegram.Game`): Optional. Information about the game.
|
||||
|
@ -119,6 +122,10 @@ class Message(TelegramObject):
|
|||
entities (List[:class:`telegram.MessageEntity`], optional): For text messages, special
|
||||
entities like usernames, URLs, bot commands, etc. that appear in the text. See
|
||||
attr:`parse_entity` and attr:`parse_entities` methods for how to use properly.
|
||||
caption_entities (List[:class:`telegram.MessageEntity`]): Optional. For Messages with a
|
||||
Caption. Special entities like usernames, URLs, bot commands, etc. that appear in the
|
||||
caption. See :attr:`Message.parse_caption_entity` and :attr:`parse_caption_entities`
|
||||
methods for how to use properly.
|
||||
audio (:class:`telegram.Audio`, optional): Message is an audio file, information
|
||||
about the file.
|
||||
document (:class:`telegram.Document`, optional): Message is a general file, information
|
||||
|
@ -196,6 +203,7 @@ class Message(TelegramObject):
|
|||
edit_date=None,
|
||||
text=None,
|
||||
entities=None,
|
||||
caption_entities=None,
|
||||
audio=None,
|
||||
document=None,
|
||||
game=None,
|
||||
|
@ -239,6 +247,7 @@ class Message(TelegramObject):
|
|||
self.edit_date = edit_date
|
||||
self.text = text
|
||||
self.entities = entities or list()
|
||||
self.caption_entities = caption_entities or list()
|
||||
self.audio = audio
|
||||
self.game = game
|
||||
self.document = document
|
||||
|
@ -289,6 +298,7 @@ class Message(TelegramObject):
|
|||
data['date'] = from_timestamp(data['date'])
|
||||
data['chat'] = Chat.de_json(data.get('chat'), bot)
|
||||
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
|
||||
data['caption_entities'] = MessageEntity.de_list(data.get('caption_entities'), bot)
|
||||
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
|
||||
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'), bot)
|
||||
data['forward_date'] = from_timestamp(data.get('forward_date'))
|
||||
|
@ -369,6 +379,8 @@ class Message(TelegramObject):
|
|||
data['photo'] = [p.to_dict() for p in self.photo]
|
||||
if self.entities:
|
||||
data['entities'] = [e.to_dict() for e in self.entities]
|
||||
if self.caption_entities:
|
||||
data['caption_entities'] = [e.to_dict() for e in self.caption_entities]
|
||||
if self.new_chat_photo:
|
||||
data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo]
|
||||
data['new_chat_member'] = data.pop('_new_chat_member', None)
|
||||
|
@ -683,7 +695,7 @@ class Message(TelegramObject):
|
|||
be an entity that belongs to this message.
|
||||
|
||||
Returns:
|
||||
str: The text of the given entity
|
||||
:obj:`str`: The text of the given entity
|
||||
|
||||
"""
|
||||
# Is it a narrow build, if so we don't need to convert
|
||||
|
@ -695,6 +707,31 @@ class Message(TelegramObject):
|
|||
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
def parse_caption_entity(self, entity):
|
||||
"""Returns the text from a given :class:`telegram.MessageEntity`.
|
||||
|
||||
Note:
|
||||
This method is present because Telegram calculates the offset and length in
|
||||
UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
|
||||
(That is, you can't just slice ``Message.caption`` with the offset and length.)
|
||||
|
||||
Args:
|
||||
entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
|
||||
be an entity that belongs to this message.
|
||||
|
||||
Returns:
|
||||
:obj:`str`: The text of the given entity
|
||||
|
||||
"""
|
||||
# Is it a narrow build, if so we don't need to convert
|
||||
if sys.maxunicode == 0xffff:
|
||||
return self.caption[entity.offset:entity.offset + entity.length]
|
||||
else:
|
||||
entity_text = self.caption.encode('utf-16-le')
|
||||
entity_text = entity_text[entity.offset * 2:(entity.offset + entity.length) * 2]
|
||||
|
||||
return entity_text.decode('utf-16-le')
|
||||
|
||||
def parse_entities(self, types=None):
|
||||
"""
|
||||
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
|
||||
|
@ -726,6 +763,37 @@ class Message(TelegramObject):
|
|||
for entity in self.entities if entity.type in types
|
||||
}
|
||||
|
||||
def parse_caption_entities(self, types=None):
|
||||
"""
|
||||
Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
|
||||
It contains entities from this message's caption filtered by their
|
||||
:attr:`telegram.MessageEntity.type` attribute as the key, and the text that each entity
|
||||
belongs to as the value of the :obj:`dict`.
|
||||
|
||||
Note:
|
||||
This method should always be used instead of the :attr:`caption_entities` attribute,
|
||||
since it calculates the correct substring from the message text based on UTF-16
|
||||
codepoints. See :attr:`parse_entity` for more info.
|
||||
|
||||
Args:
|
||||
types (List[:obj:`str`], optional): List of :class:`telegram.MessageEntity` types as
|
||||
strings. If the ``type`` attribute of an entity is contained in this list, it will
|
||||
be returned. Defaults to a list of all types. All types can be found as constants
|
||||
in :class:`telegram.MessageEntity`.
|
||||
|
||||
Returns:
|
||||
Dict[:class:`telegram.MessageEntity`, :obj:`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
|
||||
|
||||
return {
|
||||
entity: self.parse_caption_entity(entity)
|
||||
for entity in self.caption_entities if entity.type in types
|
||||
}
|
||||
|
||||
def _text_html(self, urled=False):
|
||||
entities = self.parse_entities()
|
||||
message_text = self.text
|
||||
|
|
|
@ -107,15 +107,6 @@ class TestBot(object):
|
|||
# send_photo, send_audio, send_document, send_sticker, send_video, send_voice
|
||||
# and send_video_note are tested in their respective test modules. No need to duplicate here.
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_location(self, bot, chat_id):
|
||||
message = bot.send_location(chat_id=chat_id, latitude=-23.691288, longitude=-46.788279)
|
||||
|
||||
assert message.location
|
||||
assert message.location.longitude == -46.788279
|
||||
assert message.location.latitude == -23.691288
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_venue(self, bot, chat_id):
|
||||
|
@ -368,6 +359,14 @@ class TestBot(object):
|
|||
assert chat_member.status == 'administrator'
|
||||
assert chat_member.user.username == 'EchteEldin'
|
||||
|
||||
@pytest.mark.skip(reason="Not implemented yet.")
|
||||
def test_set_chat_sticker_set(self):
|
||||
pass
|
||||
|
||||
@pytest.mark.skip(reason="Not implemented yet.")
|
||||
def test_delete_chat_sticker_set(self):
|
||||
pass
|
||||
|
||||
@pytest.mark.skipif(os.getenv('APPVEYOR'), reason='No game made for Appveyor bot (yet)')
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
|
|
@ -27,7 +27,8 @@ from telegram import User
|
|||
def chat(bot):
|
||||
return Chat(TestChat.id, TestChat.title, TestChat.type,
|
||||
all_members_are_administrators=TestChat.all_members_are_administrators,
|
||||
bot=bot)
|
||||
bot=bot, sticker_set_name=TestChat.sticker_set_name,
|
||||
can_set_sticker_set=TestChat.can_set_sticker_set)
|
||||
|
||||
|
||||
class TestChat(object):
|
||||
|
@ -35,13 +36,17 @@ class TestChat(object):
|
|||
title = 'ToledosPalaceBot - Group'
|
||||
type = 'group'
|
||||
all_members_are_administrators = False
|
||||
sticker_set_name = 'stickers'
|
||||
can_set_sticker_set = False
|
||||
|
||||
def test_de_json(self, bot):
|
||||
json_dict = {
|
||||
'id': TestChat.id,
|
||||
'title': TestChat.title,
|
||||
'type': TestChat.type,
|
||||
'all_members_are_administrators': TestChat.all_members_are_administrators
|
||||
'id': self.id,
|
||||
'title': self.title,
|
||||
'type': self.type,
|
||||
'all_members_are_administrators': self.all_members_are_administrators,
|
||||
'sticker_set_name': self.sticker_set_name,
|
||||
'can_set_sticker_set': self.can_set_sticker_set
|
||||
}
|
||||
chat = Chat.de_json(json_dict, bot)
|
||||
|
||||
|
@ -49,6 +54,8 @@ class TestChat(object):
|
|||
assert chat.title == self.title
|
||||
assert chat.type == self.type
|
||||
assert chat.all_members_are_administrators == self.all_members_are_administrators
|
||||
assert chat.sticker_set_name == self.sticker_set_name
|
||||
assert chat.can_set_sticker_set == self.can_set_sticker_set
|
||||
|
||||
def test_to_dict(self, chat):
|
||||
chat_dict = chat.to_dict()
|
||||
|
|
|
@ -29,6 +29,7 @@ def inline_query_result_location():
|
|||
TestInlineQueryResultLocation.latitude,
|
||||
TestInlineQueryResultLocation.longitude,
|
||||
TestInlineQueryResultLocation.title,
|
||||
live_period=TestInlineQueryResultLocation.live_period,
|
||||
thumb_url=TestInlineQueryResultLocation.thumb_url,
|
||||
thumb_width=TestInlineQueryResultLocation.thumb_width,
|
||||
thumb_height=TestInlineQueryResultLocation.thumb_height,
|
||||
|
@ -42,6 +43,7 @@ class TestInlineQueryResultLocation(object):
|
|||
latitude = 0.0
|
||||
longitude = 1.0
|
||||
title = 'title'
|
||||
live_period = 70
|
||||
thumb_url = 'thumb url'
|
||||
thumb_width = 10
|
||||
thumb_height = 15
|
||||
|
@ -54,6 +56,7 @@ class TestInlineQueryResultLocation(object):
|
|||
assert inline_query_result_location.latitude == self.latitude
|
||||
assert inline_query_result_location.longitude == self.longitude
|
||||
assert inline_query_result_location.title == self.title
|
||||
assert inline_query_result_location.live_period == self.live_period
|
||||
assert inline_query_result_location.thumb_url == self.thumb_url
|
||||
assert inline_query_result_location.thumb_width == self.thumb_width
|
||||
assert inline_query_result_location.thumb_height == self.thumb_height
|
||||
|
@ -72,6 +75,8 @@ class TestInlineQueryResultLocation(object):
|
|||
assert inline_query_result_location_dict['longitude'] == \
|
||||
inline_query_result_location.longitude
|
||||
assert inline_query_result_location_dict['title'] == inline_query_result_location.title
|
||||
assert inline_query_result_location_dict[
|
||||
'live_period'] == inline_query_result_location.live_period
|
||||
assert inline_query_result_location_dict['thumb_url'] == \
|
||||
inline_query_result_location.thumb_url
|
||||
assert inline_query_result_location_dict['thumb_width'] == \
|
||||
|
|
|
@ -25,16 +25,19 @@ from telegram import InputLocationMessageContent
|
|||
@pytest.fixture(scope='class')
|
||||
def input_location_message_content():
|
||||
return InputLocationMessageContent(TestInputLocationMessageContent.latitude,
|
||||
TestInputLocationMessageContent.longitude)
|
||||
TestInputLocationMessageContent.longitude,
|
||||
live_period=TestInputLocationMessageContent.live_period)
|
||||
|
||||
|
||||
class TestInputLocationMessageContent(object):
|
||||
latitude = -23.691288
|
||||
longitude = -46.788279
|
||||
live_period = 80
|
||||
|
||||
def test_expected_values(self, input_location_message_content):
|
||||
assert input_location_message_content.longitude == self.longitude
|
||||
assert input_location_message_content.latitude == self.latitude
|
||||
assert input_location_message_content.live_period == self.live_period
|
||||
|
||||
def test_to_dict(self, input_location_message_content):
|
||||
input_location_message_content_dict = input_location_message_content.to_dict()
|
||||
|
@ -44,3 +47,5 @@ class TestInputLocationMessageContent(object):
|
|||
input_location_message_content.latitude
|
||||
assert input_location_message_content_dict['longitude'] == \
|
||||
input_location_message_content.longitude
|
||||
assert input_location_message_content_dict[
|
||||
'live_period'] == input_location_message_content.live_period
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
|
||||
import pytest
|
||||
from flaky import flaky
|
||||
|
||||
from telegram import Location
|
||||
from telegram.error import BadRequest
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
|
@ -39,6 +41,46 @@ class TestLocation(object):
|
|||
assert location.latitude == self.latitude
|
||||
assert location.longitude == self.longitude
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_live_location(self, bot, chat_id):
|
||||
message = bot.send_location(chat_id=chat_id, latitude=52.223880, longitude=5.166146,
|
||||
live_period=80)
|
||||
assert message.location
|
||||
assert message.location.latitude == 52.223880
|
||||
assert message.location.longitude == 5.166146
|
||||
|
||||
message2 = bot.edit_message_live_location(message.chat_id, message.message_id,
|
||||
latitude=52.223098, longitude=5.164306)
|
||||
|
||||
assert message2.location.latitude == 52.223098
|
||||
assert message2.location.longitude == 5.164306
|
||||
|
||||
bot.stop_message_live_location(message.chat_id, message.message_id)
|
||||
with pytest.raises(BadRequest, match="Message can't be edited"):
|
||||
bot.edit_message_live_location(message.chat_id, message.message_id, latitude=52.223880,
|
||||
longitude=5.164306)
|
||||
|
||||
# TODO: Needs improvement with in inline sent live location.
|
||||
def test_edit_live_inline_message(self, monkeypatch, bot, location):
|
||||
def test(_, url, data, **kwargs):
|
||||
lat = data['latitude'] == location.latitude
|
||||
lon = data['longitude'] == location.longitude
|
||||
id = data['inline_message_id'] == 1234
|
||||
return lat and lon and id
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
assert bot.edit_message_live_location(inline_message_id=1234, location=location)
|
||||
|
||||
# TODO: Needs improvement with in inline sent live location.
|
||||
def test_stop_live_inline_message(self, monkeypatch, bot):
|
||||
def test(_, url, data, **kwargs):
|
||||
id = data['inline_message_id'] == 1234
|
||||
return id
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
assert bot.stop_message_live_location(inline_message_id=1234)
|
||||
|
||||
def test_send_with_location(self, monkeypatch, bot, chat_id, location):
|
||||
def test(_, url, data, **kwargs):
|
||||
lat = data['latitude'] == location.latitude
|
||||
|
@ -48,10 +90,32 @@ class TestLocation(object):
|
|||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
assert bot.send_location(location=location, chat_id=chat_id)
|
||||
|
||||
def test_edit_live_location_with_location(self, monkeypatch, bot, location):
|
||||
def test(_, url, data, **kwargs):
|
||||
lat = data['latitude'] == location.latitude
|
||||
lon = data['longitude'] == location.longitude
|
||||
return lat and lon
|
||||
|
||||
monkeypatch.setattr('telegram.utils.request.Request.post', test)
|
||||
assert bot.edit_message_live_location(None, None, location=location)
|
||||
|
||||
def test_send_location_without_required(self, bot, chat_id):
|
||||
with pytest.raises(ValueError, match='Either location or latitude and longitude'):
|
||||
bot.send_location(chat_id=chat_id)
|
||||
|
||||
def test_edit_location_without_required(self, bot):
|
||||
with pytest.raises(ValueError, match='Either location or latitude and longitude'):
|
||||
bot.edit_message_live_location(chat_id=2, message_id=3)
|
||||
|
||||
def test_send_location_with_all_args(self, bot, location):
|
||||
with pytest.raises(ValueError, match='Not both'):
|
||||
bot.send_location(chat_id=1, latitude=2.5, longitude=4.6, location=location)
|
||||
|
||||
def test_edit_location_with_all_args(self, bot, location):
|
||||
with pytest.raises(ValueError, match='Not both'):
|
||||
bot.edit_message_live_location(chat_id=1, message_id=7, latitude=2.5, longitude=4.6,
|
||||
location=location)
|
||||
|
||||
def test_to_dict(self, location):
|
||||
location_dict = location.to_dict()
|
||||
|
||||
|
|
|
@ -40,9 +40,12 @@ def message(bot):
|
|||
'forward_date': datetime.now()},
|
||||
{'reply_to_message': Message(50, None, None, None)},
|
||||
{'edit_date': datetime.now()},
|
||||
{'test': 'a text message',
|
||||
{'text': 'a text message',
|
||||
'enitites': [MessageEntity('bold', 10, 4),
|
||||
MessageEntity('italic', 16, 7)]},
|
||||
{'caption': 'A message caption',
|
||||
'caption_entities': [MessageEntity('bold', 1, 1),
|
||||
MessageEntity('text_link', 4, 3)]},
|
||||
{'audio': Audio('audio_id', 12),
|
||||
'caption': 'audio_file'},
|
||||
{'document': Document('document_id'),
|
||||
|
@ -78,12 +81,13 @@ def message(bot):
|
|||
{'forward_signature': 'some_forward_sign'},
|
||||
{'author_signature': 'some_author_sign'}
|
||||
],
|
||||
ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text', 'audio',
|
||||
'document', 'game', 'photo', 'sticker', 'video', 'voice', 'video_note',
|
||||
'new_members', 'contact', 'location', 'venue', 'left_member', 'new_title',
|
||||
'new_photo', 'delete_photo', 'group_created', 'supergroup_created',
|
||||
'channel_created', 'migrated_to', 'migrated_from', 'pinned', 'invoice',
|
||||
'successful_payment', 'forward_signature', 'author_signature'])
|
||||
ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text',
|
||||
'caption_entities', 'audio', 'document', 'game', 'photo', 'sticker', 'video',
|
||||
'voice', 'video_note', 'new_members', 'contact', 'location', 'venue',
|
||||
'left_member', 'new_title', 'new_photo', 'delete_photo', 'group_created',
|
||||
'supergroup_created', 'channel_created', 'migrated_to', 'migrated_from',
|
||||
'pinned', 'invoice', 'successful_payment', 'forward_signature',
|
||||
'author_signature'])
|
||||
def message_params(bot, request):
|
||||
return Message(message_id=TestMessage.id,
|
||||
from_user=TestMessage.from_user,
|
||||
|
@ -127,6 +131,14 @@ class TestMessage(object):
|
|||
message = Message(1, self.from_user, self.date, self.chat, text=text, entities=[entity])
|
||||
assert message.parse_entity(entity) == 'http://google.com'
|
||||
|
||||
def test_parse_caption_entity(self):
|
||||
caption = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
|
||||
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
|
||||
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
|
||||
message = Message(1, self.from_user, self.date, self.chat, caption=caption,
|
||||
caption_entities=[entity])
|
||||
assert message.parse_caption_entity(entity) == 'http://google.com'
|
||||
|
||||
def test_parse_entities(self):
|
||||
text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
|
||||
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
|
||||
|
@ -137,6 +149,16 @@ class TestMessage(object):
|
|||
assert message.parse_entities(MessageEntity.URL) == {entity: 'http://google.com'}
|
||||
assert message.parse_entities() == {entity: 'http://google.com', entity_2: 'h'}
|
||||
|
||||
def test_parse_caption_entities(self):
|
||||
text = (b'\\U0001f469\\u200d\\U0001f469\\u200d\\U0001f467'
|
||||
b'\\u200d\\U0001f467\\U0001f431http://google.com').decode('unicode-escape')
|
||||
entity = MessageEntity(type=MessageEntity.URL, offset=13, length=17)
|
||||
entity_2 = MessageEntity(type=MessageEntity.BOLD, offset=13, length=1)
|
||||
message = Message(1, self.from_user, self.date, self.chat,
|
||||
caption=text, caption_entities=[entity_2, entity])
|
||||
assert message.parse_caption_entities(MessageEntity.URL) == {entity: 'http://google.com'}
|
||||
assert message.parse_caption_entities() == {entity: 'http://google.com', entity_2: 'h'}
|
||||
|
||||
def test_text_html_simple(self):
|
||||
test_html_string = ('Test for <<b>bold</b>, <i>ita_lic</i>, <code>code</code>, '
|
||||
'<a href="http://github.com/">links</a> and <pre>pre</pre>. '
|
||||
|
@ -201,7 +223,6 @@ class TestMessage(object):
|
|||
item = None
|
||||
assert message_params.effective_attachment == item
|
||||
|
||||
|
||||
def test_reply_text(self, monkeypatch, message):
|
||||
def test(*args, **kwargs):
|
||||
id = args[1] == message.chat_id
|
||||
|
|
|
@ -33,6 +33,8 @@ import telegram
|
|||
IGNORED_OBJECTS = ('ResponseParameters', 'CallbackGame')
|
||||
IGNORED_PARAMETERS = {'self', 'args', 'kwargs', 'read_latency', 'network_delay', 'timeout', 'bot',
|
||||
'new_chat_member'}
|
||||
|
||||
|
||||
# TODO: New_chat_member is still in our lib but already removed from TG's docs.
|
||||
|
||||
|
||||
|
@ -49,10 +51,10 @@ def parse_table(h4):
|
|||
if not table:
|
||||
return []
|
||||
head = [td.text for td in table.tr.find_all('td')]
|
||||
row = namedtuple('{}TableRow'.format(h4.text), ','.join(head))
|
||||
# row = namedtuple('{}TableRow'.format(h4.text), ','.join(head))
|
||||
t = []
|
||||
for tr in table.find_all('tr')[1:]:
|
||||
t.append(row(*[td.text for td in tr.find_all('td')]))
|
||||
t.append([td.text for td in tr.find_all('td')])
|
||||
return t
|
||||
|
||||
|
||||
|
@ -66,12 +68,12 @@ def check_method(h4):
|
|||
|
||||
checked = []
|
||||
for parameter in table:
|
||||
param = sig.parameters.get(parameter.Parameters)
|
||||
assert param is not None, "Parameter {} not found in {}".format(parameter.Parameters,
|
||||
param = sig.parameters.get(parameter[0])
|
||||
assert param is not None, "Parameter {} not found in {}".format(parameter[0],
|
||||
method.__name__)
|
||||
# TODO: Check type via docstring
|
||||
# TODO: Check if optional or required
|
||||
checked.append(parameter.Parameters)
|
||||
checked.append(parameter[0])
|
||||
|
||||
ignored = IGNORED_PARAMETERS.copy()
|
||||
if name == 'getUpdates':
|
||||
|
@ -82,7 +84,7 @@ def check_method(h4):
|
|||
ignored |= {'edit_message'} # TODO: Now deprecated, so no longer in telegrams docs
|
||||
elif name == 'sendContact':
|
||||
ignored |= {'contact'} # Added for ease of use
|
||||
elif name == 'sendLocation':
|
||||
elif name in ['sendLocation', 'editMessageLiveLocation']:
|
||||
ignored |= {'location'} # Added for ease of use
|
||||
elif name == 'sendVenue':
|
||||
ignored |= {'venue'} # Added for ease of use
|
||||
|
@ -100,7 +102,7 @@ def check_object(h4):
|
|||
|
||||
checked = []
|
||||
for parameter in table:
|
||||
field = parameter.Field
|
||||
field = parameter[0]
|
||||
if field == 'from':
|
||||
field = 'from_user'
|
||||
elif name.startswith('InlineQueryResult') and field == 'type':
|
||||
|
|
Loading…
Reference in a new issue