From 1d6e9502cb8f7359c7d56942f2f97b0cb8b1f8b0 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Thu, 20 Aug 2015 21:07:49 -0300 Subject: [PATCH 01/20] Adding botan.io support --- telegram/utils/__init__.py | 0 telegram/utils/botan.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 telegram/utils/__init__.py create mode 100644 telegram/utils/botan.py diff --git a/telegram/utils/__init__.py b/telegram/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/telegram/utils/botan.py b/telegram/utils/botan.py new file mode 100644 index 000000000..e69de29bb From d03a394075454c2e7cb3aaf421349d6f27112835 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 21 Aug 2015 14:49:07 -0300 Subject: [PATCH 02/20] Refactoring, improving the design of existing message class and adding docstrings --- telegram/__init__.py | 2 +- telegram/message.py | 319 +++++++++++++++++++++++-------------------- tests/test_bot.py | 5 +- 3 files changed, 175 insertions(+), 151 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index dcc3e6c08..4dc2ea57e 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -22,7 +22,6 @@ __version__ = '2.7.1' from .base import TelegramObject from .user import User -from .message import Message from .update import Update from .groupchat import GroupChat from .photosize import PhotoSize @@ -43,6 +42,7 @@ from .inputfile import InputFile from .error import TelegramError from .nullhandler import NullHandler from .emoji import Emoji +from .message import Message from .bot import Bot __all__ = ['Bot', 'Emoji', 'TelegramError', 'InputFile', 'ReplyMarkup', diff --git a/telegram/message.py b/telegram/message.py index 770ec0acf..f50c2377f 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# pylint: disable=too-many-instance-attributes +# pylint: disable=too-many-arguments,too-many-branches # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,251 +18,272 @@ # 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 a object that represents a Telegram Message""" -from telegram import TelegramObject from datetime import datetime from time import mktime +from telegram import (Audio, Contact, Document, GroupChat, Location, PhotoSize, + Sticker, TelegramObject, User, Video, Voice) + class Message(TelegramObject): + """This object represents a Telegram Message. + + Note: + * In Python `from` is a reserved word, use `from_user` instead. + + Attributes: + message_id (int): + from_user (:class:`telegram.User`): + date (:class:`datetime.datetime`): + forward_from (:class:`telegram.User`): + forward_date (:class:`datetime.datetime`): + reply_to_message (:class:`telegram.Message`): + text (str): + audio (:class:`telegram.Audio`): + document (:class:`telegram.Document`): + photo (List[:class:`telegram.PhotoSize`]): + sticker (:class:`telegram.Sticker`): + video (:class:`telegram.Video`): + voice (:class:`telegram.Voice`): + caption (str): + contact (:class:`telegram.Contact`): + location (:class:`telegram.Location`): + new_chat_participant (:class:`telegram.User`): + left_chat_participant (:class:`telegram.User`): + new_chat_title (str): + new_chat_photo (List[:class:`telegram.PhotoSize`]): + delete_chat_photo (bool): + group_chat_created (bool): + + Args: + message_id (int): + from_user (:class:`telegram.User`): + date (:class:`datetime.datetime`): + chat (:class:`telegram.User` or :class:`telegram.GroupChat`): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + forward_from (Optional[:class:`telegram.User`]): + forward_date (Optional[:class:`datetime.datetime`]): + reply_to_message (Optional[:class:`telegram.Message`]): + text (Optional[str]): + audio (Optional[:class:`telegram.Audio`]): + document (Optional[:class:`telegram.Document`]): + photo (Optional[List[:class:`telegram.PhotoSize`]]): + sticker (Optional[:class:`telegram.Sticker`]): + video (Optional[:class:`telegram.Video`]): + voice (Optional[:class:`telegram.Voice`]): + caption (Optional[str]): + contact (Optional[:class:`telegram.Contact`]): + location (Optional[:class:`telegram.Location`]): + new_chat_participant (Optional[:class:`telegram.User`]): + left_chat_participant (Optional[:class:`telegram.User`]): + new_chat_title (Optional[str]): + new_chat_photo (Optional[List[:class:`telegram.PhotoSize`]): + delete_chat_photo (Optional[bool]): + group_chat_created (Optional[bool]): + """ + def __init__(self, message_id, from_user, date, chat, - forward_from=None, - forward_date=None, - reply_to_message=None, - text=None, - audio=None, - document=None, - photo=None, - sticker=None, - video=None, - voice=None, - caption=None, - contact=None, - location=None, - new_chat_participant=None, - left_chat_participant=None, - new_chat_title=None, - new_chat_photo=None, - delete_chat_photo=None, - group_chat_created=None): + **kwargs): self.message_id = message_id self.from_user = from_user self.date = date self.chat = chat - self.forward_from = forward_from - self.forward_date = forward_date - self.reply_to_message = reply_to_message - self.text = text - self.audio = audio - self.document = document - self.photo = photo - self.sticker = sticker - self.video = video - self.voice = voice - self.caption = caption - self.contact = contact - self.location = location - self.new_chat_participant = new_chat_participant - self.left_chat_participant = left_chat_participant - self.new_chat_title = new_chat_title - self.new_chat_photo = new_chat_photo - self.delete_chat_photo = delete_chat_photo - self.group_chat_created = group_chat_created + self.forward_from = kwargs.get('forward_from') + self.forward_date = kwargs.get('forward_date') + self.reply_to_message = kwargs.get('reply_to_message') + self.text = kwargs.get('text') + self.audio = kwargs.get('audio') + self.document = kwargs.get('document') + self.photo = kwargs.get('photo') + self.sticker = kwargs.get('sticker') + self.video = kwargs.get('video') + self.voice = kwargs.get('voice') + self.caption = kwargs.get('caption') + self.contact = kwargs.get('contact') + self.location = kwargs.get('location') + self.new_chat_participant = kwargs.get('new_chat_participant') + self.left_chat_participant = kwargs.get('left_chat_participant') + self.new_chat_title = kwargs.get('new_chat_title') + self.new_chat_photo = kwargs.get('new_chat_photo') + self.delete_chat_photo = kwargs.get('delete_chat_photo') + self.group_chat_created = kwargs.get('group_chat_created') @property def chat_id(self): + """int: Short for :attr:`Message.chat.id`""" return self.chat.id @staticmethod def de_json(data): - if 'from' in data: # from is a reserved word, use from_user instead. - from telegram import User - from_user = User.de_json(data['from']) - else: - from_user = None + """ + Args: + data (str): - if 'date' in data: - date = datetime.fromtimestamp(data['date']) - else: - date = None + Returns: + telegram.Message: + """ + message = dict() - if 'chat' in data: - if 'first_name' in data['chat']: - from telegram import User - chat = User.de_json(data['chat']) - if 'title' in data['chat']: - from telegram import GroupChat - chat = GroupChat.de_json(data['chat']) - else: - chat = None + message['message_id'] = int(data['message_id']) + message['from_user'] = User.de_json(data['from']) + message['date'] = datetime.fromtimestamp(data['date']) + + if 'first_name' in data['chat']: + message['chat'] = User.de_json(data['chat']) + elif 'title' in data['chat']: + message['chat'] = GroupChat.de_json(data['chat']) if 'forward_from' in data: - from telegram import User - forward_from = User.de_json(data['forward_from']) - else: - forward_from = None + message['forward_from'] = User.de_json(data['forward_from']) if 'forward_date' in data: - forward_date = datetime.fromtimestamp(data['forward_date']) - else: - forward_date = None + message['forward_date'] = \ + datetime.fromtimestamp(data['forward_date']) + + message['text'] = data.get('text', '') if 'reply_to_message' in data: - reply_to_message = Message.de_json(data['reply_to_message']) - else: - reply_to_message = None + message['reply_to_message'] = \ + Message.de_json(data['reply_to_message']) if 'audio' in data: - from telegram import Audio - audio = Audio.de_json(data['audio']) - else: - audio = None + message['audio'] = Audio.de_json(data['audio']) if 'document' in data: - from telegram import Document - document = Document.de_json(data['document']) - else: - document = None + message['document'] = Document.de_json(data['document']) if 'photo' in data: - from telegram import PhotoSize - photo = [PhotoSize.de_json(x) for x in data['photo']] - else: - photo = None + message['photo'] = [PhotoSize.de_json(x) for x in data['photo']] if 'sticker' in data: - from telegram import Sticker - sticker = Sticker.de_json(data['sticker']) - else: - sticker = None + message['sticker'] = Sticker.de_json(data['sticker']) if 'video' in data: - from telegram import Video - video = Video.de_json(data['video']) - else: - video = None + message['video'] = Video.de_json(data['video']) if 'voice' in data: - from telegram import Voice - voice = Voice.de_json(data['voice']) - else: - voice = None + message['voice'] = Voice.de_json(data['voice']) + + message['caption'] = data.get('caption', '') if 'contact' in data: - from telegram import Contact - contact = Contact.de_json(data['contact']) - else: - contact = None + message['contact'] = Contact.de_json(data['contact']) if 'location' in data: - from telegram import Location - location = Location.de_json(data['location']) - else: - location = None + message['location'] = Location.de_json(data['location']) if 'new_chat_participant' in data: - from telegram import User - new_chat_participant = User.de_json(data['new_chat_participant']) - else: - new_chat_participant = None + message['new_chat_participant'] = \ + User.de_json(data['new_chat_participant']) if 'left_chat_participant' in data: - from telegram import User - left_chat_participant = User.de_json(data['left_chat_participant']) - else: - left_chat_participant = None + message['left_chat_participant'] = \ + User.de_json(data['left_chat_participant']) + + message['new_chat_title'] = data.get('new_chat_title', '') if 'new_chat_photo' in data: - from telegram import PhotoSize - new_chat_photo = \ + message['new_chat_photo'] = \ [PhotoSize.de_json(x) for x in data['new_chat_photo']] - else: - new_chat_photo = None - return Message(message_id=data.get('message_id', None), - from_user=from_user, - date=date, - chat=chat, - forward_from=forward_from, - forward_date=forward_date, - reply_to_message=reply_to_message, - text=data.get('text', ''), - audio=audio, - document=document, - photo=photo, - sticker=sticker, - video=video, - voice=voice, - caption=data.get('caption', ''), - contact=contact, - location=location, - new_chat_participant=new_chat_participant, - left_chat_participant=left_chat_participant, - new_chat_title=data.get('new_chat_title', None), - new_chat_photo=new_chat_photo, - delete_chat_photo=data.get('delete_chat_photo', None), - group_chat_created=data.get('group_chat_created', None)) + message['delete_chat_photo'] = \ + bool(data.get('delete_chat_photo', False)) + + message['group_chat_created'] = \ + bool(data.get('group_chat_created', False)) + + return Message(**message) def to_dict(self): + """ + Returns: + dict: + """ data = {'message_id': self.message_id, 'from': self.from_user.to_dict(), + 'date': self._totimestamp(self.date), 'chat': self.chat.to_dict()} - try: - # Python 3.3+ supports .timestamp() - data['date'] = int(self.date.timestamp()) - - if self.forward_date: - data['forward_date'] = int(self.forward_date.timestamp()) - except AttributeError: - # _totimestamp() for Python 3 (< 3.3) and Python 2 - data['date'] = self._totimestamp(self.date) - - if self.forward_date: - data['forward_date'] = self._totimestamp(self.forward_date) if self.forward_from: data['forward_from'] = self.forward_from.to_dict() + + if self.forward_date: + data['forward_date'] = self._totimestamp(self.forward_date) + if self.reply_to_message: data['reply_to_message'] = self.reply_to_message.to_dict() + if self.text: data['text'] = self.text + if self.audio: data['audio'] = self.audio.to_dict() + if self.document: data['document'] = self.document.to_dict() + if self.photo: data['photo'] = [p.to_dict() for p in self.photo] + if self.sticker: data['sticker'] = self.sticker.to_dict() + if self.video: data['video'] = self.video.to_dict() + if self.voice: data['voice'] = self.voice.to_dict() + if self.caption: data['caption'] = self.caption + if self.contact: data['contact'] = self.contact.to_dict() + if self.location: data['location'] = self.location.to_dict() + if self.new_chat_participant: data['new_chat_participant'] = self.new_chat_participant.to_dict() + if self.left_chat_participant: data['left_chat_participant'] = \ self.left_chat_participant.to_dict() + if self.new_chat_title: data['new_chat_title'] = self.new_chat_title + if self.new_chat_photo: data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] + if self.delete_chat_photo: data['delete_chat_photo'] = self.delete_chat_photo + if self.group_chat_created: data['group_chat_created'] = self.group_chat_created + return data @staticmethod - def _totimestamp(dt): - return int(mktime(dt.timetuple())) + def _totimestamp(dt_obj): + """ + Args: + dt_obj (:class:`datetime.datetime`): + + Returns: + int: + """ + try: + # Python 3.3+ + return int(dt_obj.timestamp()) + except AttributeError: + # Python 3 (< 3.3) and Python 2 + return int(mktime(dt_obj.timetuple())) diff --git a/tests/test_bot.py b/tests/test_bot.py index b44808c68..fcc266ea3 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -62,8 +62,9 @@ class BotTest(unittest.TestCase): '''Test the telegram.Bot getUpdates method''' print('Testing getUpdates') updates = self._bot.getUpdates() - self.assertTrue(self.is_json(updates[0].to_json())) - self.assertIsInstance(updates[0], telegram.Update) + if updates: + self.assertTrue(self.is_json(updates[0].to_json())) + self.assertIsInstance(updates[0], telegram.Update) def testForwardMessage(self): '''Test the telegram.Bot forwardMessage method''' From fdb5f2339cdd0defd1a740eb453fab3a530b6e7e Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 21 Aug 2015 23:15:29 -0300 Subject: [PATCH 03/20] Improving the design of existing Telegram classes and adding docstrings --- telegram/__init__.py | 4 +- telegram/audio.py | 81 ++++++++++++---- telegram/base.py | 2 +- telegram/chataction.py | 5 + telegram/contact.py | 65 +++++++++++-- telegram/document.py | 79 ++++++++++++---- telegram/emoji.py | 35 +++++-- telegram/error.py | 9 +- telegram/groupchat.py | 47 +++++++++- telegram/inputfile.py | 35 +++++-- telegram/location.py | 46 +++++++-- telegram/message.py | 172 ++++++++++++++++------------------ telegram/nullhandler.py | 7 ++ telegram/photosize.py | 68 +++++++++++--- telegram/sticker.py | 84 ++++++++++++----- telegram/update.py | 52 +++++++--- telegram/user.py | 69 +++++++++++--- telegram/userprofilephotos.py | 64 +++++++++---- telegram/utils/botan.py | 74 +++++++++++++++ telegram/video.py | 96 ++++++++++++++----- telegram/voice.py | 66 ++++++++++--- 21 files changed, 877 insertions(+), 283 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index 4dc2ea57e..0c363c334 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -22,7 +22,6 @@ __version__ = '2.7.1' from .base import TelegramObject from .user import User -from .update import Update from .groupchat import GroupChat from .photosize import PhotoSize from .audio import Audio @@ -38,11 +37,12 @@ from .replymarkup import ReplyMarkup from .replykeyboardmarkup import ReplyKeyboardMarkup from .replykeyboardhide import ReplyKeyboardHide from .forcereply import ForceReply -from .inputfile import InputFile from .error import TelegramError +from .inputfile import InputFile from .nullhandler import NullHandler from .emoji import Emoji from .message import Message +from .update import Update from .bot import Bot __all__ = ['Bot', 'Emoji', 'TelegramError', 'InputFile', 'ReplyMarkup', diff --git a/telegram/audio.py b/telegram/audio.py index 5fff1d2e7..50a3bb753 100644 --- a/telegram/audio.py +++ b/telegram/audio.py @@ -16,37 +16,83 @@ # 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 a object that represents a Telegram Audio""" from telegram import TelegramObject class Audio(TelegramObject): + """This object represents a Telegram Audio. + + Attributes: + file_id (str): + duration (int): + performer (str): + title (str): + mime_type (str): + file_size (int): + + Args: + file_id (str): + duration (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + performer (Optional[str]): + title (Optional[str]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + def __init__(self, file_id, duration, - performer=None, - title=None, - mime_type=None, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.duration = duration - self.performer = performer - self.title = title - self.mime_type = mime_type - self.file_size = file_size + self.duration = int(duration) + # Optionals + self.performer = kwargs.get('performer', '') + self.title = kwargs.get('title', '') + self.mime_type = kwargs.get('mime_type', '') + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - return Audio(file_id=data.get('file_id', None), - duration=data.get('duration', None), - performer=data.get('performer', None), - title=data.get('title', None), - mime_type=data.get('mime_type', None), - file_size=data.get('file_size', None)) + """ + Args: + data (str): + + Returns: + telegram.Audio: + """ + if not data: + return None + + audio = dict() + + # Required + audio['file_id'] = data['file_id'] + audio['duration'] = data['duration'] + # Optionals + audio['performer'] = data.get('performer') + audio['title'] = data.get('title') + audio['mime_type'] = data.get('mime_type') + audio['file_size'] = data.get('file_size', 0) + + return Audio(**audio) def to_dict(self): - data = {'file_id': self.file_id, - 'duration': self.duration} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + data['duration'] = self.duration + # Optionals if self.performer: data['performer'] = self.performer if self.title: @@ -55,4 +101,5 @@ class Audio(TelegramObject): data['mime_type'] = self.mime_type if self.file_size: data['file_size'] = self.file_size + return data diff --git a/telegram/base.py b/telegram/base.py index 6654fd7d0..c6c3e4c47 100644 --- a/telegram/base.py +++ b/telegram/base.py @@ -41,4 +41,4 @@ class TelegramObject(object): @abstractmethod def to_dict(self): - return + return None diff --git a/telegram/chataction.py b/telegram/chataction.py index d9fc77610..290307005 100644 --- a/telegram/chataction.py +++ b/telegram/chataction.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# pylint: disable=R0903 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,8 +17,12 @@ # 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 a object that represents a Telegram ChatAction""" + class ChatAction(object): + """This object represents a Telegram ChatAction.""" + TYPING = 'typing' UPLOAD_PHOTO = 'upload_photo' RECORD_VIDEO = 'upload_video' diff --git a/telegram/contact.py b/telegram/contact.py index 66a5e0954..262b4bc9d 100644 --- a/telegram/contact.py +++ b/telegram/contact.py @@ -16,33 +16,78 @@ # 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 a object that represents a Telegram Contact""" from telegram import TelegramObject class Contact(TelegramObject): + """This object represents a Telegram Contact. + + Attributes: + phone_number (str): + first_name (str): + last_name (str): + user_id (int): + + Args: + phone_number (str): + first_name (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + last_name (Optional[str]): + user_id (Optional[int]): + """ + def __init__(self, phone_number, first_name, - last_name=None, - user_id=None): + **kwargs): + # Required self.phone_number = phone_number self.first_name = first_name - self.last_name = last_name - self.user_id = user_id + # Optionals + self.last_name = kwargs.get('last_name', '') + self.user_id = int(kwargs.get('user_id', 0)) @staticmethod def de_json(data): - return Contact(phone_number=data.get('phone_number', None), - first_name=data.get('first_name', None), - last_name=data.get('last_name', None), - user_id=data.get('user_id', None)) + """ + Args: + data (str): + + Returns: + telegram.Contact: + """ + if not data: + return None + + contact = dict() + + # Required + contact['phone_number'] = data['phone_number'] + contact['first_name'] = data['first_name'] + # Optionals + contact['last_name'] = data.get('last_name') + contact['user_id'] = data.get('user_id', 0) + + return Contact(**contact) def to_dict(self): - data = {'phone_number': self.phone_number, - 'first_name': self.first_name} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['phone_number'] = self.phone_number + data['first_name'] = self.first_name + # Optionals if self.last_name: data['last_name'] = self.last_name if self.user_id: data['user_id'] = self.user_id + return data diff --git a/telegram/document.py b/telegram/document.py index 23921c799..e9f7a4e7a 100644 --- a/telegram/document.py +++ b/telegram/document.py @@ -16,39 +16,77 @@ # 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 a object that represents a Telegram Document""" -from telegram import TelegramObject +from telegram import PhotoSize, TelegramObject class Document(TelegramObject): + """This object represents a Telegram Document. + + Attributes: + file_id (str): + thumb (:class:`telegram.PhotoSize`): + file_name (str): + mime_type (str): + file_size (int): + + Args: + file_id (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + file_name (Optional[str]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + def __init__(self, file_id, - thumb=None, - file_name=None, - mime_type=None, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.thumb = thumb - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size + # Optionals + self.thumb = kwargs.get('thumb') + self.file_name = kwargs.get('file_name', '') + self.mime_type = kwargs.get('mime_type', '') + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - if 'thumb' in data: - from telegram import PhotoSize - thumb = PhotoSize.de_json(data['thumb']) - else: - thumb = None + """ + Args: + data (str): - return Document(file_id=data.get('file_id', None), - thumb=thumb, - file_name=data.get('file_name', None), - mime_type=data.get('mime_type', None), - file_size=data.get('file_size', None)) + Returns: + telegram.Document: + """ + if not data: + return None + + document = dict() + + # Required + document['file_id'] = data['file_id'] + # Optionals + document['thumb'] = PhotoSize.de_json(data.get('thumb')) + document['file_name'] = data.get('file_name') + document['mime_type'] = data.get('mime_type') + document['file_size'] = data.get('file_size', 0) + + return Document(**document) def to_dict(self): - data = {'file_id': self.file_id} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + # Optionals if self.thumb: data['thumb'] = self.thumb.to_dict() if self.file_name: @@ -57,4 +95,5 @@ class Document(TelegramObject): data['mime_type'] = self.mime_type if self.file_size: data['file_size'] = self.file_size + return data diff --git a/telegram/emoji.py b/telegram/emoji.py index 1f1fa47a4..ee10651a5 100644 --- a/telegram/emoji.py +++ b/telegram/emoji.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # flake8: noqa +# pylint: disable=C0103,C0301,R0903 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -17,8 +18,12 @@ # 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 a object that represents an Emoji""" + class Emoji(object): + """This object represents an Emoji.""" + GRINNING_FACE_WITH_SMILING_EYES = b'\xF0\x9F\x98\x81' FACE_WITH_TEARS_OF_JOY = b'\xF0\x9F\x98\x82' SMILING_FACE_WITH_OPEN_MOUTH = b'\xF0\x9F\x98\x83' @@ -155,16 +160,26 @@ class Emoji(object): SQUARED_SOS = b'\xF0\x9F\x86\x98' SQUARED_UP_WITH_EXCLAMATION_MARK = b'\xF0\x9F\x86\x99' SQUARED_VS = b'\xF0\x9F\x86\x9A' - REGIONAL_INDICATOR_SYMBOL_LETTER_D_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_E = b'\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA' - REGIONAL_INDICATOR_SYMBOL_LETTER_G_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_B = b'\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7' - REGIONAL_INDICATOR_SYMBOL_LETTER_C_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_N = b'\xF0\x9F\x87\xA8\xF0\x9F\x87\xB3' - REGIONAL_INDICATOR_SYMBOL_LETTER_J_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_P = b'\xF0\x9F\x87\xAF\xF0\x9F\x87\xB5' - REGIONAL_INDICATOR_SYMBOL_LETTER_K_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R = b'\xF0\x9F\x87\xB0\xF0\x9F\x87\xB7' - REGIONAL_INDICATOR_SYMBOL_LETTER_F_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R = b'\xF0\x9F\x87\xAB\xF0\x9F\x87\xB7' - REGIONAL_INDICATOR_SYMBOL_LETTER_E_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S = b'\xF0\x9F\x87\xAA\xF0\x9F\x87\xB8' - REGIONAL_INDICATOR_SYMBOL_LETTER_I_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_T = b'\xF0\x9F\x87\xAE\xF0\x9F\x87\xB9' - REGIONAL_INDICATOR_SYMBOL_LETTER_U_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S = b'\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8' - REGIONAL_INDICATOR_SYMBOL_LETTER_R_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_U = b'\xF0\x9F\x87\xB7\xF0\x9F\x87\xBA' + REGIONAL_INDICATOR_SYMBOL_LETTER_D_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_E\ + = b'\xF0\x9F\x87\xA9\xF0\x9F\x87\xAA' + REGIONAL_INDICATOR_SYMBOL_LETTER_G_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_B\ + = b'\xF0\x9F\x87\xAC\xF0\x9F\x87\xA7' + REGIONAL_INDICATOR_SYMBOL_LETTER_C_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_N\ + = b'\xF0\x9F\x87\xA8\xF0\x9F\x87\xB3' + REGIONAL_INDICATOR_SYMBOL_LETTER_J_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_P\ + = b'\xF0\x9F\x87\xAF\xF0\x9F\x87\xB5' + REGIONAL_INDICATOR_SYMBOL_LETTER_K_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ + = b'\xF0\x9F\x87\xB0\xF0\x9F\x87\xB7' + REGIONAL_INDICATOR_SYMBOL_LETTER_F_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_R\ + = b'\xF0\x9F\x87\xAB\xF0\x9F\x87\xB7' + REGIONAL_INDICATOR_SYMBOL_LETTER_E_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ + = b'\xF0\x9F\x87\xAA\xF0\x9F\x87\xB8' + REGIONAL_INDICATOR_SYMBOL_LETTER_I_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_T\ + = b'\xF0\x9F\x87\xAE\xF0\x9F\x87\xB9' + REGIONAL_INDICATOR_SYMBOL_LETTER_U_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_S\ + = b'\xF0\x9F\x87\xBA\xF0\x9F\x87\xB8' + REGIONAL_INDICATOR_SYMBOL_LETTER_R_PLUS_REGIONAL_INDICATOR_SYMBOL_LETTER_U\ + = b'\xF0\x9F\x87\xB7\xF0\x9F\x87\xBA' SQUARED_KATAKANA_KOKO = b'\xF0\x9F\x88\x81' SQUARED_KATAKANA_SA = b'\xF0\x9F\x88\x82' SQUARED_CJK_UNIFIED_IDEOGRAPH_7121 = b'\xF0\x9F\x88\x9A' diff --git a/telegram/error.py b/telegram/error.py index 68754a04e..b19b3f905 100644 --- a/telegram/error.py +++ b/telegram/error.py @@ -16,11 +16,16 @@ # 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 a object that represents a Telegram Error""" + class TelegramError(Exception): - """Base class for Telegram errors.""" + """This object represents a Telegram Error.""" @property def message(self): - '''Returns the first argument used to construct this error.''' + """ + Returns: + str: + """ return self.args[0] diff --git a/telegram/groupchat.py b/telegram/groupchat.py index 049d16629..f3190b7db 100644 --- a/telegram/groupchat.py +++ b/telegram/groupchat.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# pylint: disable=C0103,W0622 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,23 +17,59 @@ # 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 a object that represents a Telegram GroupChat""" from telegram import TelegramObject class GroupChat(TelegramObject): + """This object represents a Telegram GroupChat. + + Attributes: + id (int): + title (str): + + Args: + id (int): + title (str): + """ + def __init__(self, id, title): - self.id = id + # Required + self.id = int(id) self.title = title @staticmethod def de_json(data): - return GroupChat(id=data.get('id', None), - title=data.get('title', None)) + """ + Args: + data (str): + + Returns: + telegram.GroupChat: + """ + if not data: + return None + + groupchat = dict() + + # Required + groupchat['id'] = data['id'] + groupchat['title'] = data['title'] + + return GroupChat(**groupchat) def to_dict(self): - data = {'id': self.id, - 'title': self.title} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['id'] = self.id + data['title'] = self.title + return data diff --git a/telegram/inputfile.py b/telegram/inputfile.py index 5d2d00fba..aec76e8b8 100644 --- a/telegram/inputfile.py +++ b/telegram/inputfile.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# pylint: disable=W0622,E0611 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,6 +17,7 @@ # 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 a object that represents a Telegram InputFile""" try: from email.generator import _make_boundary as choose_boundary @@ -29,7 +31,7 @@ import os import sys import imghdr -from .error import TelegramError +from telegram import TelegramError DEFAULT_MIME_TYPE = 'application/octet-stream' USER_AGENT = 'Python Telegram Bot' \ @@ -37,6 +39,8 @@ USER_AGENT = 'Python Telegram Bot' \ class InputFile(object): + """This object represents a Telegram InputFile.""" + def __init__(self, data): self.data = data @@ -71,14 +75,26 @@ class InputFile(object): @property def headers(self): + """ + Returns: + str: + """ return {'User-agent': USER_AGENT, 'Content-type': self.content_type} @property def content_type(self): + """ + Returns: + str: + """ return 'multipart/form-data; boundary=%s' % self.boundary def to_form(self): + """ + Returns: + str: + """ form = [] form_boundary = '--' + self.boundary @@ -105,9 +121,14 @@ class InputFile(object): form.append('--' + self.boundary + '--') form.append('') - return self._parse(form) + return InputFile._parse(form) - def _parse(self, form): + @staticmethod + def _parse(form): + """ + Returns: + str: + """ if sys.version_info > (3,): # on Python 3 form needs to be byte encoded encoded_form = [] @@ -125,11 +146,10 @@ class InputFile(object): """Check if the content file is an image by analyzing its headers. Args: - stream: - A str representing the content of a file. + stream (str): A str representing the content of a file. Returns: - The str mimetype of an image. + str: The str mimetype of an image. """ image = imghdr.what(None, stream) if image: @@ -142,8 +162,7 @@ class InputFile(object): """Check if the request is a file request. Args: - data: - A dict of (str, unicode) key/value pairs + data (str): A dict of (str, unicode) key/value pairs Returns: bool diff --git a/telegram/location.py b/telegram/location.py index 643c135d1..e51610569 100644 --- a/telegram/location.py +++ b/telegram/location.py @@ -16,23 +16,57 @@ # 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 a object that represents a Telegram Location""" from telegram import TelegramObject class Location(TelegramObject): + """This object represents a Telegram Sticker. + + Attributes: + longitude (float): + latitude (float): + + Args: + longitude (float): + latitude (float): + """ + def __init__(self, longitude, latitude): - self.longitude = longitude - self.latitude = latitude + # Required + self.longitude = float(longitude) + self.latitude = float(latitude) @staticmethod def de_json(data): - return Location(longitude=data.get('longitude', None), - latitude=data.get('latitude', None)) + """ + Args: + data (str): + + Returns: + telegram.Location: + """ + if not data: + return None + + location = dict() + + location['longitude'] = data['longitude'] + location['latitude'] = data['latitude'] + + return Location(**location) def to_dict(self): - data = {'longitude': self.longitude, - 'latitude': self.latitude} + """ + Returns: + dict: + """ + data = dict() + + data['longitude'] = self.longitude + data['latitude'] = self.latitude + return data diff --git a/telegram/message.py b/telegram/message.py index f50c2377f..7c606d4fc 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -# pylint: disable=too-many-instance-attributes -# pylint: disable=too-many-arguments,too-many-branches +# pylint: disable=R0902,R0912,R0913 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -92,29 +91,31 @@ class Message(TelegramObject): date, chat, **kwargs): - self.message_id = message_id + # Required + self.message_id = int(message_id) self.from_user = from_user self.date = date self.chat = chat + # Optionals self.forward_from = kwargs.get('forward_from') self.forward_date = kwargs.get('forward_date') self.reply_to_message = kwargs.get('reply_to_message') - self.text = kwargs.get('text') + self.text = kwargs.get('text', '') self.audio = kwargs.get('audio') self.document = kwargs.get('document') self.photo = kwargs.get('photo') self.sticker = kwargs.get('sticker') self.video = kwargs.get('video') self.voice = kwargs.get('voice') - self.caption = kwargs.get('caption') + self.caption = kwargs.get('caption', '') self.contact = kwargs.get('contact') self.location = kwargs.get('location') self.new_chat_participant = kwargs.get('new_chat_participant') self.left_chat_participant = kwargs.get('left_chat_participant') - self.new_chat_title = kwargs.get('new_chat_title') + self.new_chat_title = kwargs.get('new_chat_title', '') self.new_chat_photo = kwargs.get('new_chat_photo') - self.delete_chat_photo = kwargs.get('delete_chat_photo') - self.group_chat_created = kwargs.get('group_chat_created') + self.delete_chat_photo = bool(kwargs.get('delete_chat_photo', False)) + self.group_chat_created = bool(kwargs.get('group_chat_created', False)) @property def chat_id(self): @@ -130,75 +131,58 @@ class Message(TelegramObject): Returns: telegram.Message: """ + if not data: + return None + message = dict() - message['message_id'] = int(data['message_id']) + # Required + message['message_id'] = data['message_id'] message['from_user'] = User.de_json(data['from']) message['date'] = datetime.fromtimestamp(data['date']) - - if 'first_name' in data['chat']: - message['chat'] = User.de_json(data['chat']) - elif 'title' in data['chat']: - message['chat'] = GroupChat.de_json(data['chat']) - - if 'forward_from' in data: - message['forward_from'] = User.de_json(data['forward_from']) - - if 'forward_date' in data: - message['forward_date'] = \ - datetime.fromtimestamp(data['forward_date']) - - message['text'] = data.get('text', '') - - if 'reply_to_message' in data: - message['reply_to_message'] = \ - Message.de_json(data['reply_to_message']) - - if 'audio' in data: - message['audio'] = Audio.de_json(data['audio']) - - if 'document' in data: - message['document'] = Document.de_json(data['document']) - - if 'photo' in data: - message['photo'] = [PhotoSize.de_json(x) for x in data['photo']] - - if 'sticker' in data: - message['sticker'] = Sticker.de_json(data['sticker']) - - if 'video' in data: - message['video'] = Video.de_json(data['video']) - - if 'voice' in data: - message['voice'] = Voice.de_json(data['voice']) - - message['caption'] = data.get('caption', '') - - if 'contact' in data: - message['contact'] = Contact.de_json(data['contact']) - - if 'location' in data: - message['location'] = Location.de_json(data['location']) - - if 'new_chat_participant' in data: - message['new_chat_participant'] = \ - User.de_json(data['new_chat_participant']) - - if 'left_chat_participant' in data: - message['left_chat_participant'] = \ - User.de_json(data['left_chat_participant']) - - message['new_chat_title'] = data.get('new_chat_title', '') - - if 'new_chat_photo' in data: - message['new_chat_photo'] = \ - [PhotoSize.de_json(x) for x in data['new_chat_photo']] - + # Optionals + if 'first_name' in data.get('chat', ''): + message['chat'] = User.de_json(data.get('chat')) + elif 'title' in data.get('chat', ''): + message['chat'] = GroupChat.de_json(data.get('chat')) + message['forward_from'] = \ + User.de_json(data.get('forward_from')) + message['forward_date'] = \ + Message._fromtimestamp(data.get('forward_date')) + message['reply_to_message'] = \ + Message.de_json(data.get('reply_to_message')) + message['text'] = \ + data.get('text') + message['audio'] = \ + Audio.de_json(data.get('audio')) + message['document'] = \ + Document.de_json(data.get('document')) + message['photo'] = \ + [PhotoSize.de_json(x) for x in data.get('photo', [])] + message['sticker'] = \ + Sticker.de_json(data.get('sticker')) + message['video'] = \ + Video.de_json(data.get('video')) + message['voice'] = \ + Voice.de_json(data.get('voice')) + message['caption'] = \ + data.get('caption') + message['contact'] = \ + Contact.de_json(data.get('contact')) + message['location'] = \ + Location.de_json(data.get('location')) + message['new_chat_participant'] = \ + User.de_json(data.get('new_chat_participant')) + message['left_chat_participant'] = \ + User.de_json(data.get('left_chat_participant')) + message['new_chat_title'] = \ + data.get('new_chat_title') + message['new_chat_photo'] = \ + [PhotoSize.de_json(x) for x in data.get('new_chat_photo', [])] message['delete_chat_photo'] = \ - bool(data.get('delete_chat_photo', False)) - + data.get('delete_chat_photo') message['group_chat_created'] = \ - bool(data.get('group_chat_created', False)) + data.get('group_chat_created') return Message(**message) @@ -207,71 +191,70 @@ class Message(TelegramObject): Returns: dict: """ - data = {'message_id': self.message_id, - 'from': self.from_user.to_dict(), - 'date': self._totimestamp(self.date), - 'chat': self.chat.to_dict()} + data = dict() + # Required + data['message_id'] = self.message_id + data['from'] = self.from_user.to_dict() + data['date'] = self._totimestamp(self.date) + data['chat'] = self.chat.to_dict() + # Optionals if self.forward_from: data['forward_from'] = self.forward_from.to_dict() - if self.forward_date: data['forward_date'] = self._totimestamp(self.forward_date) - if self.reply_to_message: data['reply_to_message'] = self.reply_to_message.to_dict() - if self.text: data['text'] = self.text - if self.audio: data['audio'] = self.audio.to_dict() - if self.document: data['document'] = self.document.to_dict() - if self.photo: data['photo'] = [p.to_dict() for p in self.photo] - if self.sticker: data['sticker'] = self.sticker.to_dict() - if self.video: data['video'] = self.video.to_dict() - if self.voice: data['voice'] = self.voice.to_dict() - if self.caption: data['caption'] = self.caption - if self.contact: data['contact'] = self.contact.to_dict() - if self.location: data['location'] = self.location.to_dict() - if self.new_chat_participant: data['new_chat_participant'] = self.new_chat_participant.to_dict() - if self.left_chat_participant: data['left_chat_participant'] = \ self.left_chat_participant.to_dict() - if self.new_chat_title: data['new_chat_title'] = self.new_chat_title - if self.new_chat_photo: data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] - if self.delete_chat_photo: data['delete_chat_photo'] = self.delete_chat_photo - if self.group_chat_created: data['group_chat_created'] = self.group_chat_created return data + @staticmethod + def _fromtimestamp(unixtime): + """ + Args: + unixtime (int): + + Returns: + datetime.datetime: + """ + if not unixtime: + return None + + return datetime.fromtimestamp(unixtime) + @staticmethod def _totimestamp(dt_obj): """ @@ -281,6 +264,9 @@ class Message(TelegramObject): Returns: int: """ + if not dt_obj: + return None + try: # Python 3.3+ return int(dt_obj.timestamp()) diff --git a/telegram/nullhandler.py b/telegram/nullhandler.py index a51ccf358..50bd370eb 100644 --- a/telegram/nullhandler.py +++ b/telegram/nullhandler.py @@ -16,10 +16,17 @@ # 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 a object that represents a logging NullHandler""" import logging class NullHandler(logging.Handler): + """This object represents a logging NullHandler.""" + def emit(self, record): + """ + Args: + record (str): + """ pass diff --git a/telegram/photosize.py b/telegram/photosize.py index 54ce4effd..c528e7ce9 100644 --- a/telegram/photosize.py +++ b/telegram/photosize.py @@ -16,32 +16,78 @@ # 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 a object that represents a Telegram PhotoSize""" from telegram import TelegramObject class PhotoSize(TelegramObject): + """This object represents a Telegram PhotoSize. + + Attributes: + file_id (str): + width (int): + height (int): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + file_size (Optional[int]): + """ + def __init__(self, file_id, width, height, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.width = width - self.height = height - self.file_size = file_size + self.width = int(width) + self.height = int(height) + # Optionals + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - return PhotoSize(file_id=data.get('file_id', None), - width=data.get('width', None), - height=data.get('height', None), - file_size=data.get('file_size', None)) + """ + Args: + data (str): + + Returns: + telegram.PhotoSize: + """ + if not data: + return None + + photosize = dict() + + # Required + photosize['file_id'] = data['file_id'] + photosize['width'] = data['width'] + photosize['height'] = data['height'] + # Optionals + photosize['file_size'] = data.get('file_size', 0) + + return PhotoSize(**photosize) def to_dict(self): - data = {'file_id': self.file_id, - 'width': self.width, - 'height': self.height} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + data['width'] = self.width + data['height'] = self.height + # Optionals if self.file_size: data['file_size'] = self.file_size + return data diff --git a/telegram/sticker.py b/telegram/sticker.py index eae52d25f..cf7e9c7a8 100644 --- a/telegram/sticker.py +++ b/telegram/sticker.py @@ -16,42 +16,84 @@ # 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 a object that represents a Telegram Sticker""" -from telegram import TelegramObject +from telegram import PhotoSize, TelegramObject class Sticker(TelegramObject): + """This object represents a Telegram Sticker. + + Attributes: + file_id (str): + width (int): + height (int): + thumb (:class:`telegram.PhotoSize`): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + file_size (Optional[int]): + """ + def __init__(self, file_id, width, height, - thumb=None, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.width = width - self.height = height - self.thumb = thumb - self.file_size = file_size + self.width = int(width) + self.height = int(height) + # Optionals + self.thumb = kwargs.get('thumb') + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - if 'thumb' in data: - from telegram import PhotoSize - thumb = PhotoSize.de_json(data['thumb']) - else: - thumb = None + """ + Args: + data (str): - return Sticker(file_id=data.get('file_id', None), - width=data.get('width', None), - height=data.get('height', None), - thumb=thumb, - file_size=data.get('file_size', None)) + Returns: + telegram.Sticker: + """ + if not data: + return None + + sticker = dict() + + # Required + sticker['file_id'] = data['file_id'] + sticker['width'] = data['width'] + sticker['height'] = data['height'] + # Optionals + sticker['thumb'] = PhotoSize.de_json(data['thumb']) + sticker['file_size'] = data.get('file_size', 0) + + return Sticker(**sticker) def to_dict(self): - data = {'file_id': self.file_id, - 'width': self.width, - 'height': self.height, - 'thumb': self.thumb.to_dict()} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + data['width'] = self.width + data['height'] = self.height + # Optionals + if self.thumb: + data['thumb'] = self.thumb.to_dict() if self.file_size: data['file_size'] = self.file_size + return data diff --git a/telegram/update.py b/telegram/update.py index 362f52f64..9d2a9dfa5 100644 --- a/telegram/update.py +++ b/telegram/update.py @@ -16,30 +16,60 @@ # 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 a object that represents a Telegram Update""" -from telegram import TelegramObject +from telegram import Message, TelegramObject class Update(TelegramObject): + """This object represents a Telegram Update. + + Attributes: + update_id (int): + message (:class:`telegram.Message`): + + Args: + update_id (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + message (Optional[:class:`telegram.Message`]): + """ def __init__(self, update_id, - message=None): + **kwargs): + # Required self.update_id = update_id - self.message = message + # Optionals + self.message = kwargs.get('message') @staticmethod def de_json(data): - if 'message' in data: - from telegram import Message - message = Message.de_json(data['message']) - else: - message = None + """ + Args: + data (str): - return Update(update_id=data.get('update_id', None), - message=message) + Returns: + telegram.Update: + """ + update = dict() + + update['update_id'] = data['update_id'] + update['message'] = Message.de_json(data['message']) + + return Update(**update) def to_dict(self): - data = {'update_id': self.update_id} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['update_id'] = self.update_id + # Optionals if self.message: data['message'] = self.message.to_dict() + return data diff --git a/telegram/user.py b/telegram/user.py index eaff7f2e0..2f36c0f14 100644 --- a/telegram/user.py +++ b/telegram/user.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# pylint: disable=C0103,W0622 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,23 +17,44 @@ # 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 a object that represents a Telegram User""" from telegram import TelegramObject class User(TelegramObject): + """This object represents a Telegram Sticker. + + Attributes: + id (int): + first_name (str): + last_name (str): + username (str): + + Args: + id (int): + first_name (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + last_name (Optional[str]): + username (Optional[str]): + """ + def __init__(self, id, first_name, - last_name=None, - username=None): - self.id = id + **kwargs): + # Required + self.id = int(id) self.first_name = first_name - self.last_name = last_name - self.username = username + # Optionals + self.last_name = kwargs.get('last_name', '') + self.username = kwargs.get('username', '') @property def name(self): + """str: """ if self.username: return '@%s' % self.username if self.last_name: @@ -41,16 +63,41 @@ class User(TelegramObject): @staticmethod def de_json(data): - return User(id=data.get('id', None), - first_name=data.get('first_name', None), - last_name=data.get('last_name', None), - username=data.get('username', None)) + """ + Args: + data (str): + + Returns: + telegram.User: + """ + if not data: + return None + + user = dict() + + # Required + user['id'] = data['id'] + user['first_name'] = data['first_name'] + # Optionals + user['last_name'] = data.get('last_name') + user['username'] = data.get('username') + + return User(**user) def to_dict(self): - data = {'id': self.id, - 'first_name': self.first_name} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['id'] = self.id + data['first_name'] = self.first_name + # Optionals if self.last_name: data['last_name'] = self.last_name if self.username: data['username'] = self.username + return data diff --git a/telegram/userprofilephotos.py b/telegram/userprofilephotos.py index 6e57b495f..9c9871cd1 100644 --- a/telegram/userprofilephotos.py +++ b/telegram/userprofilephotos.py @@ -16,36 +16,64 @@ # 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 a object that represents a Telegram +UserProfilePhotos""" -from telegram import TelegramObject +from telegram import PhotoSize, TelegramObject class UserProfilePhotos(TelegramObject): + """This object represents a Telegram UserProfilePhotos. + + Attributes: + total_count (int): + photos (List[List[:class:`telegram.PhotoSize`]]): + + Args: + total_count (int): + photos (List[List[:class:`telegram.PhotoSize`]]): + """ + def __init__(self, total_count, photos): - self.total_count = total_count + # Required + self.total_count = int(total_count) self.photos = photos @staticmethod def de_json(data): - if 'photos' in data: - from telegram import PhotoSize - photos = [] - for photo in data['photos']: - photos.append([PhotoSize.de_json(x) for x in photo]) - else: - photos = None + """ + Args: + data (str): - return UserProfilePhotos(total_count=data.get('total_count', None), - photos=photos) + Returns: + telegram.UserProfilePhotos: + """ + if not data: + return None + + upf = dict() + + # Required + upf['total_count'] = data['total_count'] + upf['photos'] = [] + for photo in data['photos']: + upf['photos'].append([PhotoSize.de_json(x) for x in photo]) + + return UserProfilePhotos(**upf) def to_dict(self): - data = {} - if self.total_count: - data['total_count'] = self.total_count - if self.photos: - data['photos'] = [] - for photo in self.photos: - data['photos'].append([x.to_dict() for x in photo]) + """ + Returns: + dict: + """ + data = dict() + + # Required + data['total_count'] = self.total_count + data['photos'] = [] + for photo in self.photos: + data['photos'].append([x.to_dict() for x in photo]) + return data diff --git a/telegram/utils/botan.py b/telegram/utils/botan.py index e69de29bb..ff2b6f82c 100644 --- a/telegram/utils/botan.py +++ b/telegram/utils/botan.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# A library that provides a Python interface to the Telegram Bot API +# Copyright (C) 2015 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/]. + +import json +try: + from urllib.parse import urlencode + from urllib.request import urlopen, Request + from urllib.error import HTTPError, URLError +except ImportError: + from urllib import urlencode + from urllib2 import urlopen, Request + from urllib2 import HTTPError, URLError + +DEFAULT_BASE_URL = \ + 'https://api.botan.io/track?token=%(token)&uid=%(uid)&name=%(name)' +USER_AGENT = 'Python Telegram Bot' \ + ' (https://github.com/leandrotoledo/python-telegram-bot)' +CONTENT_TYPE = 'application/json' + +class Botan(Object): + def __init__(self, + token, + base_url=None): + self.token = token + + if base_url is None: + self.base_url = DEFAULT_BASE_URL % {'token': self.token} + else: + self.base_url = base_url % {'token': self.token} + + def track(self, + uid, + text, + name = 'Message'): + + url = self.base_url % {'uid': uid, + 'message': text, + 'name': name} + + self._post(url, message) + + def _post(self, + url, + data): + headers = {'User-agent': USER_AGENT, + 'Content-type': CONTENT_TYPE} + + try: + request = Request( + url, + data=urlencode(data).encode(), + headers=headers + ) + + return urlopen(request).read() + except IOError as e: + raise TelegramError(str(e)) + except HTTPError as e: + raise TelegramError(str(e)) diff --git a/telegram/video.py b/telegram/video.py index a75c4a931..ca5caf29e 100644 --- a/telegram/video.py +++ b/telegram/video.py @@ -16,52 +16,96 @@ # 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 a object that represents a Telegram Video""" -from telegram import TelegramObject +from telegram import PhotoSize, TelegramObject class Video(TelegramObject): + """This object represents a Telegram Video. + + Attributes: + file_id (str): + width (int): + height (int): + duration (int): + thumb (:class:`telegram.PhotoSize`): + mime_type (str): + file_size (int): + + Args: + file_id (str): + width (int): + height (int): + duration (int): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + thumb (Optional[:class:`telegram.PhotoSize`]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + def __init__(self, file_id, width, height, duration, - thumb=None, - mime_type=None, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.width = width - self.height = height - self.duration = duration - self.thumb = thumb - self.mime_type = mime_type - self.file_size = file_size + self.width = int(width) + self.height = int(height) + self.duration = int(duration) + # Optionals + self.thumb = kwargs.get('thumb') + self.mime_type = kwargs.get('mime_type', '') + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - if 'thumb' in data: - from telegram import PhotoSize - thumb = PhotoSize.de_json(data['thumb']) - else: - thumb = None + """ + Args: + data (str): - return Video(file_id=data.get('file_id', None), - width=data.get('width', None), - height=data.get('height', None), - duration=data.get('duration', None), - thumb=thumb, - mime_type=data.get('mime_type', None), - file_size=data.get('file_size', None)) + Returns: + telegram.Video: + """ + if not data: + return None + + video = dict() + + # Required + video['file_id'] = data['file_id'] + video['width'] = data['width'] + video['height'] = data['height'] + video['duration'] = data['duration'] + # Optionals + video['thumb'] = PhotoSize.de_json(data.get('thumb')) + video['mime_type'] = data.get('mime_type') + video['file_size'] = data.get('file_size', 0) + + return Video(**video) def to_dict(self): - data = {'file_id': self.file_id, - 'width': self.width, - 'height': self.height, - 'duration': self.duration} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + data['width'] = self.width + data['height'] = self.height + data['duration'] = self.duration + # Optionals if self.thumb: data['thumb'] = self.thumb.to_dict() if self.mime_type: data['mime_type'] = self.mime_type if self.file_size: data['file_size'] = self.file_size + return data diff --git a/telegram/voice.py b/telegram/voice.py index a628b04e6..fc4baec37 100644 --- a/telegram/voice.py +++ b/telegram/voice.py @@ -16,34 +16,78 @@ # 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 a object that represents a Telegram Voice""" from telegram import TelegramObject class Voice(TelegramObject): + """This object represents a Telegram Voice. + + Attributes: + file_id (str): + duration (int): + mime_type (str): + file_size (int): + + Args: + file_id (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + duration (Optional[int]): + mime_type (Optional[str]): + file_size (Optional[int]): + """ + def __init__(self, file_id, - duration=None, - mime_type=None, - file_size=None): + **kwargs): + # Required self.file_id = file_id - self.duration = duration - self.mime_type = mime_type - self.file_size = file_size + # Optionals + self.duration = int(kwargs.get('duration', 0)) + self.mime_type = kwargs.get('mime_type', '') + self.file_size = int(kwargs.get('file_size', 0)) @staticmethod def de_json(data): - return Voice(file_id=data.get('file_id', None), - duration=data.get('duration', None), - mime_type=data.get('mime_type', None), - file_size=data.get('file_size', None)) + """ + Args: + data (str): + + Returns: + telegram.Voice: + """ + if not data: + return None + + voice = dict() + + # Required + voice['file_id'] = data['file_id'] + # Optionals + voice['duration'] = data.get('duration', 0) + voice['mime_type'] = data.get('mime_type') + voice['file_size'] = data.get('file_size', 0) + + return Voice(**voice) def to_dict(self): - data = {'file_id': self.file_id} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['file_id'] = self.file_id + # Optionals if self.duration: data['duration'] = self.duration if self.mime_type: data['mime_type'] = self.mime_type if self.file_size: data['file_size'] = self.file_size + return data From d43e292499836c4fc66afd2943d4455fcc37b002 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 21 Aug 2015 23:22:58 -0300 Subject: [PATCH 04/20] Adding required comments --- telegram/location.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/telegram/location.py b/telegram/location.py index e51610569..b5148b7ea 100644 --- a/telegram/location.py +++ b/telegram/location.py @@ -54,6 +54,7 @@ class Location(TelegramObject): location = dict() + # Required location['longitude'] = data['longitude'] location['latitude'] = data['latitude'] @@ -66,6 +67,7 @@ class Location(TelegramObject): """ data = dict() + # Required data['longitude'] = self.longitude data['latitude'] = self.latitude From 25f9eb789890a1a007d3310b0685c13880b12ecc Mon Sep 17 00:00:00 2001 From: Masoud Naservand Date: Fri, 28 Aug 2015 18:08:03 +0430 Subject: [PATCH 05/20] Extended the Bot class. Added a dispatching mechanism, and decorators to add command to the bot the flask routing style, e.g: >>> @bot.command('/start') ... def start(command, user_id): ... return ('Hello, there', None, None) --- telegram/enchancedbot.py | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 telegram/enchancedbot.py diff --git a/telegram/enchancedbot.py b/telegram/enchancedbot.py new file mode 100644 index 000000000..79606ca2c --- /dev/null +++ b/telegram/enchancedbot.py @@ -0,0 +1,83 @@ +import telegram + + +class NoSuchCommandException(BaseException): + pass + +class CommandDispatcher: + def __init__(self,): + self.commands = list() + self.default = None + + def addCommand(self, command, callback): + self.commands.append((command, callback)) + + def setDefault(self, callback): + self.default = callback + + def dispatch(self, update): + if hasattr(update.message, 'text'): + text = update.message.text + else: + text = '' + + user_id = update.message.from_user.id + com = text.split('@')[0] + for command, callback in self.commands: + if com == command: + return callback(command, user_id) + if self.default is not None: + return self.default(text, user_id) + else: + raise NoSuchCommandException() + + +class EnhancedBot(telegram.Bot): + """The Bot class with command dispatcher added. + + >>> bot = EnhancedBot(token=TOKEN) + >>> @bot.command('/start') + ... def start(command, user_id): + ... # should return a tuple: (text, reply_id, custom_keyboard) + ... return ("Hello, there! Your id is {}".format(user_id), None, None) + >>> while True: + ... bot.processUpdates() + ... time.sleep(3) + """ + def __init__(self, token): + self.dispatcher = CommandDispatcher() + telegram.Bot.__init__(self, token=token) + self.offset = 0 #id of the last processed update + + def command(self, *names, default=False): + """Decorator for adding callbacks for commands.""" + + def inner_command(callback): + for name in names: + self.dispatcher.addCommand(name, callback) + if default: + self.dispatcher.setDefault(callback) + return callback # doesn't touch the callback, so we can use it + return inner_command + + def processUpdates(self): + updates = self.getUpdates(offset=self.offset) + + for update in updates: + print('processing update: {}'.format(str(update.to_dict()))) + self.offset = update.update_id + 1 + if not hasattr(update, 'message'): + continue + + try: + answer, reply_to, reply_markup = self.dispatcher.dispatch(update) + except Exception as e: + print('error occured') # TODO logging + print(update.to_dict()) + raise e + + if answer is not None: + self.sendMessage(chat_id=update.message.chat_id, + text=answer, + reply_to_message_id=reply_to, + reply_markup=reply_markup) From b20f5af1e1910be23284f5f5a328bbc96c63bc79 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 12:19:30 -0300 Subject: [PATCH 06/20] Improving the design of existing Telegram classes and adding docstrings --- telegram/__init__.py | 1 + telegram/base.py | 18 +++++- telegram/bot.py | 103 ++++++++++++++++++++++---------- telegram/forcereply.py | 51 ++++++++++++++-- telegram/replykeyboardhide.py | 52 ++++++++++++++-- telegram/replykeyboardmarkup.py | 68 ++++++++++++++++----- telegram/replymarkup.py | 10 +++- 7 files changed, 242 insertions(+), 61 deletions(-) diff --git a/telegram/__init__.py b/telegram/__init__.py index 0c363c334..78e0dad8c 100644 --- a/telegram/__init__.py +++ b/telegram/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +"""A library that provides a Python interface to the Telegram Bot API""" __author__ = 'leandrotoledodesouza@gmail.com' __version__ = '2.7.1' diff --git a/telegram/base.py b/telegram/base.py index c6c3e4c47..8158abfbd 100644 --- a/telegram/base.py +++ b/telegram/base.py @@ -16,13 +16,14 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Base class for Telegram Objects""" import json from abc import ABCMeta, abstractmethod class TelegramObject(object): - """Base class for most telegram object""" + """Base class for most telegram objects""" __metaclass__ = ABCMeta @@ -34,11 +35,26 @@ class TelegramObject(object): @staticmethod def de_json(data): + """ + Args: + data (str): + + Returns: + telegram.TelegramObject: + """ raise NotImplementedError def to_json(self): + """ + Returns: + str: + """ return json.dumps(self.to_dict()) @abstractmethod def to_dict(self): + """ + Returns: + dict: + """ return None diff --git a/telegram/bot.py b/telegram/bot.py index 8264651e7..502c35b39 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# pylint: disable=E0611,E0213,E1102,C0103,E1101,W0613,R0913,R0904 # # A library that provides a Python interface to the Telegram Bot API # Copyright (C) 2015 Leandro Toledo de Souza @@ -16,6 +17,7 @@ # 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 a object that represents a Telegram Bot""" import json try: @@ -32,11 +34,27 @@ import logging from telegram import (User, Message, Update, UserProfilePhotos, TelegramError, ReplyMarkup, InputFile, TelegramObject, NullHandler) -h = NullHandler() -logging.getLogger(__name__).addHandler(h) +H = NullHandler() +logging.getLogger(__name__).addHandler(H) class Bot(TelegramObject): + """This object represents a Telegram Bot. + + Attributes: + id (int): + first_name (str): + last_name (str): + username (str): + name (str): + + Args: + token (str): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + base_url (Optional[str]): + """ def __init__(self, token, @@ -50,11 +68,17 @@ class Bot(TelegramObject): self.bot = None - self.log = logging.getLogger(__name__) + self.logger = logging.getLogger(__name__) def info(func): + """ + bla + """ @functools.wraps(func) def decorator(self, *args, **kwargs): + """ + bla + """ if not self.bot: self.getMe() @@ -65,36 +89,48 @@ class Bot(TelegramObject): @property @info def id(self): + """int: """ return self.bot.id @property @info def first_name(self): + """str: """ return self.bot.first_name @property @info def last_name(self): + """str: """ return self.bot.last_name @property @info def username(self): + """str: """ return self.bot.username @property def name(self): + """str: """ return '@%s' % self.username def log(func): + """ + Returns: + A telegram.Message instance representing the message posted. + """ logger = logging.getLogger(func.__module__) @functools.wraps(func) def decorator(self, *args, **kwargs): - logger.debug('Entering: %s' % func.__name__) + """ + decorator + """ + logger.debug('Entering: %s', func.__name__) result = func(self, *args, **kwargs) logger.debug(result) - logger.debug('Exiting: %s' % func.__name__) + logger.debug('Exiting: %s', func.__name__) return result return decorator @@ -105,6 +141,9 @@ class Bot(TelegramObject): """ @functools.wraps(func) def decorator(self, *args, **kwargs): + """ + decorator + """ url, data = func(self, *args, **kwargs) if kwargs.get('reply_to_message_id'): @@ -118,8 +157,8 @@ class Bot(TelegramObject): else: data['reply_markup'] = reply_markup - json_data = self._requestUrl(url, 'POST', data=data) - data = self._parseAndCheckTelegram(json_data) + json_data = Bot._requestUrl(url, 'POST', data=data) + data = Bot._parseAndCheckTelegram(json_data) if data is True: return data @@ -150,8 +189,7 @@ class Bot(TelegramObject): chat_id, text, disable_web_page_preview=None, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send text messages. Args: @@ -222,8 +260,7 @@ class Bot(TelegramObject): chat_id, photo, caption=None, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send photos. Args: @@ -265,8 +302,7 @@ class Bot(TelegramObject): duration=None, performer=None, title=None, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send audio files, if you want Telegram clients to display them in the music player. Your audio must be in an .mp3 format. On success, the sent Message is returned. Bots can currently send audio @@ -321,8 +357,7 @@ class Bot(TelegramObject): def sendDocument(self, chat_id, document, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send general files. Args: @@ -355,8 +390,7 @@ class Bot(TelegramObject): def sendSticker(self, chat_id, sticker, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send .webp stickers. Args: @@ -391,8 +425,7 @@ class Bot(TelegramObject): video, duration=None, caption=None, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send video files, Telegram clients support mp4 videos (other formats may be sent as telegram.Document). @@ -437,8 +470,7 @@ class Bot(TelegramObject): chat_id, voice, duration=None, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send audio files, if you want Telegram clients to display the file as a playable voice message. For this to work, your audio must be in an .ogg file encoded with OPUS (other formats may be @@ -482,8 +514,7 @@ class Bot(TelegramObject): chat_id, latitude, longitude, - reply_to_message_id=None, - reply_markup=None): + **kwargs): """Use this method to send point on the map. Args: @@ -617,10 +648,10 @@ class Bot(TelegramObject): data = self._parseAndCheckTelegram(json_data) if data: - self.log.info( - 'Getting updates: %s' % [u['update_id'] for u in data]) + self.logger.info( + 'Getting updates: %s', [u['update_id'] for u in data]) else: - self.log.info('No new updates found.') + self.logger.info('No new updates found.') return [Update.de_json(x) for x in data] @@ -650,8 +681,8 @@ class Bot(TelegramObject): return True - def _requestUrl(self, - url, + @staticmethod + def _requestUrl(url, method, data=None): """Request an URL. @@ -688,12 +719,12 @@ class Bot(TelegramObject): url, urlencode(data).encode() ).read() - except IOError as e: - raise TelegramError(str(e)) except HTTPError as e: raise TelegramError(str(e)) except URLError as e: raise TelegramError(str(e)) + except IOError as e: + raise TelegramError(str(e)) if method == 'GET': try: @@ -701,8 +732,8 @@ class Bot(TelegramObject): except URLError as e: raise TelegramError(str(e)) - def _parseAndCheckTelegram(self, - json_data): + @staticmethod + def _parseAndCheckTelegram(json_data): """Try and parse the JSON returned from Telegram and return an empty dictionary if there is any error. @@ -725,7 +756,15 @@ class Bot(TelegramObject): return data['result'] + @staticmethod + def de_json(data): + pass + def to_dict(self): + """ + Returns: + dict: + """ data = {'id': self.id, 'username': self.username, 'first_name': self.username} diff --git a/telegram/forcereply.py b/telegram/forcereply.py index 8e163d6b3..7e8893441 100644 --- a/telegram/forcereply.py +++ b/telegram/forcereply.py @@ -16,24 +16,63 @@ # 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 a object that represents a Telegram ForceReply""" from telegram import ReplyMarkup class ForceReply(ReplyMarkup): + """This object represents a Telegram ForceReply. + + Attributes: + force_reply (bool): + selective (bool): + + Args: + force_reply (bool): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + selective (Optional[bool]): + """ + def __init__(self, force_reply=True, - selective=None): - self.force_reply = force_reply - self.selective = selective + **kwargs): + # Required + self.force_reply = bool(force_reply) + # Optionals + self.selective = bool(kwargs.get('selective', False)) @staticmethod def de_json(data): - return ForceReply(force_reply=data.get('force_reply', None), - selective=data.get('selective', None)) + """ + Args: + data (str): + + Returns: + telegram.ForceReply: + """ + force_reply = dict() + + # Required + force_reply['force_reply'] = data['force_reply'] + # Optionals + force_reply['selective'] = data.get('selective', False) + + return ForceReply(**force_reply) def to_dict(self): - data = {'force_reply': self.force_reply} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['force_reply'] = self.force_reply + # Optionals if self.selective: data['selective'] = self.selective + return data diff --git a/telegram/replykeyboardhide.py b/telegram/replykeyboardhide.py index 084e6cd0a..7e0dd364e 100644 --- a/telegram/replykeyboardhide.py +++ b/telegram/replykeyboardhide.py @@ -16,24 +16,64 @@ # 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 a object that represents a Telegram +ReplyKeyboardHide""" from telegram import ReplyMarkup class ReplyKeyboardHide(ReplyMarkup): + """This object represents a Telegram ReplyKeyboardHide. + + Attributes: + hide_keyboard (bool): + selective (bool): + + Args: + hide_keyboard (bool): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + selective (Optional[bool]): + """ + def __init__(self, hide_keyboard=True, - selective=None): - self.hide_keyboard = hide_keyboard - self.selective = selective + **kwargs): + # Required + self.hide_keyboard = bool(hide_keyboard) + # Optionals + self.selective = bool(kwargs.get('selective', False)) @staticmethod def de_json(data): - return ReplyKeyboardHide(hide_keyboard=data.get('hide_keyboard', None), - selective=data.get('selective', None)) + """ + Args: + data (str): + + Returns: + telegram.ReplyKeyboardHide: + """ + rkh = dict() + + # Required + rkh['hide_keyboard'] = data['hide_keyboard'] + # Optionals + rkh['selective'] = data.get('selective', False) + + return ReplyKeyboardHide(**rkh) def to_dict(self): - data = {'hide_keyboard': self.hide_keyboard} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['hide_keyboard'] = self.hide_keyboard + # Optionals if self.selective: data['selective'] = self.selective + return data diff --git a/telegram/replykeyboardmarkup.py b/telegram/replykeyboardmarkup.py index e9175475d..dfd9aa2c8 100644 --- a/telegram/replykeyboardmarkup.py +++ b/telegram/replykeyboardmarkup.py @@ -16,38 +16,76 @@ # 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 a object that represents a Telegram +ReplyKeyboardMarkup""" from telegram import ReplyMarkup class ReplyKeyboardMarkup(ReplyMarkup): + """This object represents a Telegram ReplyKeyboardMarkup. + + Attributes: + keyboard (List[List[str]]): + resize_keyboard (bool): + one_time_keyboard (bool): + selective (bool): + + Args: + keyboard (List[List[str]]): + **kwargs: Arbitrary keyword arguments. + + Keyword Args: + resize_keyboard (Optional[bool]): + one_time_keyboard (Optional[bool]): + selective (Optional[bool]): + """ + def __init__(self, keyboard, - resize_keyboard=None, - one_time_keyboard=None, - selective=None): + **kwargs): + # Required self.keyboard = keyboard - self.resize_keyboard = resize_keyboard - self.one_time_keyboard = one_time_keyboard - self.selective = selective + # Optionals + self.resize_keyboard = bool(kwargs.get('resize_keyboard', False)) + self.one_time_keyboard = bool(kwargs.get('one_time_keyboard', False)) + self.selective = bool(kwargs.get('selective', False)) @staticmethod def de_json(data): - return ReplyKeyboardMarkup(keyboard=data.get('keyboard', None), - resize_keyboard=data.get( - 'resize_keyboard', None - ), - one_time_keyboard=data.get( - 'one_time_keyboard', None - ), - selective=data.get('selective', None)) + """ + Args: + data (str): + + Returns: + telegram.ReplyKeyboardMarkup: + """ + rkm = dict() + + # Required + rkm['keyboard'] = data['keyboard'] + # Optionals + rkm['resize_keyboard'] = data.get('resize_keyboard', False) + rkm['one_time_keyboard'] = data.get('one_time_keyboard', False) + rkm['selective'] = data.get('selective', False) + + return ReplyKeyboardMarkup(**rkm) def to_dict(self): - data = {'keyboard': self.keyboard} + """ + Returns: + dict: + """ + data = dict() + + # Required + data['keyboard'] = self.keyboard + # Optionals if self.resize_keyboard: data['resize_keyboard'] = self.resize_keyboard if self.one_time_keyboard: data['one_time_keyboard'] = self.one_time_keyboard if self.selective: data['selective'] = self.selective + return data diff --git a/telegram/replymarkup.py b/telegram/replymarkup.py index e86f0b3da..a308e9d5c 100644 --- a/telegram/replymarkup.py +++ b/telegram/replymarkup.py @@ -16,9 +16,17 @@ # You should have received a copy of the GNU Lesser Public License # along with this program. If not, see [http://www.gnu.org/licenses/]. +"""Base class for Telegram ReplyMarkup Objects""" from telegram import TelegramObject class ReplyMarkup(TelegramObject): - pass + """Base class for Telegram ReplyMarkup Objects""" + + @staticmethod + def de_json(data): + pass + + def to_dict(self): + pass From 0f924508f714c79da7e9c9ef638fd511f8a25467 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 13:02:02 -0300 Subject: [PATCH 07/20] Fix tests and minor changes --- .travis.yml | 3 +- CHANGES => CHANGES.rst | 0 Makefile | 12 ++++--- .../docs-requirements.txt | 0 telegram_test.py | 30 ------------------ tests/{ => data}/telegram.mp3 | Bin tests/{ => data}/telegram.mp4 | Bin tests/{ => data}/telegram.ogg | Bin tests/{ => data}/telegram.png | Bin tests/test_bot.py | 14 +++++--- 10 files changed, 18 insertions(+), 41 deletions(-) rename CHANGES => CHANGES.rst (100%) rename docs-requirements.txt => docs/docs-requirements.txt (100%) delete mode 100644 telegram_test.py rename tests/{ => data}/telegram.mp3 (100%) rename tests/{ => data}/telegram.mp4 (100%) rename tests/{ => data}/telegram.ogg (100%) rename tests/{ => data}/telegram.png (100%) diff --git a/.travis.yml b/.travis.yml index f2630b00c..36fd6dd52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,10 @@ python: - "2.7" - "3.3" - "3.4" + - "nightly" install: - pip install coveralls script: - coverage run telegram_test.py + coverage run make test after_success: coveralls diff --git a/CHANGES b/CHANGES.rst similarity index 100% rename from CHANGES rename to CHANGES.rst diff --git a/Makefile b/Makefile index ec8ec3b62..6ebba9d0b 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,4 @@ -help: - @echo " clean remove unwanted stuff" - @echo " lint check style with flake8" - @echo " test run tests" +.PHONY: clean test lint help clean: rm -fr build @@ -14,4 +11,9 @@ lint: flake8 --doctests --max-complexity 10 telegram test: - python telegram_test.py + @- $(foreach TEST, $(wildcard tests/test_*.py), python $(TEST)) + +help: + @echo " clean remove unwanted stuff" + @echo " lint check style with flake8" + @echo " test run tests" diff --git a/docs-requirements.txt b/docs/docs-requirements.txt similarity index 100% rename from docs-requirements.txt rename to docs/docs-requirements.txt diff --git a/telegram_test.py b/telegram_test.py deleted file mode 100644 index 488b915a7..000000000 --- a/telegram_test.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python -# -# A library that provides a Python interface to the Telegram Bot API -# Copyright (C) 2015 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/]. - - -import logging -import unittest -from tests.test_bot import BotTest - -if __name__ == '__main__': - logging.basicConfig( - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') - logger = logging.getLogger() - logger.setLevel(logging.DEBUG) - testsuite = unittest.TestLoader().loadTestsFromTestCase(BotTest) - unittest.TextTestRunner(verbosity=1).run(testsuite) diff --git a/tests/telegram.mp3 b/tests/data/telegram.mp3 similarity index 100% rename from tests/telegram.mp3 rename to tests/data/telegram.mp3 diff --git a/tests/telegram.mp4 b/tests/data/telegram.mp4 similarity index 100% rename from tests/telegram.mp4 rename to tests/data/telegram.mp4 diff --git a/tests/telegram.ogg b/tests/data/telegram.ogg similarity index 100% rename from tests/telegram.ogg rename to tests/data/telegram.ogg diff --git a/tests/telegram.png b/tests/data/telegram.png similarity index 100% rename from tests/telegram.png rename to tests/data/telegram.png diff --git a/tests/test_bot.py b/tests/test_bot.py index fcc266ea3..54ab4f61e 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -19,6 +19,8 @@ import os +import sys +sys.path.append('.') import json import telegram import unittest @@ -80,7 +82,7 @@ class BotTest(unittest.TestCase): def testSendPhoto(self): '''Test the telegram.Bot sendPhoto method''' print('Testing sendPhoto - File') - message = self._bot.sendPhoto(photo=open('tests/telegram.png', 'rb'), + message = self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'), caption='testSendPhoto', chat_id=12173560) self.assertTrue(self.is_json(message.to_json())) @@ -122,7 +124,7 @@ class BotTest(unittest.TestCase): def testSendAudio(self): '''Test the telegram.Bot sendAudio method''' print('Testing sendAudio - File') - message = self._bot.sendAudio(audio=open('tests/telegram.mp3', 'rb'), + message = self._bot.sendAudio(audio=open('tests/data/telegram.mp3', 'rb'), chat_id=12173560, performer='Leandro Toledo', title='Teste') @@ -144,7 +146,7 @@ class BotTest(unittest.TestCase): def testSendVoice(self): '''Test the telegram.Bot sendVoice method''' print('Testing sendVoice - File') - message = self._bot.sendVoice(voice=open('tests/telegram.ogg', 'rb'), + message = self._bot.sendVoice(voice=open('tests/data/telegram.ogg', 'rb'), chat_id=12173560) self.assertTrue(self.is_json(message.to_json())) self.assertEqual(9199, message.voice.file_size) @@ -160,7 +162,7 @@ class BotTest(unittest.TestCase): def testSendDocument(self): '''Test the telegram.Bot sendDocument method''' print('Testing sendDocument - File') - message = self._bot.sendDocument(document=open('tests/telegram.png', 'rb'), + message = self._bot.sendDocument(document=open('tests/data/telegram.png', 'rb'), chat_id=12173560) self.assertTrue(self.is_json(message.to_json())) self.assertEqual(12948, message.document.file_size) @@ -184,7 +186,7 @@ class BotTest(unittest.TestCase): def testSendVideo(self): '''Test the telegram.Bot sendVideo method''' print('Testing sendVideo - File') - message = self._bot.sendVideo(video=open('tests/telegram.mp4', 'rb'), + message = self._bot.sendVideo(video=open('tests/data/telegram.mp4', 'rb'), caption='testSendVideo', chat_id=12173560) self.assertTrue(self.is_json(message.to_json())) @@ -229,3 +231,5 @@ class BotTest(unittest.TestCase): upf = self._bot.getUserProfilePhotos(user_id=12173560) self.assertTrue(self.is_json(upf.to_json())) self.assertEqual(6547, upf.photos[0][0].file_size) + +unittest.main() From 6132e65dc3eb7b31e2dac1ef5ba24896dd702bc3 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 13:04:36 -0300 Subject: [PATCH 08/20] Fix travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 36fd6dd52..28e1fad29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,6 @@ python: install: - pip install coveralls script: - coverage run make test + coverage run tests/test_*.py after_success: coveralls From 6837bef9bb5455b2c17f4816a6afe046346c28a8 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 13:07:12 -0300 Subject: [PATCH 09/20] Fix tests for Py2.6 --- tests/test_bot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_bot.py b/tests/test_bot.py index 54ab4f61e..5f5c5c74c 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -58,7 +58,7 @@ class BotTest(unittest.TestCase): text='Моё судно на воздушной подушке полно угрей') self.assertTrue(self.is_json(message.to_json())) self.assertEqual(u'Моё судно на воздушной подушке полно угрей', message.text) - self.assertIsInstance(message.date, datetime) + self.assertTrue(isinstance(message.date, datetime)) def testGetUpdates(self): '''Test the telegram.Bot getUpdates method''' @@ -66,7 +66,7 @@ class BotTest(unittest.TestCase): updates = self._bot.getUpdates() if updates: self.assertTrue(self.is_json(updates[0].to_json())) - self.assertIsInstance(updates[0], telegram.Update) + self.assertTrue(isinstance(updates[0], telegram.Update)) def testForwardMessage(self): '''Test the telegram.Bot forwardMessage method''' @@ -77,7 +77,7 @@ class BotTest(unittest.TestCase): self.assertTrue(self.is_json(message.to_json())) self.assertEqual('Oi', message.text) self.assertEqual('leandrotoledo', message.forward_from.username) - self.assertIsInstance(message.forward_date, datetime) + self.assertTrue(isinstance(message.forward_date, datetime)) def testSendPhoto(self): '''Test the telegram.Bot sendPhoto method''' From 942706e20f16150aa889996678985e2119fe7793 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 13:09:47 -0300 Subject: [PATCH 10/20] Add PyPy to travis --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 28e1fad29..066b7d757 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ python: - "2.7" - "3.3" - "3.4" - - "nightly" + - "pypy" + - "pypy3" install: - pip install coveralls script: From 1c4595123cd0d669918f5cfdeda9b55164a35a40 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 13:28:58 -0300 Subject: [PATCH 11/20] Add improvements to tests --- tests/__init__.py | 0 tests/test_bot.py | 127 ++++++++++++++++++++++++++++------------------ 2 files changed, 77 insertions(+), 50 deletions(-) delete mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/test_bot.py b/tests/test_bot.py index 5f5c5c74c..b5d51b7e3 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -38,32 +38,40 @@ class BotTest(unittest.TestCase): def setUp(self): bot = telegram.Bot(token=os.environ.get('TOKEN')) + chat_id = os.environ.get('CHAT_ID') + self._bot = bot + self._chat_id = chat_id + print('Testing the Bot API class') def testGetMe(self): '''Test the telegram.Bot getMe method''' print('Testing getMe') bot = self._bot.getMe() + self.assertTrue(self.is_json(bot.to_json())) - self.assertEqual(120405045, bot.id) - self.assertEqual('Toledo\'s Palace Bot', bot.first_name) - self.assertEqual(None, bot.last_name) - self.assertEqual('ToledosPalaceBot', bot.username) + self.assertEqual(bot.id, 120405045) + self.assertEqual(bot.first_name, 'Toledo\'s Palace Bot') + self.assertEqual(bot.last_name, None) + self.assertEqual(bot.username, 'ToledosPalaceBot') + self.assertEqual(bot.name, '@ToledosPalaceBot') def testSendMessage(self): '''Test the telegram.Bot sendMessage method''' print('Testing sendMessage') - message = self._bot.sendMessage(chat_id=12173560, + message = self._bot.sendMessage(chat_id=self._chat_id, text='Моё судно на воздушной подушке полно угрей') + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(u'Моё судно на воздушной подушке полно угрей', message.text) + self.assertEqual(message.text, u'Моё судно на воздушной подушке полно угрей') self.assertTrue(isinstance(message.date, datetime)) def testGetUpdates(self): '''Test the telegram.Bot getUpdates method''' print('Testing getUpdates') updates = self._bot.getUpdates() + if updates: self.assertTrue(self.is_json(updates[0].to_json())) self.assertTrue(isinstance(updates[0], telegram.Update)) @@ -71,12 +79,13 @@ class BotTest(unittest.TestCase): def testForwardMessage(self): '''Test the telegram.Bot forwardMessage method''' print('Testing forwardMessage') - message = self._bot.forwardMessage(chat_id=12173560, - from_chat_id=12173560, + message = self._bot.forwardMessage(chat_id=self._chat_id, + from_chat_id=self._chat_id, message_id=138) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual('Oi', message.text) - self.assertEqual('leandrotoledo', message.forward_from.username) + self.assertEqual(message.text, 'Oi') + self.assertEqual(message.forward_from.username, 'leandrotoledo') self.assertTrue(isinstance(message.forward_date, datetime)) def testSendPhoto(self): @@ -84,152 +93,170 @@ class BotTest(unittest.TestCase): print('Testing sendPhoto - File') message = self._bot.sendPhoto(photo=open('tests/data/telegram.png', 'rb'), caption='testSendPhoto', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(1451, message.photo[0].file_size) - self.assertEqual('testSendPhoto', message.caption) + self.assertEqual(message.photo[0].file_size, 1451) + self.assertEqual(message.caption, 'testSendPhoto') def testResendPhoto(self): '''Test the telegram.Bot sendPhoto method''' print('Testing sendPhoto - Resend') message = self._bot.sendPhoto(photo='AgADAQADr6cxGzU8LQe6q0dMJD2rHYkP2ykABFymiQqJgjxRGGMAAgI', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual('AgADAQADr6cxGzU8LQe6q0dMJD2rHYkP2ykABFymiQqJgjxRGGMAAgI', message.photo[0].file_id) + self.assertEqual(message.photo[0].file_id, 'AgADAQADr6cxGzU8LQe6q0dMJD2rHYkP2ykABFymiQqJgjxRGGMAAgI') def testSendJPGURLPhoto(self): '''Test the telegram.Bot sendPhoto method''' print('Testing testSendJPGURLPhoto - URL') message = self._bot.sendPhoto(photo='http://dummyimage.com/600x400/000/fff.jpg&text=telegram', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(822, message.photo[0].file_size) + self.assertEqual(message.photo[0].file_size, 822) def testSendPNGURLPhoto(self): '''Test the telegram.Bot sendPhoto method''' print('Testing testSendPNGURLPhoto - URL') message = self._bot.sendPhoto(photo='http://dummyimage.com/600x400/000/fff.png&text=telegram', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(684, message.photo[0].file_size) + self.assertEqual(message.photo[0].file_size, 684) def testSendGIFURLPhoto(self): '''Test the telegram.Bot sendPhoto method''' print('Testing testSendGIFURLPhoto - URL') message = self._bot.sendPhoto(photo='http://dummyimage.com/600x400/000/fff.gif&text=telegram', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(684, message.photo[0].file_size) + self.assertEqual(message.photo[0].file_size, 684) def testSendAudio(self): '''Test the telegram.Bot sendAudio method''' print('Testing sendAudio - File') message = self._bot.sendAudio(audio=open('tests/data/telegram.mp3', 'rb'), - chat_id=12173560, + chat_id=self._chat_id, performer='Leandro Toledo', title='Teste') + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(28232, message.audio.file_size) - self.assertEqual('Leandro Toledo', message.audio.performer) - self.assertEqual('Teste', message.audio.title) + self.assertEqual(message.audio.file_size, 28232) + self.assertEqual(message.audio.performer, 'Leandro Toledo') + self.assertEqual(message.audio.title, 'Teste') def testResendAudio(self): '''Test the telegram.Bot sendAudio method''' print('Testing sendAudio - Resend') message = self._bot.sendAudio(audio='BQADAQADwwcAAjU8LQdBRsl3_qD2TAI', - chat_id=12173560, + chat_id=self._chat_id, performer='Leandro Toledo', # Bug #39 title='Teste') # Bug #39 + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual('BQADAQADwwcAAjU8LQdBRsl3_qD2TAI', message.audio.file_id) + self.assertEqual(message.audio.file_id, 'BQADAQADwwcAAjU8LQdBRsl3_qD2TAI') def testSendVoice(self): '''Test the telegram.Bot sendVoice method''' print('Testing sendVoice - File') message = self._bot.sendVoice(voice=open('tests/data/telegram.ogg', 'rb'), - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(9199, message.voice.file_size) + self.assertEqual(message.voice.file_size, 9199) def testResendVoice(self): '''Test the telegram.Bot sendVoice method''' print('Testing sendVoice - Resend') message = self._bot.sendVoice(voice='AwADAQADIQEAAvjAuQABSAXg_GhkhZcC', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual('AwADAQADIQEAAvjAuQABSAXg_GhkhZcC', message.voice.file_id) + self.assertEqual(message.voice.file_id, 'AwADAQADIQEAAvjAuQABSAXg_GhkhZcC') def testSendDocument(self): '''Test the telegram.Bot sendDocument method''' print('Testing sendDocument - File') message = self._bot.sendDocument(document=open('tests/data/telegram.png', 'rb'), - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(12948, message.document.file_size) + self.assertEqual(message.document.file_size, 12948) def testSendGIFURLDocument(self): '''Test the telegram.Bot sendDocument method''' print('Testing sendDocument - File') message = self._bot.sendPhoto(photo='http://dummyimage.com/600x400/000/fff.gif&text=telegram', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(684, message.photo[0].file_size) + self.assertEqual(message.photo[0].file_size, 684) def testResendDocument(self): '''Test the telegram.Bot sendDocument method''' print('Testing sendDocument - Resend') message = self._bot.sendDocument(document='BQADAQADHAADNTwtBxZxUGKyxYbYAg', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual('BQADAQADHAADNTwtBxZxUGKyxYbYAg', message.document.file_id) + self.assertEqual(message.document.file_id, 'BQADAQADHAADNTwtBxZxUGKyxYbYAg') def testSendVideo(self): '''Test the telegram.Bot sendVideo method''' print('Testing sendVideo - File') message = self._bot.sendVideo(video=open('tests/data/telegram.mp4', 'rb'), caption='testSendVideo', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(326534, message.video.file_size) - self.assertEqual('testSendVideo', message.caption) + self.assertEqual(message.video.file_size, 326534) + self.assertEqual(message.caption, 'testSendVideo') def testResendVideo(self): '''Test the telegram.Bot sendVideo method''' print('Testing sendVideo - Resend') message = self._bot.sendVideo(video='BAADAQADIgEAAvjAuQABOuTB937fPTgC', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(4, message.video.duration) + self.assertEqual(message.video.duration, 4) def testResendSticker(self): '''Test the telegram.Bot sendSticker method''' print('Testing sendSticker - Resend') message = self._bot.sendSticker(sticker='BQADAQADHAADyIsGAAFZfq1bphjqlgI', - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(39518, message.sticker.file_size) + self.assertEqual(message.sticker.file_size, 39518) def testSendLocation(self): '''Test the telegram.Bot sendLocation method''' print('Testing sendLocation') message = self._bot.sendLocation(latitude=-23.558873, longitude=-46.659732, - chat_id=12173560) + chat_id=self._chat_id) + self.assertTrue(self.is_json(message.to_json())) - self.assertEqual(-23.558873, message.location.latitude) - self.assertEqual(-46.659732, message.location.longitude) + self.assertEqual(message.location.latitude, -23.558873) + self.assertEqual(message.location.longitude, -46.659732) def testSendChatAction(self): '''Test the telegram.Bot sendChatAction method''' print('Testing sendChatAction - ChatAction.TYPING') + self._bot.sendChatAction(action=telegram.ChatAction.TYPING, - chat_id=12173560) + chat_id=self._chat_id) def testGetUserProfilePhotos(self): '''Test the telegram.Bot getUserProfilePhotos method''' print('Testing getUserProfilePhotos') - upf = self._bot.getUserProfilePhotos(user_id=12173560) + upf = self._bot.getUserProfilePhotos(user_id=self._chat_id) + self.assertTrue(self.is_json(upf.to_json())) - self.assertEqual(6547, upf.photos[0][0].file_size) + self.assertEqual(upf.photos[0][0].file_size, 6547) unittest.main() From a86fc6c2ac1d984f070ac0dbfe12741b96ff373d Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 17:45:44 -0300 Subject: [PATCH 12/20] Improving the design of existing Telegram classes --- Makefile | 15 ++- ...requirements.txt => requirements-docs.txt} | 0 telegram/audio.py | 35 +------ telegram/base.py | 14 ++- telegram/contact.py | 29 +----- telegram/document.py | 33 +------ telegram/forcereply.py | 26 +---- telegram/groupchat.py | 21 +--- telegram/location.py | 21 +--- telegram/message.py | 96 +++++-------------- telegram/photosize.py | 34 +++---- telegram/replykeyboardhide.py | 25 +---- telegram/replykeyboardmarkup.py | 31 +----- telegram/sticker.py | 31 +----- telegram/update.py | 23 +---- telegram/user.py | 29 +----- telegram/userprofilephotos.py | 14 +-- telegram/video.py | 36 +------ telegram/voice.py | 30 +----- tests/test_bot.py | 2 +- 20 files changed, 84 insertions(+), 461 deletions(-) rename docs/{docs-requirements.txt => requirements-docs.txt} (100%) diff --git a/Makefile b/Makefile index 6ebba9d0b..79d2c0ff7 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean test lint help +.PHONY: clean pep8 lint test clean: rm -fr build @@ -7,13 +7,18 @@ clean: find . -name '*.pyo' -exec rm -f {} \; find . -name '*~' -exec rm -f {} \; +pep8: + flake8 telegram + lint: - flake8 --doctests --max-complexity 10 telegram + pylint -E telegram test: @- $(foreach TEST, $(wildcard tests/test_*.py), python $(TEST)) help: - @echo " clean remove unwanted stuff" - @echo " lint check style with flake8" - @echo " test run tests" + @echo "Available targets:" + @echo "- clean Clean up the source directory" + @echo "- pep8 Check style with flake8" + @echo "- lint Check style with pylint" + @echo "- test Run tests" diff --git a/docs/docs-requirements.txt b/docs/requirements-docs.txt similarity index 100% rename from docs/docs-requirements.txt rename to docs/requirements-docs.txt diff --git a/telegram/audio.py b/telegram/audio.py index 50a3bb753..ff54b339d 100644 --- a/telegram/audio.py +++ b/telegram/audio.py @@ -69,37 +69,4 @@ class Audio(TelegramObject): if not data: return None - audio = dict() - - # Required - audio['file_id'] = data['file_id'] - audio['duration'] = data['duration'] - # Optionals - audio['performer'] = data.get('performer') - audio['title'] = data.get('title') - audio['mime_type'] = data.get('mime_type') - audio['file_size'] = data.get('file_size', 0) - - return Audio(**audio) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['file_id'] = self.file_id - data['duration'] = self.duration - # Optionals - if self.performer: - data['performer'] = self.performer - if self.title: - data['title'] = self.title - if self.mime_type: - data['mime_type'] = self.mime_type - if self.file_size: - data['file_size'] = self.file_size - - return data + return Audio(**data) diff --git a/telegram/base.py b/telegram/base.py index 8158abfbd..bcbc04315 100644 --- a/telegram/base.py +++ b/telegram/base.py @@ -19,7 +19,7 @@ """Base class for Telegram Objects""" import json -from abc import ABCMeta, abstractmethod +from abc import ABCMeta class TelegramObject(object): @@ -51,10 +51,18 @@ class TelegramObject(object): """ return json.dumps(self.to_dict()) - @abstractmethod def to_dict(self): """ Returns: dict: """ - return None + data = dict() + + for key, value in self.__dict__.iteritems(): + if value: + if hasattr(value, 'to_dict'): + data[key] = value.to_dict() + else: + data[key] = value + + return data diff --git a/telegram/contact.py b/telegram/contact.py index 262b4bc9d..e26d755c3 100644 --- a/telegram/contact.py +++ b/telegram/contact.py @@ -63,31 +63,4 @@ class Contact(TelegramObject): if not data: return None - contact = dict() - - # Required - contact['phone_number'] = data['phone_number'] - contact['first_name'] = data['first_name'] - # Optionals - contact['last_name'] = data.get('last_name') - contact['user_id'] = data.get('user_id', 0) - - return Contact(**contact) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['phone_number'] = self.phone_number - data['first_name'] = self.first_name - # Optionals - if self.last_name: - data['last_name'] = self.last_name - if self.user_id: - data['user_id'] = self.user_id - - return data + return Contact(**data) diff --git a/telegram/document.py b/telegram/document.py index e9f7a4e7a..1fb21a92c 100644 --- a/telegram/document.py +++ b/telegram/document.py @@ -65,35 +65,4 @@ class Document(TelegramObject): if not data: return None - document = dict() - - # Required - document['file_id'] = data['file_id'] - # Optionals - document['thumb'] = PhotoSize.de_json(data.get('thumb')) - document['file_name'] = data.get('file_name') - document['mime_type'] = data.get('mime_type') - document['file_size'] = data.get('file_size', 0) - - return Document(**document) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['file_id'] = self.file_id - # Optionals - if self.thumb: - data['thumb'] = self.thumb.to_dict() - if self.file_name: - data['file_name'] = self.file_name - if self.mime_type: - data['mime_type'] = self.mime_type - if self.file_size: - data['file_size'] = self.file_size - - return data + return Document(**data) diff --git a/telegram/forcereply.py b/telegram/forcereply.py index 7e8893441..b28e8f58c 100644 --- a/telegram/forcereply.py +++ b/telegram/forcereply.py @@ -53,26 +53,8 @@ class ForceReply(ReplyMarkup): Returns: telegram.ForceReply: """ - force_reply = dict() + if not data: + return None - # Required - force_reply['force_reply'] = data['force_reply'] - # Optionals - force_reply['selective'] = data.get('selective', False) - - return ForceReply(**force_reply) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['force_reply'] = self.force_reply - # Optionals - if self.selective: - data['selective'] = self.selective - - return data + return ForceReply(**data) + diff --git a/telegram/groupchat.py b/telegram/groupchat.py index f3190b7db..1b3fac8df 100644 --- a/telegram/groupchat.py +++ b/telegram/groupchat.py @@ -53,23 +53,4 @@ class GroupChat(TelegramObject): if not data: return None - groupchat = dict() - - # Required - groupchat['id'] = data['id'] - groupchat['title'] = data['title'] - - return GroupChat(**groupchat) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['id'] = self.id - data['title'] = self.title - - return data + return GroupChat(**data) diff --git a/telegram/location.py b/telegram/location.py index b5148b7ea..66c28fc59 100644 --- a/telegram/location.py +++ b/telegram/location.py @@ -52,23 +52,4 @@ class Location(TelegramObject): if not data: return None - location = dict() - - # Required - location['longitude'] = data['longitude'] - location['latitude'] = data['latitude'] - - return Location(**location) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['longitude'] = self.longitude - data['latitude'] = self.latitude - - return data + return Location(**data) diff --git a/telegram/message.py b/telegram/message.py index 7c606d4fc..1caf61dc7 100644 --- a/telegram/message.py +++ b/telegram/message.py @@ -134,110 +134,60 @@ class Message(TelegramObject): if not data: return None - message = dict() - - # Required - message['message_id'] = data['message_id'] - message['from_user'] = User.de_json(data['from']) - message['date'] = datetime.fromtimestamp(data['date']) - # Optionals + data['from_user'] = User.de_json(data['from']) + data['date'] = datetime.fromtimestamp(data['date']) if 'first_name' in data.get('chat', ''): - message['chat'] = User.de_json(data.get('chat')) + data['chat'] = User.de_json(data.get('chat')) elif 'title' in data.get('chat', ''): - message['chat'] = GroupChat.de_json(data.get('chat')) - message['forward_from'] = \ + data['chat'] = GroupChat.de_json(data.get('chat')) + data['forward_from'] = \ User.de_json(data.get('forward_from')) - message['forward_date'] = \ + data['forward_date'] = \ Message._fromtimestamp(data.get('forward_date')) - message['reply_to_message'] = \ + data['reply_to_message'] = \ Message.de_json(data.get('reply_to_message')) - message['text'] = \ - data.get('text') - message['audio'] = \ + data['audio'] = \ Audio.de_json(data.get('audio')) - message['document'] = \ + data['document'] = \ Document.de_json(data.get('document')) - message['photo'] = \ - [PhotoSize.de_json(x) for x in data.get('photo', [])] - message['sticker'] = \ + data['photo'] = \ + PhotoSize.de_list(data.get('photo')) + data['sticker'] = \ Sticker.de_json(data.get('sticker')) - message['video'] = \ + data['video'] = \ Video.de_json(data.get('video')) - message['voice'] = \ + data['voice'] = \ Voice.de_json(data.get('voice')) - message['caption'] = \ - data.get('caption') - message['contact'] = \ + data['contact'] = \ Contact.de_json(data.get('contact')) - message['location'] = \ + data['location'] = \ Location.de_json(data.get('location')) - message['new_chat_participant'] = \ + data['new_chat_participant'] = \ User.de_json(data.get('new_chat_participant')) - message['left_chat_participant'] = \ + data['left_chat_participant'] = \ User.de_json(data.get('left_chat_participant')) - message['new_chat_title'] = \ - data.get('new_chat_title') - message['new_chat_photo'] = \ - [PhotoSize.de_json(x) for x in data.get('new_chat_photo', [])] - message['delete_chat_photo'] = \ - data.get('delete_chat_photo') - message['group_chat_created'] = \ - data.get('group_chat_created') + data['new_chat_photo'] = \ + PhotoSize.de_list(data.get('new_chat_photo')) - return Message(**message) + return Message(**data) def to_dict(self): """ Returns: dict: """ - data = dict() + data = super(Message, self).to_dict() # Required - data['message_id'] = self.message_id - data['from'] = self.from_user.to_dict() + data['from'] = data.pop('from_user') data['date'] = self._totimestamp(self.date) - data['chat'] = self.chat.to_dict() # Optionals - if self.forward_from: - data['forward_from'] = self.forward_from.to_dict() if self.forward_date: data['forward_date'] = self._totimestamp(self.forward_date) - if self.reply_to_message: - data['reply_to_message'] = self.reply_to_message.to_dict() - if self.text: - data['text'] = self.text - if self.audio: - data['audio'] = self.audio.to_dict() - if self.document: - data['document'] = self.document.to_dict() if self.photo: data['photo'] = [p.to_dict() for p in self.photo] - if self.sticker: - data['sticker'] = self.sticker.to_dict() - if self.video: - data['video'] = self.video.to_dict() - if self.voice: - data['voice'] = self.voice.to_dict() - if self.caption: - data['caption'] = self.caption - if self.contact: - data['contact'] = self.contact.to_dict() - if self.location: - data['location'] = self.location.to_dict() - if self.new_chat_participant: - data['new_chat_participant'] = self.new_chat_participant.to_dict() - if self.left_chat_participant: - data['left_chat_participant'] = \ - self.left_chat_participant.to_dict() - if self.new_chat_title: - data['new_chat_title'] = self.new_chat_title if self.new_chat_photo: data['new_chat_photo'] = [p.to_dict() for p in self.new_chat_photo] - if self.delete_chat_photo: - data['delete_chat_photo'] = self.delete_chat_photo - if self.group_chat_created: - data['group_chat_created'] = self.group_chat_created return data diff --git a/telegram/photosize.py b/telegram/photosize.py index c528e7ce9..adc47649a 100644 --- a/telegram/photosize.py +++ b/telegram/photosize.py @@ -64,30 +64,22 @@ class PhotoSize(TelegramObject): if not data: return None - photosize = dict() + return PhotoSize(**data) - # Required - photosize['file_id'] = data['file_id'] - photosize['width'] = data['width'] - photosize['height'] = data['height'] - # Optionals - photosize['file_size'] = data.get('file_size', 0) - - return PhotoSize(**photosize) - - def to_dict(self): + @staticmethod + def de_list(data): """ + Args: + data (list): + Returns: - dict: + List: """ - data = dict() + if not data: + return [] - # Required - data['file_id'] = self.file_id - data['width'] = self.width - data['height'] = self.height - # Optionals - if self.file_size: - data['file_size'] = self.file_size + photos = list() + for photo in data: + photos.append(PhotoSize.de_json(photo)) - return data + return photos diff --git a/telegram/replykeyboardhide.py b/telegram/replykeyboardhide.py index 7e0dd364e..f2cb80bb7 100644 --- a/telegram/replykeyboardhide.py +++ b/telegram/replykeyboardhide.py @@ -54,26 +54,7 @@ class ReplyKeyboardHide(ReplyMarkup): Returns: telegram.ReplyKeyboardHide: """ - rkh = dict() + if not data: + return None - # Required - rkh['hide_keyboard'] = data['hide_keyboard'] - # Optionals - rkh['selective'] = data.get('selective', False) - - return ReplyKeyboardHide(**rkh) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['hide_keyboard'] = self.hide_keyboard - # Optionals - if self.selective: - data['selective'] = self.selective - - return data + return ReplyKeyboardHide(**data) diff --git a/telegram/replykeyboardmarkup.py b/telegram/replykeyboardmarkup.py index dfd9aa2c8..39912ea39 100644 --- a/telegram/replykeyboardmarkup.py +++ b/telegram/replykeyboardmarkup.py @@ -60,32 +60,7 @@ class ReplyKeyboardMarkup(ReplyMarkup): Returns: telegram.ReplyKeyboardMarkup: """ - rkm = dict() + if not data: + return None - # Required - rkm['keyboard'] = data['keyboard'] - # Optionals - rkm['resize_keyboard'] = data.get('resize_keyboard', False) - rkm['one_time_keyboard'] = data.get('one_time_keyboard', False) - rkm['selective'] = data.get('selective', False) - - return ReplyKeyboardMarkup(**rkm) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['keyboard'] = self.keyboard - # Optionals - if self.resize_keyboard: - data['resize_keyboard'] = self.resize_keyboard - if self.one_time_keyboard: - data['one_time_keyboard'] = self.one_time_keyboard - if self.selective: - data['selective'] = self.selective - - return data + return ReplyKeyboardMarkup(**data) diff --git a/telegram/sticker.py b/telegram/sticker.py index cf7e9c7a8..c663a3fed 100644 --- a/telegram/sticker.py +++ b/telegram/sticker.py @@ -67,33 +67,4 @@ class Sticker(TelegramObject): if not data: return None - sticker = dict() - - # Required - sticker['file_id'] = data['file_id'] - sticker['width'] = data['width'] - sticker['height'] = data['height'] - # Optionals - sticker['thumb'] = PhotoSize.de_json(data['thumb']) - sticker['file_size'] = data.get('file_size', 0) - - return Sticker(**sticker) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['file_id'] = self.file_id - data['width'] = self.width - data['height'] = self.height - # Optionals - if self.thumb: - data['thumb'] = self.thumb.to_dict() - if self.file_size: - data['file_size'] = self.file_size - - return data + return Sticker(**data) diff --git a/telegram/update.py b/telegram/update.py index 9d2a9dfa5..83e068593 100644 --- a/telegram/update.py +++ b/telegram/update.py @@ -52,24 +52,9 @@ class Update(TelegramObject): Returns: telegram.Update: """ - update = dict() + if not data: + return None - update['update_id'] = data['update_id'] - update['message'] = Message.de_json(data['message']) + data['message'] = Message.de_json(data['message']) - return Update(**update) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['update_id'] = self.update_id - # Optionals - if self.message: - data['message'] = self.message.to_dict() - - return data + return Update(**data) diff --git a/telegram/user.py b/telegram/user.py index 2f36c0f14..1cdbe8e86 100644 --- a/telegram/user.py +++ b/telegram/user.py @@ -73,31 +73,4 @@ class User(TelegramObject): if not data: return None - user = dict() - - # Required - user['id'] = data['id'] - user['first_name'] = data['first_name'] - # Optionals - user['last_name'] = data.get('last_name') - user['username'] = data.get('username') - - return User(**user) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['id'] = self.id - data['first_name'] = self.first_name - # Optionals - if self.last_name: - data['last_name'] = self.last_name - if self.username: - data['username'] = self.username - - return data + return User(**data) diff --git a/telegram/userprofilephotos.py b/telegram/userprofilephotos.py index 9c9871cd1..bce623b3b 100644 --- a/telegram/userprofilephotos.py +++ b/telegram/userprofilephotos.py @@ -53,25 +53,17 @@ class UserProfilePhotos(TelegramObject): if not data: return None - upf = dict() + data['photos'] = [PhotoSize.de_list(photo) for photo in data['photos']] - # Required - upf['total_count'] = data['total_count'] - upf['photos'] = [] - for photo in data['photos']: - upf['photos'].append([PhotoSize.de_json(x) for x in photo]) - - return UserProfilePhotos(**upf) + return UserProfilePhotos(**data) def to_dict(self): """ Returns: dict: """ - data = dict() + data = super(UserProfilePhotos, self).to_dict() - # Required - data['total_count'] = self.total_count data['photos'] = [] for photo in self.photos: data['photos'].append([x.to_dict() for x in photo]) diff --git a/telegram/video.py b/telegram/video.py index ca5caf29e..b9414835b 100644 --- a/telegram/video.py +++ b/telegram/video.py @@ -74,38 +74,4 @@ class Video(TelegramObject): if not data: return None - video = dict() - - # Required - video['file_id'] = data['file_id'] - video['width'] = data['width'] - video['height'] = data['height'] - video['duration'] = data['duration'] - # Optionals - video['thumb'] = PhotoSize.de_json(data.get('thumb')) - video['mime_type'] = data.get('mime_type') - video['file_size'] = data.get('file_size', 0) - - return Video(**video) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['file_id'] = self.file_id - data['width'] = self.width - data['height'] = self.height - data['duration'] = self.duration - # Optionals - if self.thumb: - data['thumb'] = self.thumb.to_dict() - if self.mime_type: - data['mime_type'] = self.mime_type - if self.file_size: - data['file_size'] = self.file_size - - return data + return Video(**data) diff --git a/telegram/voice.py b/telegram/voice.py index fc4baec37..96f3fa5f1 100644 --- a/telegram/voice.py +++ b/telegram/voice.py @@ -62,32 +62,4 @@ class Voice(TelegramObject): if not data: return None - voice = dict() - - # Required - voice['file_id'] = data['file_id'] - # Optionals - voice['duration'] = data.get('duration', 0) - voice['mime_type'] = data.get('mime_type') - voice['file_size'] = data.get('file_size', 0) - - return Voice(**voice) - - def to_dict(self): - """ - Returns: - dict: - """ - data = dict() - - # Required - data['file_id'] = self.file_id - # Optionals - if self.duration: - data['duration'] = self.duration - if self.mime_type: - data['mime_type'] = self.mime_type - if self.file_size: - data['file_size'] = self.file_size - - return data + return Voice(**data) diff --git a/tests/test_bot.py b/tests/test_bot.py index b5d51b7e3..707b50ca7 100644 --- a/tests/test_bot.py +++ b/tests/test_bot.py @@ -53,7 +53,7 @@ class BotTest(unittest.TestCase): self.assertTrue(self.is_json(bot.to_json())) self.assertEqual(bot.id, 120405045) self.assertEqual(bot.first_name, 'Toledo\'s Palace Bot') - self.assertEqual(bot.last_name, None) + self.assertEqual(bot.last_name, '') self.assertEqual(bot.username, 'ToledosPalaceBot') self.assertEqual(bot.name, '@ToledosPalaceBot') From 98c489c44fbe81888fac3a5e522a9dde80bfbcf3 Mon Sep 17 00:00:00 2001 From: Leandro Toledo Date: Fri, 28 Aug 2015 17:53:31 -0300 Subject: [PATCH 13/20] Fix Python 3 --- telegram/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telegram/base.py b/telegram/base.py index bcbc04315..ac0deac0f 100644 --- a/telegram/base.py +++ b/telegram/base.py @@ -58,7 +58,7 @@ class TelegramObject(object): """ data = dict() - for key, value in self.__dict__.iteritems(): + for key, value in self.__dict__.items(): if value: if hasattr(value, 'to_dict'): data[key] = value.to_dict() From 3f3cb1edc5e8f9eb4ac764205d1ef8a165b9919a Mon Sep 17 00:00:00 2001 From: njittam Date: Wed, 2 Sep 2015 16:04:15 +0200 Subject: [PATCH 14/20] Update AUTHORS.rst --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 55a05b75b..5ce4542de 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -14,6 +14,7 @@ The following wonderful people contributed directly or indirectly to this projec - `JASON0916 `_ - `JRoot3D `_ - `macrojames `_ +- `njittam `_ - `Rahiel Kasim `_ - `sooyhwang `_ - `wjt `_ From 8fddd3b027b2fe0f6f13b006c6cfcb601a8e586e Mon Sep 17 00:00:00 2001 From: njittam Date: Wed, 2 Sep 2015 16:11:21 +0200 Subject: [PATCH 15/20] test for commandHandler Not everything has a test. and I'm quite new to unit testing so I'm not sure if I am doing this the right way. --- testscommand_handlertest.py | 73 +++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 testscommand_handlertest.py diff --git a/testscommand_handlertest.py b/testscommand_handlertest.py new file mode 100644 index 000000000..4916a8d15 --- /dev/null +++ b/testscommand_handlertest.py @@ -0,0 +1,73 @@ +import telegram +import unittest +import unittest.mock +from telegram import CommandHandler, CommandHandlerWithHelp + + +class CommandHandlerTmp(CommandHandler): + def __init__(self, *args, **kwargs): + super(CommandHandlerTmp, self).__init__(*args, **kwargs) + self.output = None + + def command_test(self, update): + self.output = 1 + + +class CommandHandlerTmp2(CommandHandlerWithHelp): + def __init__(self, *args, **kwargs): + super(CommandHandlerTmp2, self).__init__(*args, **kwargs) + self.output_test = None + + def command_test(self, update): + self.output_test = 1 + + +def fake_getUpdates(*args, **kwargs): + from_user = telegram.User(id=42, first_name='hello') + message = telegram.Message(message_id=42, from_user=from_user, date=None, chat=from_user, text='/test') + update = telegram.Update(update_id=42, message=message) + return [update] + +output_fsm = None + + +def fake_sendMessage(chat_id, message, *args, **kwargs): + global output_fsm + output_fsm = (chat_id, message) + return telegram.Message(43, 123, 000000, telegram.User(chat_id, 'test'), text=message) + + +class CommandHandlerTest(unittest.TestCase): + def setUp(self): + self.bot = unittest.mock.MagicMock() + self.bot.getUpdates = fake_getUpdates + self.bot.sendMessage = fake_sendMessage + + def test_get_command_func(self): + CH = CommandHandlerTmp(self.bot) + self.assertEqual(CH.command_test, CH._get_command_func('test')) + self.assertEqual(CH.command_test, CH._get_command_func('/test')) + self.assertEqual(None, CH._get_command_func('this function does not exsist')) + + def test_run_once(self): + CH = CommandHandlerTmp(self.bot) + self.assertEqual(CH.output, None) + threads, last_update = CH.run_once(make_thread=True) + for t in threads: + t.start() + for t in threads: + t.join() + self.assertEqual(CH.output, 1) + + def test_run(self): + pass # TODO implement test + + def test__command_not_found(self): + CH = CommandHandlerTmp(self.bot) + CH._command_not_found(self.bot.getUpdates()[0]) + self.assertEqual(output_fsm, (42, "Sorry, I didn't understand the command: /test.")) + + +if __name__ == '__main__': + import sys + unittest.main(sys.argv) From 256d219862bb6f7b70c2da2a66900372b5676673 Mon Sep 17 00:00:00 2001 From: njittam Date: Wed, 2 Sep 2015 16:17:57 +0200 Subject: [PATCH 16/20] A command handler I saw in the TODO list on pypi that you wanted a commandhandler. I am not sure about the run and run_once commands. and I didn't implement logging yet. --- telegram/command_handler.py | 174 ++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 telegram/command_handler.py diff --git a/telegram/command_handler.py b/telegram/command_handler.py new file mode 100644 index 000000000..86de9f9d9 --- /dev/null +++ b/telegram/command_handler.py @@ -0,0 +1,174 @@ +from inspect import getmembers, ismethod +import threading +import logging +import telegram +import time +logger = logging.getLogger(__name__) +__all__ = ['CommandHandler', 'CommandHandlerWithHelp'] +class CommandHandler(object): + """ This handles incomming commands and gives an easy way to create commands. + + How to use this: + create a new class which inherits this class or CommandHandlerWithHelp. + define new methods that start with 'command_' and then the command_name. + run run() + """ + def __init__(self, bot): + self.bot = bot # a telegram bot + self.isValidCommand = None # a function that returns a boolean and takes one agrument an update. if False is returned the the comaand is not executed. + + def _get_command_func(self, command): + if command[0] == '/': + command = command[1:] + if hasattr(self, 'command_' + command): + return self.__getattribute__('command_' + command) # a function + else: + return None + + def run(self, make_thread=True, last_update_id=None, thread_timeout=2, sleep=0.2): + """Continuously check for commands and run the according method + + Args: + make_thread: + if True make a thread for each command it found. + if False make run the code linearly + last_update: + the offset arg from getUpdates and is kept up to date within this function + thread_timeout: + The timeout on a thread. If a thread is alive after this period then try to join the thread in + the next loop. + """ + old_threads = [] + while True: + time.sleep(sleep) + threads, last_update_id = self.run_once(make_thread=make_thread, last_update_id=last_update_id) + for t in threads: + t.start() + for t in old_threads: + threads.append(t) + old_threads = [] + for t in threads: + t.join(timeout=thread_timeout) + if t.isAlive(): + old_threads.append(t) + + def run_once(self, make_thread=True, last_update_id=None): + """ Check the the messages for commands and make a Thread with the command or run the command depending on make_thread. + + Args: + make_thread: + True: the function returns a list with threads. Which didn't start yet. + False: the function just runs the command it found and returns an empty list. + last_update_id: + the offset arg from getUpdates and is kept up to date within this function + + Returns: + A tuple of two elements. The first element is a list with threads which didn't start yet or an empty list if + make_threads==False. The second element is the updated las_update_id + """ + bot_name = self.bot.getMe().username + threads = [] + try: + updates = self.bot.getUpdates(offset=last_update_id) + except: + updates = [] + for update in updates: + last_update_id = update.update_id + 1 + message = update.message + if message.text[0] == '/': + command, username = message.text.split(' ')[0], bot_name + if '@' in command: + command, username = command.split('@') + if username == bot_name: + command_func = self._get_command_func(command) + if command_func is not None: + self.bot.sendChatAction(update.message.chat.id,telegram.ChatAction.TYPING) + if self.isValidCommand is None or self.isValidCommand(update): + if make_thread: + t = threading.Thread(target=command_func, args=(update,)) + threads.append(t) + else: + command_func(update) + else: + self._command_not_found(update) # TODO this must be another function. + else: + if make_thread: + t = threading.Thread(target=self._command_not_found, args=(update,)) + threads.append(t) + else: + self._command_not_valid(update) + return threads, last_update_id + + def _command_not_valid(self, update): + """Inform the telegram user that the command was not found. + + Override this method if you want to do it another way then by sending the the text: + Sorry, I didn't understand the command: /command[@bot]. + """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = "Sorry, the command was not authorised or valid: {command}.".format(command=update.message.text.split(' ')[0]) + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + def _command_not_found(self, update): + """Inform the telegram user that the command was not found. + + Override this method if you want to do it another way then by sending the the text: + Sorry, I didn't understand the command: /command[@bot]. + """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = "Sorry, I didn't understand the command: {command}.".format(command=update.message.text.split(' ')[0]) + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + +class CommandHandlerWithHelp(CommandHandler): + """ This CommandHandler has a builtin /help. It grabs the text from the docstrings of command_ functions.""" + def __init__(self, bot): + super(CommandHandlerWithHelp, self).__init__(bot) + self._help_title = 'Welcome to {name}.'.format(name=self.bot.getMe().username) # the title of help + self._help_before_list = '' # text with information about the bot + self._help_after_list = '' # a footer + self._help_list_title = 'These are the commands:' # the title of the list + self.is_reply = True + self.command_start = self.command_help + + def _generate_help(self): + """ Generate a string which can be send as a help file. + + This function generates a help file from all the docstrings from the commands. + so docstrings of methods that start with command_ should explain what a command does and how a to use the + command to the telegram user. + """ + + command_functions = [attr[1] for attr in getmembers(self, predicate=ismethod) if attr[0][:8] == 'command_'] + help_message = self._help_title + '\n\n' + help_message += self._help_before_list + '\n\n' + help_message += self._help_list_title + '\n' + for command_function in command_functions: + if command_function.__doc__ is not None: + help_message += ' /' + command_function.__name__[8:] + ' - ' + command_function.__doc__ + '\n' + else: + help_message += ' /' + command_function.__name__[8:] + ' - ' + '\n' + help_message += '\n' + help_message += self._help_after_list + return help_message + + def _command_not_found(self, update): + """Inform the telegram user that the command was not found.""" + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = 'Sorry, I did not understand the command: {command}. Please see /help for all available commands' + if self.is_reply: + self.bot.sendMessage(chat_id, message.format(command=update.message.text.split(' ')[0]), + reply_to_message_id=reply_to) + else: + self.bot.sendMessage(chat_id, message.format(command=update.message.text.split(' ')[0])) + + def command_help(self, update): + """ The help file. """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = self._generate_help() + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + From 9241534ba664f8818215400af2042f7f184284ac Mon Sep 17 00:00:00 2001 From: njittam Date: Wed, 2 Sep 2015 16:21:11 +0200 Subject: [PATCH 17/20] Create command_handler_example.py this is an example of how to write a commandHandler --- examples/command_handler_example.py | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 examples/command_handler_example.py diff --git a/examples/command_handler_example.py b/examples/command_handler_example.py new file mode 100644 index 000000000..33e1bc9f8 --- /dev/null +++ b/examples/command_handler_example.py @@ -0,0 +1,89 @@ +# There could be some unused imports +from inspect import getmembers, ismethod +import threading +import logging +import telegram +import time +from telegram import CommandHandlerWithHelp, CommandHandler +class ExampleCommandHandler(CommandHandlerWithHelp): + """This is an example how to use a CommandHandlerWithHelp or just a CommandHandler. + + If You want to use a CommandHandler it is very easy. + create a class which inherits a CommandHandler. + create a method in this class which start with 'command_' and takes 1 argument: 'update' (which comes directly from + getUpdate()). + If you inherit CommandHandlerWithHelp it also creates a nice /help for you. + """ + def __init__(self, bot): # only necessary for a WithHelp + super(ExampleCommandHandler, self).__init__(bot) + self._help_title = 'Welcome this is a help file!' # optional + self._help_before_list = """ + Yeah here I explain some things about this bot. + and of course I can do this in Multiple lines. + """ # default is empty + self._help_list_title = ' These are the available commands:' # optional + self._help_after_list = ' These are some footnotes' # default is empty + self.is_reply = True # default is True + + # only necessary if you want to override to default + def _command_not_found(self, update): + """Inform the telegram user that the command was not found.""" + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = "Sorry, I don't know how to do {command}.".format(command=update.message.text.split(' ')[0]) + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + # creates /test command. This code gets called when a telegram user enters /test + def command_test(self, update): + """ Test if the server is online. """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = 'Yeah, the server is online!' + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + # creates /parrot command + def command_parrot(self, update): + """ Says back what you say after the command""" + chat_id = update.message.chat.id + reply_to = update.message.message_id + send = update.message.text.split(' ') + message = update.message.text[len(send[0]):] + if len(send) == 1: + message = '...' + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + # creates /p command + def command_p(self, update): + """Does the same as parrot.""" + return self.command_parrot(update) + + # this doesn't create a command. + def another_test(self, update): + """ This won't be called by the CommandHandler. + + This is an example of a function that isn't a command in telegram. + Because it didn't start with 'command_'. + """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = 'Yeah, this is another test' + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + + +class Exampe2CommandHandler(CommandHandler): + """ + This is an example of a small working CommandHandler with only one command. + """ + def command_test(self, update): + """ Test if the server is online. """ + chat_id = update.message.chat.id + reply_to = update.message.message_id + message = 'Yeah, the server is online!' + self.bot.sendMessage(chat_id, message, reply_to_message_id=reply_to) + +if __name__ == '__main__': + import telegram + token = '' # use your own token here + Bot = telegram.Bot(token=token) + test_command_handler = ExampleCommandHandler(Bot) + test_command_handler.run() From 910959b672d4c007dcee5d013caa9f14a20a4776 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 4 Sep 2015 17:15:44 -0300 Subject: [PATCH 18/20] Raises error when chat_id is not set --- telegram/bot.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/telegram/bot.py b/telegram/bot.py index 502c35b39..b09907c91 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -146,6 +146,9 @@ class Bot(TelegramObject): """ url, data = func(self, *args, **kwargs) + if not kwargs.get('chat_id'): + raise TelegramError('Invalid chat_id.') + if kwargs.get('reply_to_message_id'): reply_to_message_id = kwargs.get('reply_to_message_id') data['reply_to_message_id'] = reply_to_message_id From 05b7fda4a1a47801bf3aea0554a3bdb968c62196 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 4 Sep 2015 17:53:39 -0300 Subject: [PATCH 19/20] Add certificate arg to setWebhook function --- telegram/bot.py | 15 +++++--- telegram/inputfile.py | 3 ++ testscommand_handlertest.py | 73 ------------------------------------- 3 files changed, 13 insertions(+), 78 deletions(-) delete mode 100644 testscommand_handlertest.py diff --git a/telegram/bot.py b/telegram/bot.py index b09907c91..c6ec61731 100644 --- a/telegram/bot.py +++ b/telegram/bot.py @@ -72,12 +72,12 @@ class Bot(TelegramObject): def info(func): """ - bla + Returns: """ @functools.wraps(func) def decorator(self, *args, **kwargs): """ - bla + decorator """ if not self.bot: self.getMe() @@ -660,7 +660,8 @@ class Bot(TelegramObject): @log def setWebhook(self, - webhook_url): + webhook_url=None, + certificate=None): """Use this method to specify a url and receive incoming updates via an outgoing webhook. Whenever there is an update for the bot, we will send an HTTPS POST request to the specified url, containing a @@ -677,12 +678,16 @@ class Bot(TelegramObject): """ url = '%s/setWebhook' % self.base_url - data = {'url': webhook_url} + data = {} + if webhook_url: + data['url'] = webhook_url + if certificate: + data['certificate'] = certificate json_data = self._requestUrl(url, 'POST', data=data) data = self._parseAndCheckTelegram(json_data) - return True + return data @staticmethod def _requestUrl(url, diff --git a/telegram/inputfile.py b/telegram/inputfile.py index aec76e8b8..e0f234f62 100644 --- a/telegram/inputfile.py +++ b/telegram/inputfile.py @@ -61,6 +61,9 @@ class InputFile(object): if 'voice' in data: self.input_name = 'voice' self.input_file = data.pop('voice') + if 'certificate' in data: + self.input_name = 'certificate' + self.input_file = data.pop('certificate') if isinstance(self.input_file, file): self.input_file_content = self.input_file.read() diff --git a/testscommand_handlertest.py b/testscommand_handlertest.py deleted file mode 100644 index 4916a8d15..000000000 --- a/testscommand_handlertest.py +++ /dev/null @@ -1,73 +0,0 @@ -import telegram -import unittest -import unittest.mock -from telegram import CommandHandler, CommandHandlerWithHelp - - -class CommandHandlerTmp(CommandHandler): - def __init__(self, *args, **kwargs): - super(CommandHandlerTmp, self).__init__(*args, **kwargs) - self.output = None - - def command_test(self, update): - self.output = 1 - - -class CommandHandlerTmp2(CommandHandlerWithHelp): - def __init__(self, *args, **kwargs): - super(CommandHandlerTmp2, self).__init__(*args, **kwargs) - self.output_test = None - - def command_test(self, update): - self.output_test = 1 - - -def fake_getUpdates(*args, **kwargs): - from_user = telegram.User(id=42, first_name='hello') - message = telegram.Message(message_id=42, from_user=from_user, date=None, chat=from_user, text='/test') - update = telegram.Update(update_id=42, message=message) - return [update] - -output_fsm = None - - -def fake_sendMessage(chat_id, message, *args, **kwargs): - global output_fsm - output_fsm = (chat_id, message) - return telegram.Message(43, 123, 000000, telegram.User(chat_id, 'test'), text=message) - - -class CommandHandlerTest(unittest.TestCase): - def setUp(self): - self.bot = unittest.mock.MagicMock() - self.bot.getUpdates = fake_getUpdates - self.bot.sendMessage = fake_sendMessage - - def test_get_command_func(self): - CH = CommandHandlerTmp(self.bot) - self.assertEqual(CH.command_test, CH._get_command_func('test')) - self.assertEqual(CH.command_test, CH._get_command_func('/test')) - self.assertEqual(None, CH._get_command_func('this function does not exsist')) - - def test_run_once(self): - CH = CommandHandlerTmp(self.bot) - self.assertEqual(CH.output, None) - threads, last_update = CH.run_once(make_thread=True) - for t in threads: - t.start() - for t in threads: - t.join() - self.assertEqual(CH.output, 1) - - def test_run(self): - pass # TODO implement test - - def test__command_not_found(self): - CH = CommandHandlerTmp(self.bot) - CH._command_not_found(self.bot.getUpdates()[0]) - self.assertEqual(output_fsm, (42, "Sorry, I didn't understand the command: /test.")) - - -if __name__ == '__main__': - import sys - unittest.main(sys.argv) From 62f06da897d2a05c03cce47a401b7a4d744b63f7 Mon Sep 17 00:00:00 2001 From: leandrotoledo Date: Fri, 4 Sep 2015 17:54:30 -0300 Subject: [PATCH 20/20] Move command_handler tests to tests/ folder --- tests/test_command_handler.py | 75 +++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/test_command_handler.py diff --git a/tests/test_command_handler.py b/tests/test_command_handler.py new file mode 100644 index 000000000..171b24ef4 --- /dev/null +++ b/tests/test_command_handler.py @@ -0,0 +1,75 @@ +import sys +sys.path.append('.') +import telegram +import unittest +import unittest.mock +from telegram.command_handler import CommandHandler, CommandHandlerWithHelp + + +class CommandHandlerTmp(CommandHandler): + def __init__(self, *args, **kwargs): + super(CommandHandlerTmp, self).__init__(*args, **kwargs) + self.output = None + + def command_test(self, update): + self.output = 1 + + +class CommandHandlerTmp2(CommandHandlerWithHelp): + def __init__(self, *args, **kwargs): + super(CommandHandlerTmp2, self).__init__(*args, **kwargs) + self.output_test = None + + def command_test(self, update): + self.output_test = 1 + + +def fake_getUpdates(*args, **kwargs): + from_user = telegram.User(id=42, first_name='hello') + message = telegram.Message(message_id=42, from_user=from_user, date=None, chat=from_user, text='/test') + update = telegram.Update(update_id=42, message=message) + return [update] + +output_fsm = None + + +def fake_sendMessage(chat_id, message, *args, **kwargs): + global output_fsm + output_fsm = (chat_id, message) + return telegram.Message(43, 123, 000000, telegram.User(chat_id, 'test'), text=message) + + +class CommandHandlerTest(unittest.TestCase): + def setUp(self): + self.bot = unittest.mock.MagicMock() + self.bot.getUpdates = fake_getUpdates + self.bot.sendMessage = fake_sendMessage + + def test_get_command_func(self): + CH = CommandHandlerTmp(self.bot) + self.assertEqual(CH.command_test, CH._get_command_func('test')) + self.assertEqual(CH.command_test, CH._get_command_func('/test')) + self.assertEqual(None, CH._get_command_func('this function does not exsist')) + + def test_run_once(self): + CH = CommandHandlerTmp(self.bot) + self.assertEqual(CH.output, None) + threads, last_update = CH.run_once(make_thread=True) + for t in threads: + t.start() + for t in threads: + t.join() + self.assertEqual(CH.output, 1) + + def test_run(self): + pass # TODO implement test + + def test__command_not_found(self): + CH = CommandHandlerTmp(self.bot) + CH._command_not_found(self.bot.getUpdates()[0]) + self.assertEqual(output_fsm, (42, "Sorry, I didn't understand the command: /test.")) + + +if __name__ == '__main__': + import sys + unittest.main(sys.argv)