From 4426eb0c61fc0beed16f8ff4e33cb1610d67a08f Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 20 May 2017 19:25:24 +0200 Subject: [PATCH 1/9] Add VideoNote --- telegram/videonote.py | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 telegram/videonote.py diff --git a/telegram/videonote.py b/telegram/videonote.py new file mode 100644 index 000000000..ba3616d43 --- /dev/null +++ b/telegram/videonote.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2017 +# Leandro Toledo de Souza +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser 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 Lesser Public License for more details. +# +# 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 VideoNote.""" + +from telegram import PhotoSize, TelegramObject + + +class VideoNote(TelegramObject): + """This object represents a Telegram VideoNote. + + Attributes: + file_id (str): Unique identifier for this file + length (int): Video width and height as defined by sender + duration (int): Duration of the video in seconds as defined by sender + thumb (Optional[:class:`telegram.PhotoSize`]): Video thumbnail + file_size (Optional[int]): File size + """ + + def __init__(self, file_id, length, duration, thumb=None, file_size=None, **kwargs): + # Required + self.file_id = str(file_id) + self.length = int(length) + self.duration = int(duration) + # Optionals + self.thumb = thumb + self.file_size = file_size + + self._id_attrs = (self.file_id,) + + @staticmethod + def de_json(data, bot): + """ + Args: + data (dict): + bot (telegram.Bot): + + Returns: + telegram.VideoNote: + """ + if not data: + return None + + data = super(VideoNote, VideoNote).de_json(data, bot) + + data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot) + + return VideoNote(**data) From 65929a08130bb239401c9ece44aa5bc20c2b252e Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 20 May 2017 19:26:26 +0200 Subject: [PATCH 2/9] VideoNote chatactions --- telegram/chataction.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telegram/chataction.py b/telegram/chataction.py index 99b39139f..df6e99a9e 100644 --- a/telegram/chataction.py +++ b/telegram/chataction.py @@ -31,3 +31,5 @@ class ChatAction(object): UPLOAD_AUDIO = 'upload_audio' UPLOAD_DOCUMENT = 'upload_document' FIND_LOCATION = 'find_location' + RECORD_VIDEO_NOTE = 'record_video_note' + UPLOAD_VIDEO_NOTE = 'upload_video_note' From ae39c902ed0d6d900f4649ecbe20d49841c3c2b5 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 20 May 2017 19:30:07 +0200 Subject: [PATCH 3/9] Add video_note to Message --- telegram/message.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/telegram/message.py b/telegram/message.py index 5c3c2ff6f..5547ebac1 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -25,6 +25,7 @@ from time import mktime from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, TelegramObject, User, Video, Voice, Venue, MessageEntity, Game) from telegram.utils.helpers import escape_html, escape_markdown +from telegram.videonote import VideoNote class Message(TelegramObject): @@ -51,6 +52,8 @@ class Message(TelegramObject): sticker (:class:`telegram.Sticker`): video (:class:`telegram.Video`): voice (:class:`telegram.Voice`): + video_note (:class:`telegram.VideoNote`): Message is a video note, information about the + video message caption (str): contact (:class:`telegram.Contact`): location (:class:`telegram.Location`): @@ -91,6 +94,7 @@ class Message(TelegramObject): sticker (Optional[:class:`telegram.Sticker`]): video (Optional[:class:`telegram.Video`]): voice (Optional[:class:`telegram.Voice`]): + video_note (Optional[:class:`telegram.VideoNote`]): caption (Optional[str]): contact (Optional[:class:`telegram.Contact`]): location (Optional[:class:`telegram.Location`]): @@ -125,6 +129,7 @@ class Message(TelegramObject): sticker=None, video=None, voice=None, + video_note=None, caption=None, contact=None, location=None, @@ -163,6 +168,7 @@ class Message(TelegramObject): self.sticker = sticker self.video = video self.voice = voice + self.video_note = video_note self.caption = caption self.contact = contact self.location = location @@ -220,6 +226,7 @@ class Message(TelegramObject): data['sticker'] = Sticker.de_json(data.get('sticker'), bot) data['video'] = Video.de_json(data.get('video'), bot) data['voice'] = Voice.de_json(data.get('voice'), bot) + data['video_note'] = VideoNote.de_json(data.get('video_note'), bot) data['contact'] = Contact.de_json(data.get('contact'), bot) data['location'] = Location.de_json(data.get('location'), bot) data['venue'] = Venue.de_json(data.get('venue'), bot) From 2746ab77e57da1047077b564bc68b621acf59dc4 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sat, 20 May 2017 19:35:55 +0200 Subject: [PATCH 4/9] Add sendVideoNote to Bot --- telegram/bot.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/telegram/bot.py b/telegram/bot.py index e6da982f0..b1c4211fc 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -671,6 +671,66 @@ class Bot(TelegramObject): timeout=timeout, **kwargs) + @log + def send_video_note(self, + chat_id, + video_note, + duration=None, + length=None, + disable_notification=False, + reply_to_message_id=None, + reply_markup=None, + timeout=20., + **kwargs): + """As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute + long. Use this method to send video messages + + Args: + chat_id (int|str): Unique identifier for the message recipient - Chat id. + voice: 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. Sending video notes + by a URL is currently unsupported + duration (Optional[int]): Duration of sent audio in seconds. + length (Optional[int]): Video width and height + disable_notification (Optional[bool]): Sends the message silently. iOS users will not + receive a notification, Android users will receive a notification with no sound. + reply_to_message_id (Optional[int]): If the message is a reply, ID of the original + message. + 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[int|float]): Send file timeout (default: 20 seconds). + **kwargs (dict): Arbitrary keyword arguments. + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + Raises: + :class:`telegram.TelegramError` + + """ + url = '{0}/sendVideoNote'.format(self.base_url) + + data = {'chat_id': chat_id, 'video_note': video_note} + + if duration: + data['duration'] = duration + if length: + data['length'] = length + + return self._message_wrapper( + url, + data, + chat_id=chat_id, + video_note=video_note, + duration=duration, + length=length, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + reply_markup=reply_markup, + timeout=timeout, + **kwargs) + @log @message def send_location(self, @@ -1787,6 +1847,7 @@ class Bot(TelegramObject): sendSticker = send_sticker sendVideo = send_video sendVoice = send_voice + sendVideoNote = send_video_note sendLocation = send_location sendVenue = send_venue sendContact = send_contact From 0e2bcf28a6939ba99aa7f9cd4ce2cd45ee6c9a10 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 21 May 2017 13:50:15 +0200 Subject: [PATCH 5/9] Export VideoNote --- telegram/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index b6929e838..b942e6813 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -86,6 +86,7 @@ from .inputvenuemessagecontent import InputVenueMessageContent from .inputcontactmessagecontent import InputContactMessageContent from .webhookinfo import WebhookInfo from .gamehighscore import GameHighScore +from .videonote import VideoNote from .update import Update from .bot import Bot from .constants import (MAX_MESSAGE_LENGTH, MAX_CAPTION_LENGTH, SUPPORTED_WEBHOOK_PORTS, @@ -115,5 +116,5 @@ __all__ = [ 'Video', 'Voice', 'MAX_MESSAGE_LENGTH', 'MAX_CAPTION_LENGTH', 'SUPPORTED_WEBHOOK_PORTS', 'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT', 'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation', - 'Game', 'GameHighScore' + 'Game', 'GameHighScore', 'VideoNote' ] From 41299244b79324be7d59276d7eab004be3aecb73 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 21 May 2017 13:50:52 +0200 Subject: [PATCH 6/9] Allow InputFile to handle video notes --- telegram/inputfile.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/telegram/inputfile.py b/telegram/inputfile.py index e73115f79..de4aac011 100644 --- a/telegram/inputfile.py +++ b/telegram/inputfile.py @@ -35,7 +35,8 @@ from telegram import TelegramError DEFAULT_MIME_TYPE = 'application/octet-stream' USER_AGENT = 'Python Telegram Bot (https://github.com/python-telegram-bot/python-telegram-bot)' -FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate') +FILE_TYPES = ('audio', 'document', 'photo', 'sticker', 'video', 'voice', 'certificate', + 'video_note') class InputFile(object): @@ -66,6 +67,9 @@ class InputFile(object): elif 'certificate' in data: self.input_name = 'certificate' self.input_file = data.pop('certificate') + elif 'video_note' in data: + self.input_name = 'video_note' + self.input_file = data.pop('video_note') else: raise TelegramError('Unknown inputfile type') @@ -117,7 +121,7 @@ class InputFile(object): form_boundary, 'Content-Disposition: form-data; name="%s"' % name, '', str(value) ]) - # Add input_file to upload +# Add input_file to upload form.extend([ form_boundary, 'Content-Disposition: form-data; name="%s"; filename="%s"' % (self.input_name, From 2dd5290ec2df50450ba59e0c4a88b2531cb072d9 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 21 May 2017 13:51:27 +0200 Subject: [PATCH 7/9] Add reply_video_note in line with other reply_* --- telegram/message.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/telegram/message.py b/telegram/message.py index 5547ebac1..d3e54313c 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -415,6 +415,23 @@ class Message(TelegramObject): self._quote(kwargs) return self.bot.sendVideo(self.chat_id, *args, **kwargs) + def reply_video_note(self, *args, **kwargs): + """ + Shortcut for ``bot.send_video_note(update.message.chat_id, *args, **kwargs)`` + + Keyword Args: + quote (Optional[bool]): If set to ``True``, the video is sent as an actual reply to + this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter + will be ignored. Default: ``True`` in group chats and ``False`` in private chats. + + Returns: + :class:`telegram.Message`: On success, instance representing the message posted. + + """ + + self._quote(kwargs) + return self.bot.send_video_note(self.chat_id, *args, **kwargs) + def reply_voice(self, *args, **kwargs): """ Shortcut for ``bot.sendVoice(update.message.chat_id, *args, **kwargs)`` From 7583fa9d6511560ccc15357cca469a62d1943c5d Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 21 May 2017 13:51:58 +0200 Subject: [PATCH 8/9] Tests for video notes --- tests/test_videonote.py | 212 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 tests/test_videonote.py diff --git a/tests/test_videonote.py b/tests/test_videonote.py new file mode 100644 index 000000000..078c08ea3 --- /dev/null +++ b/tests/test_videonote.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015-2017 +# Leandro Toledo de Souza +# +# 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 VideoNote""" + +import sys +import unittest +import os + +from flaky import flaky + +sys.path.append('.') + +import telegram +from tests.base import BaseTest, timeout + + +class VideoNoteTest(BaseTest, unittest.TestCase): + """This object represents Tests for Telegram VideoNote.""" + + def setUp(self): + self.videonote_file = open('tests/data/telegram.mp4', 'rb') + self.videonote_file_id = 'DQADAQADBwAD5VIIRYemhHpbPmIQAg' + self.duration = 5 + self.length = 1 # No bloody clue what this does, see note in first test + self.thumb = telegram.PhotoSize.de_json({ + 'file_id': 'AAQBABOMsecvAAQqqoY1Pee_MqcyAAIC', + 'width': 51, + 'file_size': 645, + 'height': 90 + }, self._bot) + self.file_size = 326534 + + self.json_dict = { + 'file_id': self.videonote_file_id, + 'duration': self.duration, + 'length': self.length, + 'thumb': self.thumb.to_dict(), + 'file_size': self.file_size + } + + @flaky(3, 1) + @timeout(10) + def test_error_send_videonote_required_args_only(self): + # This is where it gets weird.... + # According to telegram length is Video width and height.. but how that works with one + # parameter I couldn't tell you + # It would also seem that it is in fact a required parameter, so the original test below + # fails. Therefore I decided I check for the error instead - that way we'll also know + # when telegram fixes their shit + with self.assertRaisesRegex(telegram.error.BadRequest, r'Wrong video note length'): + message = self._bot.sendVideoNote(self._chat_id, self.videonote_file, timeout=10) + +# videonote = message.videonote +# +# self.assertTrue(isinstance(videonote.file_id, str)) +# self.assertNotEqual(videonote.file_id, None) +# self.assertEqual(videonote.duration, self.duration) +# self.assertEqual(videonote.length, self.length) +# self.assertEqual(videonote.thumb, self.thumb) +# self.assertEqual(videonote.file_size, self.file_size) + + @flaky(3, 1) + @timeout(10) + def test_send_videonote_actual_required_args_only(self): + # See above test... if you pass any number that's > 0 and < some high number, it seems + # to work + message = self._bot.sendVideoNote( + self._chat_id, self.videonote_file, length=self.length, timeout=10) + + videonote = message.video_note + + self.assertTrue(isinstance(videonote.file_id, str)) + self.assertNotEqual(videonote.file_id, None) + self.assertEqual(videonote.duration, self.duration) + # self.assertEqual(videonote.length, self.length) + self.assertEqual(videonote.thumb, self.thumb) + self.assertEqual(videonote.file_size, self.file_size) + + @flaky(3, 1) + @timeout(10) + def test_send_videonote_all_args(self): + message = self._bot.sendVideoNote( + self._chat_id, + self.videonote_file, + timeout=10, + duration=self.duration, + length=self.length) + + videonote = message.video_note + + self.assertTrue(isinstance(videonote.file_id, str)) + self.assertNotEqual(videonote.file_id, None) + # self.assertEqual(videonote.length, self.length) + self.assertEqual(videonote.duration, self.duration) + self.assertEqual(videonote.thumb, self.thumb) + self.assertEqual(videonote.file_size, self.file_size) + + @flaky(3, 1) + @timeout(10) + def test_send_videonote_resend(self): + message = self._bot.sendVideoNote( + chat_id=self._chat_id, + video_note=self.videonote_file_id, + timeout=10, + duration=self.duration, + length=self.length) + + videonote = message.video_note + + self.assertEqual(videonote.file_id, self.videonote_file_id) + # self.assertEqual(videonote.length, self.length) + self.assertEqual(videonote.duration, self.duration) + self.assertEqual(videonote.thumb, self.thumb) + self.assertEqual(videonote.file_size, self.file_size) + + def test_videonote_de_json(self): + videonote = telegram.VideoNote.de_json(self.json_dict, self._bot) + + self.assertEqual(videonote.file_id, self.videonote_file_id) + # self.assertEqual(videonote.duration, self.duration) + self.assertEqual(videonote.thumb, self.thumb) + self.assertEqual(videonote.length, self.length) + self.assertEqual(videonote.file_size, self.file_size) + + def test_videonote_to_json(self): + videonote = telegram.VideoNote.de_json(self.json_dict, self._bot) + + self.assertTrue(self.is_json(videonote.to_json())) + + def test_videonote_to_dict(self): + videonote = telegram.VideoNote.de_json(self.json_dict, self._bot) + + self.assertTrue(self.is_dict(videonote.to_dict())) + self.assertEqual(videonote['file_id'], self.videonote_file_id) + self.assertEqual(videonote['duration'], self.duration) + self.assertEqual(videonote['length'], self.length) + self.assertEqual(videonote['file_size'], self.file_size) + + @flaky(3, 1) + @timeout(10) + def test_error_send_videonote_empty_file(self): + json_dict = self.json_dict + + del (json_dict['file_id']) + json_dict['video_note'] = open(os.devnull, 'rb') + + self.assertRaises(telegram.TelegramError, + lambda: self._bot.sendVideoNote(chat_id=self._chat_id, + timeout=10, + **json_dict)) + + @flaky(3, 1) + @timeout(10) + def test_error_send_videonote_empty_file_id(self): + json_dict = self.json_dict + + del (json_dict['file_id']) + json_dict['video_note'] = '' + + self.assertRaises(telegram.TelegramError, + lambda: self._bot.sendVideoNote(chat_id=self._chat_id, + timeout=10, + **json_dict)) + + @flaky(3, 1) + @timeout(10) + def test_reply_videonote(self): + """Test for Message.reply_videonote""" + message = self._bot.sendMessage(self._chat_id, '.') + # Length is needed... see first test + message = message.reply_video_note(self.videonote_file, length=self.length) + + self.assertNotEqual(message.video_note.file_id, None) + + def test_equality(self): + a = telegram.VideoNote(self.videonote_file_id, self.length, self.duration) + b = telegram.VideoNote(self.videonote_file_id, self.length, self.duration) + c = telegram.VideoNote(self.videonote_file_id, 0, 0, 0) + d = telegram.VideoNote("", self.length, self.duration) + e = telegram.Voice(self.videonote_file_id, self.duration) + + self.assertEqual(a, b) + self.assertEqual(hash(a), hash(b)) + self.assertIsNot(a, b) + + self.assertEqual(a, c) + self.assertEqual(hash(a), hash(c)) + + self.assertNotEqual(a, d) + self.assertNotEqual(hash(a), hash(d)) + + self.assertNotEqual(a, e) + self.assertNotEqual(hash(a), hash(e)) + +if __name__ == '__main__': + unittest.main() From 99ecac5649d199051215331e73928efd6126fac8 Mon Sep 17 00:00:00 2001 From: Jacob Bom Date: Sun, 21 May 2017 14:10:55 +0200 Subject: [PATCH 9/9] assertRaisesRegex doesn't exist on py2 (also fuck yapf) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This deprecation is totally gonna come back and bite us when the regexp gets removed completely in never version´ --- tests/test_videonote.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/test_videonote.py b/tests/test_videonote.py index 078c08ea3..5689157bb 100644 --- a/tests/test_videonote.py +++ b/tests/test_videonote.py @@ -63,17 +63,17 @@ class VideoNoteTest(BaseTest, unittest.TestCase): # It would also seem that it is in fact a required parameter, so the original test below # fails. Therefore I decided I check for the error instead - that way we'll also know # when telegram fixes their shit - with self.assertRaisesRegex(telegram.error.BadRequest, r'Wrong video note length'): + with self.assertRaisesRegexp(telegram.error.BadRequest, r'Wrong video note length'): message = self._bot.sendVideoNote(self._chat_id, self.videonote_file, timeout=10) -# videonote = message.videonote -# -# self.assertTrue(isinstance(videonote.file_id, str)) -# self.assertNotEqual(videonote.file_id, None) -# self.assertEqual(videonote.duration, self.duration) -# self.assertEqual(videonote.length, self.length) -# self.assertEqual(videonote.thumb, self.thumb) -# self.assertEqual(videonote.file_size, self.file_size) + # videonote = message.videonote + # + # self.assertTrue(isinstance(videonote.file_id, str)) + # self.assertNotEqual(videonote.file_id, None) + # self.assertEqual(videonote.duration, self.duration) + # self.assertEqual(videonote.length, self.length) + # self.assertEqual(videonote.thumb, self.thumb) + # self.assertEqual(videonote.file_size, self.file_size) @flaky(3, 1) @timeout(10)