Allow Passing Custom Filename For All Media (#2249)

* Add filename arg to send_media methods and InputMedia*

* Tests
This commit is contained in:
Bibo-Joshi 2020-12-16 14:28:53 +01:00 committed by GitHub
parent e0dbb99b08
commit 786762bb73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 176 additions and 17 deletions

View file

@ -575,6 +575,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
) -> Optional[Message]:
"""Use this method to send photos.
@ -591,6 +592,9 @@ class Bot(TelegramObject):
(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.
filename (:obj:`str`, optional): Custom file name for the photo, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Photo caption (may also be used when resending photos
by file_id), 0-1024 characters after entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
@ -619,7 +623,10 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'photo': parse_file_input(photo, PhotoSize)}
data: JSONDict = {
'chat_id': chat_id,
'photo': parse_file_input(photo, PhotoSize, filename=filename),
}
if caption:
data['caption'] = caption
@ -657,6 +664,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
) -> Optional[Message]:
"""
Use this method to send audio files, if you want Telegram clients to display them in the
@ -680,6 +688,9 @@ class Bot(TelegramObject):
(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.
filename (:obj:`str`, optional): Custom file name for the audio, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Audio caption, 0-1024 characters after entities
parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
@ -717,7 +728,10 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'audio': parse_file_input(audio, Audio)}
data: JSONDict = {
'chat_id': chat_id,
'audio': parse_file_input(audio, Audio, filename=filename),
}
if duration:
data['duration'] = duration
@ -782,8 +796,9 @@ class Bot(TelegramObject):
(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.
filename (:obj:`str`, optional): Custom file name for the document, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Document caption (may also be used when resending
documents by file_id), 0-1024 characters after entities parsing.
disable_content_type_detection (:obj:`bool`, optional): Disables automatic server-side
@ -927,6 +942,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
) -> Optional[Message]:
"""
Use this method to send video files, Telegram clients support mp4 videos
@ -951,6 +967,9 @@ class Bot(TelegramObject):
(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.
filename (:obj:`str`, optional): Custom file name for the video, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
duration (:obj:`int`, optional): Duration of sent video in seconds.
width (:obj:`int`, optional): Video width.
height (:obj:`int`, optional): Video height.
@ -990,7 +1009,10 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'video': parse_file_input(video, Video)}
data: JSONDict = {
'chat_id': chat_id,
'video': parse_file_input(video, Video, filename=filename),
}
if duration:
data['duration'] = duration
@ -1034,6 +1056,7 @@ class Bot(TelegramObject):
thumb: FileInput = None,
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
filename: str = None,
) -> Optional[Message]:
"""
As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long.
@ -1055,6 +1078,9 @@ class Bot(TelegramObject):
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.
filename (:obj:`str`, optional): Custom file name for the video note, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
duration (:obj:`int`, optional): Duration of sent video in seconds.
length (:obj:`int`, optional): Video width and height, i.e. diameter of the video
message.
@ -1086,7 +1112,7 @@ class Bot(TelegramObject):
"""
data: JSONDict = {
'chat_id': chat_id,
'video_note': parse_file_input(video_note, VideoNote),
'video_note': parse_file_input(video_note, VideoNote, filename=filename),
}
if duration is not None:
@ -1125,6 +1151,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
) -> Optional[Message]:
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
@ -1145,6 +1172,9 @@ class Bot(TelegramObject):
servers (recommended), pass an HTTP URL as a String for Telegram to get an
animation from the Internet, or upload a new animation using multipart/form-data.
Lastly you can pass an existing :class:`telegram.Animation` object to send.
filename (:obj:`str`, optional): Custom file name for the animation, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
duration (:obj:`int`, optional): Duration of sent animation in seconds.
width (:obj:`int`, optional): Animation width.
height (:obj:`int`, optional): Animation height.
@ -1182,7 +1212,10 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'animation': parse_file_input(animation, Animation)}
data: JSONDict = {
'chat_id': chat_id,
'animation': parse_file_input(animation, Animation, filename=filename),
}
if duration:
data['duration'] = duration
@ -1225,6 +1258,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
allow_sending_without_reply: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
) -> Optional[Message]:
"""
Use this method to send audio files, if you want Telegram clients to display the file
@ -1245,6 +1279,9 @@ class Bot(TelegramObject):
(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.
filename (:obj:`str`, optional): Custom file name for the voice, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Voice message caption, 0-1024 characters after entities
parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to
@ -1274,7 +1311,10 @@ class Bot(TelegramObject):
:class:`telegram.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'voice': parse_file_input(voice, Voice)}
data: JSONDict = {
'chat_id': chat_id,
'voice': parse_file_input(voice, Voice, filename=filename),
}
if duration:
data['duration'] = duration

View file

@ -78,6 +78,9 @@ class InputMediaAnimation(InputMedia):
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Animation` object to send.
filename (:obj:`str`, optional): Custom file name for the animation, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
thumb (`filelike object` | :class:`pathlib.Path`, optional): Thumbnail of the file sent;
can be ignored if
thumbnail generation for the file is supported server-side. The thumbnail should be
@ -111,6 +114,7 @@ class InputMediaAnimation(InputMedia):
height: int = None,
duration: int = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
self.type = 'animation'
@ -120,7 +124,7 @@ class InputMediaAnimation(InputMedia):
self.height = media.height
self.duration = media.duration
else:
self.media = parse_file_input(media, attach=True)
self.media = parse_file_input(media, attach=True, filename=filename)
if thumb:
self.thumb = parse_file_input(thumb, attach=True)
@ -154,6 +158,9 @@ class InputMediaPhoto(InputMedia):
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.PhotoSize` object to send.
filename (:obj:`str`, optional): Custom file name for the photo, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional ): Caption of the photo to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@ -169,9 +176,10 @@ class InputMediaPhoto(InputMedia):
caption: str = None,
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
self.type = 'photo'
self.media = parse_file_input(media, PhotoSize, attach=True)
self.media = parse_file_input(media, PhotoSize, attach=True, filename=filename)
if caption:
self.caption = caption
@ -202,6 +210,9 @@ class InputMediaVideo(InputMedia):
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Video` object to send.
filename (:obj:`str`, optional): Custom file name for the video, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Caption of the video to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@ -241,6 +252,7 @@ class InputMediaVideo(InputMedia):
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
thumb: FileInput = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
self.type = 'video'
@ -250,7 +262,7 @@ class InputMediaVideo(InputMedia):
self.height = media.height
self.duration = media.duration
else:
self.media = parse_file_input(media, attach=True)
self.media = parse_file_input(media, attach=True, filename=filename)
if thumb:
self.thumb = parse_file_input(thumb, attach=True)
@ -291,6 +303,9 @@ class InputMediaAudio(InputMedia):
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Audio` object to send.
filename (:obj:`str`, optional): Custom file name for the audio, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Caption of the audio to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@ -325,6 +340,7 @@ class InputMediaAudio(InputMedia):
performer: str = None,
title: str = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
self.type = 'audio'
@ -334,7 +350,7 @@ class InputMediaAudio(InputMedia):
self.performer = media.performer
self.title = media.title
else:
self.media = parse_file_input(media, attach=True)
self.media = parse_file_input(media, attach=True, filename=filename)
if thumb:
self.thumb = parse_file_input(thumb, attach=True)
@ -372,6 +388,9 @@ class InputMediaDocument(InputMedia):
file_id to send a file that exists on the Telegram servers (recommended), pass an HTTP
URL for Telegram to get a file from the Internet. Lastly you can pass an existing
:class:`telegram.Document` object to send.
filename (:obj:`str`, optional): Custom file name for the document, when uploading a
new file. Convenience parameter, useful e.g. when sending files generated by the
:obj:`tempfile` module.
caption (:obj:`str`, optional): Caption of the document to be sent, 0-1024 characters after
entities parsing.
parse_mode (:obj:`str`, optional): Send Markdown or HTML, if you want Telegram apps to show
@ -398,9 +417,10 @@ class InputMediaDocument(InputMedia):
parse_mode: Union[str, DefaultValue] = DEFAULT_NONE,
disable_content_type_detection: bool = None,
caption_entities: Union[List[MessageEntity], Tuple[MessageEntity, ...]] = None,
filename: str = None,
):
self.type = 'document'
self.media = parse_file_input(media, Document, attach=True)
self.media = parse_file_input(media, Document, attach=True, filename=filename)
if thumb:
self.thumb = parse_file_input(thumb, attach=True)

View file

@ -96,6 +96,16 @@ class TestAnimation:
assert message.animation.thumb.width == self.width
assert message.animation.thumb.height == self.height
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['animation'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_animation(chat_id, animation_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, animation):

View file

@ -108,6 +108,16 @@ class TestAudio:
assert message.audio.thumb.width == self.thumb_width
assert message.audio.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_audio_custom_filename(self, bot, chat_id, audio_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['audio'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_audio(chat_id, audio_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, audio):

View file

@ -437,6 +437,34 @@ class TestSendMediaGroup:
mes.caption_entities == [MessageEntity(MessageEntity.BOLD, 0, 5)] for mes in messages
)
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_media_group_custom_filename(
self,
bot,
chat_id,
photo_file, # noqa: F811
animation_file, # noqa: F811
audio_file, # noqa: F811
video_file, # noqa: F811
monkeypatch,
):
def make_assertion(url, data, **kwargs):
result = all(im.media.filename == 'custom_filename' for im in data['media'])
# We are a bit hacky here b/c Bot.send_media_group expects a list of Message-dicts
return [Message(0, None, None, text=result).to_dict()]
monkeypatch.setattr(bot.request, 'post', make_assertion)
media = [
InputMediaAnimation(animation_file, filename='custom_filename'),
InputMediaAudio(audio_file, filename='custom_filename'),
InputMediaPhoto(photo_file, filename='custom_filename'),
InputMediaVideo(video_file, filename='custom_filename'),
]
assert bot.send_media_group(chat_id, media)[0].text is True
def test_send_media_group_with_thumbs(
self, bot, chat_id, video_file, photo_file, monkeypatch # noqa: F811
):

View file

@ -76,8 +76,19 @@ def check_method(h4):
ignored = IGNORED_PARAMETERS.copy()
if name == 'getUpdates':
ignored -= {'timeout'} # Has it's own timeout parameter that we do wanna check for
elif name == 'sendDocument':
ignored |= {'filename'} # Undocumented
elif name in (
f'send{media_type}'
for media_type in [
'Animation',
'Audio',
'Document',
'Photo',
'Video',
'VideoNote',
'Voice',
]
):
ignored |= {'filename'} # Convenience parameter
elif name == 'setGameScore':
ignored |= {'edit_message'} # TODO: Now deprecated, so no longer in telegrams docs
elif name == 'sendContact':
@ -131,8 +142,8 @@ def check_object(h4):
ignored |= {'credentials'}
elif name == 'PassportElementError':
ignored |= {'message', 'type', 'source'}
elif name == 'Message':
ignored |= {'default_quote'}
elif name.startswith('InputMedia'):
ignored |= {'filename'} # Convenience parameter
assert (sig.parameters.keys() ^ checked) - ignored == set()

View file

@ -115,6 +115,16 @@ class TestPhoto:
assert message.caption == TestPhoto.caption.replace('*', '')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['photo'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_photo(chat_id, photo_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_photo_parse_mode_markdown(self, bot, chat_id, photo_file, thumb, photo):

View file

@ -114,6 +114,16 @@ class TestVideo:
assert message.video.file_name == self.file_name
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['video'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_video(chat_id, video_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, video):

View file

@ -96,6 +96,16 @@ class TestVideoNote:
assert message.video_note.thumb.width == self.thumb_width
assert message.video_note.thumb.height == self.thumb_height
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_video_note_custom_filename(self, bot, chat_id, video_note_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['video_note'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_video_note(chat_id, video_note_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, video_note):

View file

@ -86,6 +86,16 @@ class TestVoice:
assert message.voice.file_size == voice.file_size
assert message.caption == self.caption.replace('*', '')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_voice_custom_filename(self, bot, chat_id, voice_file, monkeypatch):
def make_assertion(url, data, **kwargs):
return data['voice'].filename == 'custom_filename'
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.send_voice(chat_id, voice_file, filename='custom_filename')
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_get_and_download(self, bot, voice):