send_* now accepts tg-objects (#742)

Fixes #731
This commit is contained in:
Eldinnie 2017-07-25 00:35:22 +02:00 committed by Noam Meltzer
parent 2d1028acb6
commit 8d1d38cc4c
14 changed files with 223 additions and 60 deletions

View file

@ -13,6 +13,7 @@ Changes
- Remove deprecated ``Botan`` import from ``utils`` (``Botan`` is still available through ``contrib``).
- Remove deprecated ``ReplyKeyboardHide``.
- Remove deprecated ``edit_message`` argument of `bot.set_game_score``.
- Add the possibility to add objects as arguments to send_* methods.
**2017-06-18**

View file

@ -26,7 +26,9 @@ import warnings
from datetime import datetime
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet)
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
PhotoSize, Audio, Document, Sticker, Video, Voice, VideoNote, Location,
Venue, Contact)
from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request
@ -361,10 +363,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
photo (:obj:`str` | `filelike object`): Photo to send. Pass a file_id as String to send
a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a
String for Telegram to get a photo from the Internet, or upload a new photo using
multipart/form-data.
photo (:obj:`str` | `filelike object` | :class:`telegram.PhotoSize`): Photo to send.
Pass a file_id as String to send a photo that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the
Internet, or upload a new photo using multipart/form-data. Lastly you can pass
an existing :class:`telegram.PhotoSize` object to send.
caption (:obj:`str`, optional): Photo caption (may also be used when resending photos
by file_id), 0-200 characters.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
@ -386,6 +389,9 @@ class Bot(TelegramObject):
url = '{0}/sendPhoto'.format(self.base_url)
if isinstance(photo, PhotoSize):
photo = photo.file_id
data = {'chat_id': chat_id, 'photo': photo}
if caption:
@ -422,10 +428,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
audio (:obj:`str` | `filelike object`): Audio file to send. Pass a file_id as String to
send an audio file that exists on the Telegram servers (recommended), pass an HTTP
URL as a String for Telegram to get an audio file from the Internet, or upload a
new one using multipart/form-data.
audio (:obj:`str` | `filelike object` | :class:`telegram.Audio`): Audio file to send.
Pass a file_id as String to send an audio file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an audio file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Audio` object to send.
caption (:obj:`str`, optional): Audio caption, 0-200 characters.
duration (:obj:`int`, optional): Duration of sent audio in seconds.
performer (:obj:`str`, optional): Performer.
@ -449,6 +456,9 @@ class Bot(TelegramObject):
url = '{0}/sendAudio'.format(self.base_url)
if isinstance(audio, Audio):
audio = audio.file_id
data = {'chat_id': chat_id, 'audio': audio}
if duration:
@ -484,10 +494,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
document (:obj:`str` | `filelike object`): File to send. Pass a file_id as String to
send a file that exists on the Telegram servers (recommended), pass an HTTP URL as
a String for Telegram to get a file from the Internet, or upload a new one using
multipart/form-data.
document (:obj:`str` | `filelike object` | :class:`telegram.Document`): File to send.
Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a file from the
Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Document` object to send.
filename (:obj:`str`, optional): File name that shows in telegram message (it is useful
when you send file generated by temp module, for example). Undocumented.
caption (:obj:`str`, optional): Document caption (may also be used when resending
@ -511,6 +522,9 @@ class Bot(TelegramObject):
url = '{0}/sendDocument'.format(self.base_url)
if isinstance(document, Document):
document = document.file_id
data = {'chat_id': chat_id, 'document': document}
if filename:
@ -540,10 +554,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
sticker (:obj:`str` | `filelike object`): Sticker to send. Pass a file_id as String to
send a file that exists on the Telegram servers (recommended), pass an HTTP URL as
a String for Telegram to get a .webp file from the Internet, or upload a new one
using multipart/form-data.
sticker (:obj:`str` | `filelike object` :class:`telegram.Sticker`): Sticker to send.
Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a .webp file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Sticker` object to send.
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
@ -563,6 +578,9 @@ class Bot(TelegramObject):
url = '{0}/sendSticker'.format(self.base_url)
if isinstance(sticker, Sticker):
sticker = sticker.file_id
data = {'chat_id': chat_id, 'sticker': sticker}
return url, data
@ -592,10 +610,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
video (:obj:`str` | `filelike object`): Video file to send. Pass a file_id as String to
send an video file that exists on the Telegram servers (recommended), pass an HTTP
URL as a String for Telegram to get an video file from the Internet, or upload a
new one using multipart/form-data.
video (:obj:`str` | `filelike object` | :class:`telegram.Video`): Video file to send.
Pass a file_id as String to send an video file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an video file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Video` object to send.
duration (:obj:`int`, optional): Duration of sent video in seconds.
width (Optional[int)): Video width.
height (:obj:`int`, optional): Video height.
@ -620,6 +639,9 @@ class Bot(TelegramObject):
url = '{0}/sendVideo'.format(self.base_url)
if isinstance(video, Video):
video = video.file_id
data = {'chat_id': chat_id, 'video': video}
if duration:
@ -657,10 +679,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
voice (:obj:`str` | `filelike object`): Voice file to send. Pass a file_id as String
to send an voice file that exists on the Telegram servers (recommended), pass an
HTTP URL as a String for Telegram to get an voice file from the Internet, or upload
a new one using multipart/form-data.
voice (:obj:`str` | `filelike object` | :class:`telegram.Voice`): Voice file to send.
Pass a file_id as String to send an voice file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get an voice file from
the Internet, or upload a new one using multipart/form-data. Lastly you can pass
an existing :class:`telegram.Voice` object to send.
caption (:obj:`str`, optional): Voice message caption, 0-200 characters.
duration (:obj:`int`, optional): Duration of the voice message in seconds.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
@ -682,6 +705,9 @@ class Bot(TelegramObject):
url = '{0}/sendVoice'.format(self.base_url)
if isinstance(voice, Voice):
voice = voice.file_id
data = {'chat_id': chat_id, 'voice': voice}
if duration:
@ -713,10 +739,11 @@ class Bot(TelegramObject):
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
video_note (:obj:`str` | `filelike object`): Video note to send. Pass a file_id as
String to send a video note that exists on the Telegram servers (recommended) or
upload a new video using multipart/form-data. Sending video notes by a URL is
currently unsupported.
video_note (:obj:`str` | `filelike object` | :class:`telegram.VideoNote`): Video note
to send. Pass a file_id as String to send a video note that exists on the Telegram
servers (recommended) or upload a new video using multipart/form-data. Or you can
pass an existing :class:`telegram.VideoNote` object to send. Sending video notes by
a URL is currently unsupported.
duration (:obj:`int`, optional): Duration of sent video in seconds.
length (:obj:`int`, optional): Video width and height
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
@ -738,6 +765,9 @@ class Bot(TelegramObject):
url = '{0}/sendVideoNote'.format(self.base_url)
if isinstance(video_note, VideoNote):
video_note = video_note.file_id
data = {'chat_id': chat_id, 'video_note': video_note}
if duration is not None:
@ -751,21 +781,26 @@ class Bot(TelegramObject):
@message
def send_location(self,
chat_id,
latitude,
longitude,
latitude=None,
longitude=None,
disable_notification=False,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
location=None,
**kwargs):
"""
Use this method to send point on the map.
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).
latitude (:obj:`float`): Latitude of location.
longitude (:obj:`float`): Longitude of location.
latitude (:obj:`float`, optional): Latitude of location.
longitude (:obj:`float`, optional): Longitude of location.
location (:class:`telegram.Location`, optional): The location to send.
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
@ -787,6 +822,14 @@ class Bot(TelegramObject):
url = '{0}/sendLocation'.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 isinstance(location, Location):
latitude = location.latitude
longitude = location.longitude
data = {'chat_id': chat_id, 'latitude': latitude, 'longitude': longitude}
return url, data
@ -795,27 +838,33 @@ class Bot(TelegramObject):
@message
def send_venue(self,
chat_id,
latitude,
longitude,
title,
address,
latitude=None,
longitude=None,
title=None,
address=None,
foursquare_id=None,
disable_notification=False,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
venue=None,
**kwargs):
"""
Use this method to send information about a venue.
Note:
you can either supply :obj:`venue`, or :obj:`latitude`, :obj:`longitude`,
:obj:`title` and :obj:`address` and optionally :obj:`foursquare_id`.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
latitude (:obj:`float`): Latitude of venue.
longitude (:obj:`float`): Longitude of venue.
title (:obj:`str`): Name of the venue.
address (:obj:`str`): Address of the venue.
latitude (:obj:`float`, optional): Latitude of venue.
longitude (:obj:`float`, optional): Longitude of venue.
title (:obj:`str`, optional): Name of the venue.
address (:obj:`str`, optional): Address of the venue.
foursquare_id (:obj:`str`, optional): Foursquare identifier of the venue.
venue (:class:`telegram.Venue`, optional): The venue to send.
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
@ -837,6 +886,17 @@ class Bot(TelegramObject):
url = '{0}/sendVenue'.format(self.base_url)
if not (venue or all([latitude, longitude, address, title])):
raise ValueError("Either venue or latitude, longitude, address and title must be"
"passed as arguments.")
if isinstance(venue, Venue):
latitude = venue.location.latitude
longitude = venue.location.longitude
address = venue.address
title = venue.title
foursquare_id = venue.foursquare_id
data = {
'chat_id': chat_id,
'latitude': latitude,
@ -854,23 +914,29 @@ class Bot(TelegramObject):
@message
def send_contact(self,
chat_id,
phone_number,
first_name,
phone_number=None,
first_name=None,
last_name=None,
disable_notification=False,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
contact=None,
**kwargs):
"""
Use this method to send phone contacts.
Note:
You can either supply :obj:`contact` or :obj:`phone_number` and :obj:`first_name`
with optionally :obj:`last_name`.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
phone_number (:obj:`str`): Contact's phone number.
first_name (:obj:`str`): Contact's first name.
phone_number (:obj:`str`, optional): Contact's phone number.
first_name (:obj:`str`, optional): Contact's first name.
last_name (:obj:`str`, optional): Contact's last name.
contact (:class:`telegram.Contact`, optional): The contact to send.
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
@ -892,6 +958,15 @@ class Bot(TelegramObject):
url = '{0}/sendContact'.format(self.base_url)
if (not contact) and (not all([phone_number, first_name])):
raise ValueError("Either contact or phone_number and first_name must be passed as"
"arguments.")
if isinstance(contact, Contact):
phone_number = contact.phone_number
first_name = contact.first_name
last_name = contact.last_name
data = {'chat_id': chat_id, 'phone_number': phone_number, 'first_name': first_name}
if last_name:

View file

@ -149,7 +149,7 @@ class MaskPosition(TelegramObject):
size, from left to right.
y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face
size, from top to bottom.
zoom (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
Notes:
:attr:`type` should be one of the following: `forehead`, `eyes`, `mouth` or `chin`. You can
@ -163,7 +163,7 @@ class MaskPosition(TelegramObject):
y_shift (:obj:`float`): Shift by Y-axis measured in heights of the mask scaled to the face
size, from top to bottom. For example, 1.0 will place the mask just below the default
mask position.
zoom (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
scale (:obj:`float`): Mask scaling coefficient. For example, 2.0 means double size.
"""
FOREHEAD = 'forehead'
@ -175,11 +175,11 @@ class MaskPosition(TelegramObject):
CHIN = 'chin'
""":obj:`str`: 'chin'"""
def __init__(self, point, x_shift, y_shift, zoom, **kwargs):
def __init__(self, point, x_shift, y_shift, scale, **kwargs):
self.point = point
self.x_shift = x_shift
self.y_shift = y_shift
self.zoom = zoom
self.scale = scale
@classmethod
def de_json(cls, data, bot):

View file

@ -155,6 +155,14 @@ class AudioTest(BaseTest, unittest.TestCase):
self.assertEqual(audio, self.audio)
@flaky(3, 1)
@timeout(10)
def test_send_audio_with_audio(self):
message = self._bot.send_audio(audio=self.audio, chat_id=self._chat_id)
audio = message.audio
self.assertEqual(audio, self.audio)
def test_audio_de_json(self):
audio = telegram.Audio.de_json(self.json_dict, self._bot)

View file

@ -53,6 +53,13 @@ class ContactTest(BaseTest, unittest.TestCase):
self.assertEqual(contact.last_name, self.last_name)
self.assertEqual(contact.user_id, self.user_id)
def test_send_contact_with_contact(self):
con = telegram.Contact.de_json(self.json_dict, self._bot)
message = self._bot.send_contact(contact=con, chat_id=self._chat_id)
contact = message.contact
self.assertEqual(contact, con)
def test_contact_to_json(self):
contact = telegram.Contact.de_json(self.json_dict, self._bot)

View file

@ -132,6 +132,15 @@ class DocumentTest(BaseTest, unittest.TestCase):
self.assertEqual(document, self.document)
@flaky(3, 1)
@timeout(10)
def test_send_document_with_document(self):
message = self._bot.send_document(document=self.document, chat_id=self._chat_id)
document = message.document
self.assertEqual(document, self.document)
def test_document_de_json(self):
document = telegram.Document.de_json(self.json_dict, self._bot)

View file

@ -55,6 +55,13 @@ class LocationTest(BaseTest, unittest.TestCase):
self.assertEqual(location.latitude, self.latitude)
self.assertEqual(location.longitude, self.longitude)
def test_send_location_with_location(self):
loc = telegram.Location(longitude=self.longitude, latitude=self.latitude)
message = self._bot.send_location(location=loc, chat_id=self._chat_id)
location = message.location
self.assertEqual(location, loc)
def test_location_de_json(self):
location = telegram.Location.de_json(self.json_dict, self._bot)
@ -78,9 +85,8 @@ class LocationTest(BaseTest, unittest.TestCase):
json_dict['latitude'] = ''
json_dict['longitude'] = ''
self.assertRaises(telegram.TelegramError,
lambda: self._bot.sendLocation(chat_id=self._chat_id,
**json_dict))
with self.assertRaises(TypeError):
self._bot.sendLocation(chat_id=self._chat_id, **json_dict)
def test_error_location_without_required_args(self):
json_dict = self.json_dict
@ -88,9 +94,8 @@ class LocationTest(BaseTest, unittest.TestCase):
del (json_dict['latitude'])
del (json_dict['longitude'])
self.assertRaises(TypeError,
lambda: self._bot.sendLocation(chat_id=self._chat_id,
**json_dict))
with self.assertRaises(ValueError):
self._bot.sendLocation(chat_id=self._chat_id, **json_dict)
@flaky(3, 1)
def test_reply_location(self):

View file

@ -63,6 +63,12 @@ def check_method(h4):
ignored |= {'filename'} # Undocumented
elif name == 'setGameScore':
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':
ignored |= {'location'} # Added for ease of use
elif name == 'sendVenue':
ignored |= {'venue'} # Added for ease of use
logger.debug((sig.parameters.keys(), checked, ignored,
sig.parameters.keys() - checked - ignored))

View file

@ -181,7 +181,8 @@ class PhotoTest(BaseTest, unittest.TestCase):
@flaky(3, 1)
@timeout(10)
def test_silent_send_photo(self):
message = self._bot.sendPhoto(photo=self.photo_file, chat_id=self._chat_id, disable_notification=True)
message = self._bot.sendPhoto(photo=self.photo_file, chat_id=self._chat_id,
disable_notification=True)
thumb, photo = message.photo
self.assertIsInstance(thumb, telegram.PhotoSize)
@ -192,6 +193,15 @@ class PhotoTest(BaseTest, unittest.TestCase):
self.assertIsInstance(photo.file_id, str)
self.assertNotEqual(photo.file_id, '')
@flaky(3, 1)
@timeout(10)
def test_send_photo_with_photosize(self):
message = self._bot.send_photo(photo=self.photo, chat_id=self._chat_id)
thumb, photo = message.photo
self.assertEqual(photo, self.photo)
self.assertEqual(thumb, self.thumb)
@flaky(3, 1)
@timeout(10)
def test_send_photo_resend(self):

View file

@ -152,6 +152,15 @@ class StickerTest(BaseTest, unittest.TestCase):
self.assertEqual(sticker.emoji, self.emoji)
self.assertEqual(sticker.file_size, self.sticker.file_size)
@flaky(3, 1)
@timeout(10)
def test_send_sticker_with_sticker(self):
message = self._bot.send_sticker(sticker=self.sticker, chat_id=self._chat_id)
sticker = message.sticker
self.assertEqual(sticker, self.sticker)
def test_sticker_to_json(self):
self.assertTrue(self.is_json(self.sticker.to_json()))
@ -288,13 +297,13 @@ class TestMaskPosition(BaseTest, unittest.TestCase):
self.point = telegram.MaskPosition.EYES
self.x_shift = -1
self.y_shift = 1
self.zoom = 2
self.scale = 2
self.json_dict = {
'point': self.point,
'x_shift': self.x_shift,
'y_shift': self.y_shift,
'zoom': self.zoom
'scale': self.scale
}
def test_mask_position_de_json(self):
@ -303,7 +312,7 @@ class TestMaskPosition(BaseTest, unittest.TestCase):
self.assertEqual(mask_position.point, self.point)
self.assertEqual(mask_position.x_shift, self.x_shift)
self.assertEqual(mask_position.y_shift, self.y_shift)
self.assertEqual(mask_position.zoom, self.zoom)
self.assertEqual(mask_position.scale, self.scale)
def test_mask_positiont_to_json(self):
mask_position = telegram.MaskPosition.de_json(self.json_dict, self._bot)

View file

@ -33,7 +33,7 @@ class VenueTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Telegram Venue."""
def setUp(self):
self.location = telegram.Location(longitude=1., latitude=0.)
self.location = telegram.Location(longitude=-46.788279, latitude=-23.691288)
self.title = 'title'
self._address = '_address'
self.foursquare_id = 'foursquare id'
@ -53,6 +53,13 @@ class VenueTest(BaseTest, unittest.TestCase):
self.assertEqual(sticker.address, self._address)
self.assertEqual(sticker.foursquare_id, self.foursquare_id)
def test_send_venue_with_venue(self):
ven = telegram.Venue.de_json(self.json_dict, self._bot)
message = self._bot.send_venue(chat_id=self._chat_id, venue=ven)
venue = message.venue
self.assertEqual(venue, ven)
def test_venue_to_json(self):
sticker = telegram.Venue.de_json(self.json_dict, self._bot)

View file

@ -144,6 +144,15 @@ class VideoTest(BaseTest, unittest.TestCase):
self.assertEqual(video.thumb, self.video.thumb)
self.assertEqual(video.mime_type, self.video.mime_type)
@flaky(3, 1)
@timeout(10)
def test_send_video_with_video(self):
message = self._bot.send_video(video=self.video, chat_id=self._chat_id)
video = message.video
self.assertEqual(video, self.video)
def test_video_de_json(self):
video = telegram.Video.de_json(self.json_dict, self._bot)

View file

@ -114,6 +114,14 @@ class VideoNoteTest(BaseTest, unittest.TestCase):
self.assertEqual(videonote.thumb, self.videonote.thumb)
self.assertEqual(videonote.file_size, self.videonote.file_size)
@flaky(3, 1)
@timeout(10)
def test_send_video_note_with_video_note(self):
message = self._bot.send_video_note(video_note=self.videonote, chat_id=self._chat_id)
video_note = message.video_note
self.assertEqual(video_note, self.videonote)
def test_videonote_de_json(self):
videonote = telegram.VideoNote.de_json(self.json_dict, self._bot)

View file

@ -149,6 +149,15 @@ class VoiceTest(BaseTest, unittest.TestCase):
self.assertEqual(voice.duration, self.voice.duration)
self.assertEqual(voice.mime_type, self.voice.mime_type)
@flaky(3, 1)
@timeout(10)
def test_send_voice_with_voice(self):
message = self._bot.send_voice(voice=self.voice, chat_id=self._chat_id)
voice = message.voice
self.assertEqual(voice, self.voice)
def test_voice_de_json(self):
voice = telegram.Voice.de_json(self.json_dict, self._bot)