Merge pull request #308 from python-telegram-bot/bot2.1

New methods for Bot 2.1 API
This commit is contained in:
Leandro Toledo 2016-05-28 12:14:01 -03:00
commit 792ad62fe8
26 changed files with 516 additions and 126 deletions

View file

@ -23,6 +23,7 @@ from sys import version_info
from .base import TelegramObject from .base import TelegramObject
from .user import User from .user import User
from .chat import Chat from .chat import Chat
from .chatmember import ChatMember
from .photosize import PhotoSize from .photosize import PhotoSize
from .audio import Audio from .audio import Audio
from .voice import Voice from .voice import Voice
@ -82,24 +83,25 @@ from .bot import Bot
__author__ = 'devs@python-telegram-bot.org' __author__ = 'devs@python-telegram-bot.org'
__version__ = '4.1.2' __version__ = '4.1.2'
__all__ = ['Audio', 'Bot', 'Chat', 'ChatAction', 'ChosenInlineResult', 'CallbackQuery', 'Contact', __all__ = ['Audio', 'Bot', 'Chat', 'ChatMember', 'ChatAction', 'ChosenInlineResult',
'Document', 'Emoji', 'File', 'ForceReply', 'InlineKeyboardButton', 'CallbackQuery', 'Contact', 'Document', 'Emoji', 'File', 'ForceReply',
'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult', 'InlineQueryResult', 'InlineKeyboardButton', 'InlineKeyboardMarkup', 'InlineQuery', 'InlineQueryResult',
'InlineQueryResultArticle', 'InlineQueryResultAudio', 'InlineQueryResultCachedAudio', 'InlineQueryResult', 'InlineQueryResultArticle', 'InlineQueryResultAudio',
'InlineQueryResultCachedDocument', 'InlineQueryResultCachedGif', 'InlineQueryResultCachedAudio', 'InlineQueryResultCachedDocument',
'InlineQueryResultCachedMpeg4Gif', 'InlineQueryResultCachedPhoto', 'InlineQueryResultCachedGif', 'InlineQueryResultCachedMpeg4Gif',
'InlineQueryResultCachedSticker', 'InlineQueryResultCachedVideo', 'InlineQueryResultCachedPhoto', 'InlineQueryResultCachedSticker',
'InlineQueryResultCachedVoice', 'InlineQueryResultContact', 'InlineQueryResultDocument', 'InlineQueryResultCachedVideo', 'InlineQueryResultCachedVoice',
'InlineQueryResultGif', 'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif', 'InlineQueryResultContact', 'InlineQueryResultDocument', 'InlineQueryResultGif',
'InlineQueryResultPhoto', 'InlineQueryResultVenue', 'InlineQueryResultVideo', 'InlineQueryResultLocation', 'InlineQueryResultMpeg4Gif', 'InlineQueryResultPhoto',
'InlineQueryResultVoice', 'InputContactMessageContent', 'InputFile', 'InlineQueryResultVenue', 'InlineQueryResultVideo', 'InlineQueryResultVoice',
'InputLocationMessageContent', 'InputMessageContent', 'InputTextMessageContent', 'InputContactMessageContent', 'InputFile', 'InputLocationMessageContent',
'InputVenueMessageContent', 'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'InputMessageContent', 'InputTextMessageContent', 'InputVenueMessageContent',
'NullHandler', 'ParseMode', 'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'KeyboardButton', 'Location', 'Message', 'MessageEntity', 'NullHandler', 'ParseMode',
'ReplyMarkup', 'Sticker', 'TelegramError', 'TelegramObject', 'Update', 'User', 'PhotoSize', 'ReplyKeyboardHide', 'ReplyKeyboardMarkup', 'ReplyMarkup', 'Sticker',
'UserProfilePhotos', 'Venue', 'Video', 'Voice'] 'TelegramError', 'TelegramObject', 'Update', 'User', 'UserProfilePhotos', 'Venue',
'Video', 'Voice']
if version_info < (2, 7): if version_info < (2, 7):
from warnings import warn from warnings import warn
warn("python-telegram-bot will stop supporting Python 2.6 in a future release. " warn("python-telegram-bot will stop supporting Python 2.6 in a future release. "
"Please upgrade your Python!") "Please upgrade your Python version to at least Python 2.7!")

View file

@ -19,11 +19,11 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents a Telegram Bot.""" """This module contains a object that represents a Telegram Bot."""
import logging
import functools import functools
import logging
from telegram import (User, Message, Update, UserProfilePhotos, File, ReplyMarkup, TelegramObject, from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
NullHandler) ReplyMarkup, TelegramObject, NullHandler)
from telegram.error import InvalidToken from telegram.error import InvalidToken
from telegram.utils import request from telegram.utils import request
@ -154,7 +154,7 @@ class Bot(TelegramObject):
return decorator return decorator
@log @log
def getMe(self): def getMe(self, **kwargs):
"""A simple method for testing your bot's auth token. """A simple method for testing your bot's auth token.
Returns: Returns:
@ -1182,7 +1182,7 @@ class Bot(TelegramObject):
return url, data return url, data
@log @log
def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=.2): def getUpdates(self, offset=None, limit=100, timeout=0, network_delay=.2, **kwargs):
"""Use this method to receive incoming updates using long polling. """Use this method to receive incoming updates using long polling.
Args: Args:
@ -1271,6 +1271,166 @@ class Bot(TelegramObject):
return result return result
@log
def leaveChat(self, chat_id, **kwargs):
"""Use this method for your bot to leave a group, supergroup or
channel.
Args:
chat_id:
Unique identifier for the target chat or username of the target
channel (in the format @channelusername).
Keyword Args:
timeout (Optional[float]): If this value is specified, use it as
the definitive timeout (in seconds) for urlopen() operations.
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/leaveChat'.format(self.base_url)
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
return result
@log
def getChat(self, chat_id, **kwargs):
"""Use this method to get up to date information about the chat
(current name of the user for one-on-one conversations, current
username of a user, group or channel, etc.).
Args:
chat_id:
Unique identifier for the target chat or username of the target
channel (in the format @channelusername).
Keyword Args:
timeout (Optional[float]): If this value is specified, use it as
the definitive timeout (in seconds) for urlopen() operations.
Returns:
:class:`telegram.Chat`: On success, :class:`telegram.Chat` is
returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/getChat'.format(self.base_url)
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
return Chat.de_json(result)
@log
def getChatAdministrators(self, chat_id, **kwargs):
"""Use this method to get a list of administrators in a chat. On
success, returns an Array of ChatMember objects that contains
information about all chat administrators except other bots. If the
chat is a group or a supergroup and no administrators were appointed,
only the creator will be returned.
Args:
chat_id:
Unique identifier for the target chat or username of the target
channel (in the format @channelusername).
Keyword Args:
timeout (Optional[float]): If this value is specified, use it as
the definitive timeout (in seconds) for urlopen() operations.
Returns:
list[:class:`telegram.ChatMember`]: On success, a list of
:class:`telegram.ChatMember` objects are returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/getChatAdministrators'.format(self.base_url)
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
return [ChatMember.de_json(x) for x in result]
@log
def getChatMembersCount(self, chat_id, **kwargs):
"""Use this method to get the number of members in a chat.
Args:
chat_id:
Unique identifier for the target chat or username of the target
channel (in the format @channelusername).
Keyword Args:
timeout (Optional[float]): If this value is specified, use it as
the definitive timeout (in seconds) for urlopen() operations.
Returns:
int: On success, an `int` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/getChatMembersCount'.format(self.base_url)
data = {'chat_id': chat_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
return result
@log
def getChatMember(self, chat_id, user_id, **kwargs):
"""Use this method to get information about a member of a chat.
Args:
chat_id:
Unique identifier for the target chat or username of the target
channel (in the format @channelusername).
user_id:
Unique identifier of the target user.
Keyword Args:
timeout (Optional[float]): If this value is specified, use it as
the definitive timeout (in seconds) for urlopen() operations.
Returns:
:class:`telegram.ChatMember`: On success,
:class:`telegram.ChatMember` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/getChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
result = request.post(url, data, timeout=kwargs.get('timeout'))
return ChatMember.de_json(result)
@staticmethod @staticmethod
def de_json(data): def de_json(data):
data = super(Bot, Bot).de_json(data) data = super(Bot, Bot).de_json(data)
@ -1314,3 +1474,8 @@ class Bot(TelegramObject):
edit_message_reply_markup = editMessageReplyMarkup edit_message_reply_markup = editMessageReplyMarkup
get_updates = getUpdates get_updates = getUpdates
set_webhook = setWebhook set_webhook = setWebhook
leave_chat = leaveChat
get_chat = getChat
get_chat_administrators = getChatAdministrators
get_chat_member = getChatMember
get_chat_members_count = getChatMembersCount

View file

@ -42,6 +42,11 @@ class Chat(TelegramObject):
type (Optional[str]): type (Optional[str]):
""" """
PRIVATE = 'private'
GROUP = 'group'
SUPERGROUP = 'supergroup'
CHANNEL = 'channel'
def __init__(self, id, type, **kwargs): def __init__(self, id, type, **kwargs):
# Required # Required
self.id = int(id) self.id = int(id)

62
telegram/chatmember.py Normal file
View file

@ -0,0 +1,62 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2016
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents a Telegram ChatMember."""
from telegram import User, TelegramObject
class ChatMember(TelegramObject):
"""This object represents a Telegram ChatMember.
Attributes:
user (:class:`telegram.User`): Information about the user.
status (str): The member's status in the chat. Can be 'creator', 'administrator', 'member',
'left' or 'kicked'.
Args:
user (:class:`telegram.User`):
status (str):
"""
CREATOR = 'creator'
ADMINISTRATOR = 'administrator'
MEMBER = 'member'
LEFT = 'left'
KICKED = 'kicked'
def __init__(self, user, status, **kwargs):
# Required
self.user = user
self.status = status
@staticmethod
def de_json(data):
"""
Args:
data (dict):
Returns:
telegram.ChatMember:
"""
if not data:
return None
data['user'] = User.de_json(data.get('user'))
return ChatMember(**data)

View file

@ -41,7 +41,13 @@ class ChosenInlineResult(TelegramObject):
""" """
def __init__(self, result_id, from_user, query, location=None, inline_message_id=None): def __init__(self,
result_id,
from_user,
query,
location=None,
inline_message_id=None,
**kwargs):
# Required # Required
self.result_id = result_id self.result_id = result_id
self.from_user = from_user self.from_user = from_user

View file

@ -51,6 +51,7 @@ class TelegramError(Exception):
msg = _lstrip_str(message, 'Error: ') msg = _lstrip_str(message, 'Error: ')
msg = _lstrip_str(msg, '[Error]: ') msg = _lstrip_str(msg, '[Error]: ')
msg = _lstrip_str(msg, 'Bad Request: ')
if msg != message: if msg != message:
# api_error - capitalize the msg... # api_error - capitalize the msg...
msg = msg.capitalize() msg = msg.capitalize()
@ -76,6 +77,10 @@ class NetworkError(TelegramError):
pass pass
class BadRequest(NetworkError):
pass
class TimedOut(NetworkError): class TimedOut(NetworkError):
def __init__(self): def __init__(self):

View file

@ -34,6 +34,8 @@ class CommandHandler(Handler):
callback (function): A function that takes ``bot, update`` as callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update`` positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler. has determined that an update should be processed by this handler.
allow_edited (Optional[bool]): If the handler should also accept edited messages.
Default is ``False``
pass_args (optional[bool]): If the handler should be passed the pass_args (optional[bool]): If the handler should be passed the
arguments passed to the command as a keyword argument called ` arguments passed to the command as a keyword argument called `
``args``. It will contain a list of strings, which is the text ``args``. It will contain a list of strings, which is the text
@ -43,21 +45,35 @@ class CommandHandler(Handler):
be used to insert updates. Default is ``False`` be used to insert updates. Default is ``False``
""" """
def __init__(self, command, callback, pass_args=False, pass_update_queue=False): def __init__(self,
command,
callback,
allow_edited=False,
pass_args=False,
pass_update_queue=False):
super(CommandHandler, self).__init__(callback, pass_update_queue) super(CommandHandler, self).__init__(callback, pass_update_queue)
self.command = command self.command = command
self.allow_edited = allow_edited
self.pass_args = pass_args self.pass_args = pass_args
def check_update(self, update): def check_update(self, update):
return (isinstance(update, Update) and update.message and update.message.text if (isinstance(update, Update)
and update.message.text.startswith('/') and (update.message or update.edited_message and self.allow_edited)):
and update.message.text[1:].split(' ')[0].split('@')[0] == self.command) message = update.message or update.edited_message
return (message.text and message.text.startswith('/')
and message.text[1:].split(' ')[0].split('@')[0] == self.command)
else:
return False
def handle_update(self, update, dispatcher): def handle_update(self, update, dispatcher):
optional_args = self.collect_optional_args(dispatcher) optional_args = self.collect_optional_args(dispatcher)
message = update.message or update.edited_message
if self.pass_args: if self.pass_args:
optional_args['args'] = update.message.text.split(' ')[1:] optional_args['args'] = message.text.split(' ')[1:]
self.callback(dispatcher.bot, update, **optional_args) self.callback(dispatcher.bot, update, **optional_args)

View file

@ -30,57 +30,56 @@ class Filters(object):
""" """
@staticmethod @staticmethod
def text(update): def text(message):
return update.message.text and not update.message.text.startswith('/') return message.text and not message.text.startswith('/')
@staticmethod @staticmethod
def command(update): def command(message):
return update.message.text and update.message.text.startswith('/') return message.text and message.text.startswith('/')
@staticmethod @staticmethod
def audio(update): def audio(message):
return bool(update.message.audio) return bool(message.audio)
@staticmethod @staticmethod
def document(update): def document(message):
return bool(update.message.document) return bool(message.document)
@staticmethod @staticmethod
def photo(update): def photo(message):
return bool(update.message.photo) return bool(message.photo)
@staticmethod @staticmethod
def sticker(update): def sticker(message):
return bool(update.message.sticker) return bool(message.sticker)
@staticmethod @staticmethod
def video(update): def video(message):
return bool(update.message.video) return bool(message.video)
@staticmethod @staticmethod
def voice(update): def voice(message):
return bool(update.message.voice) return bool(message.voice)
@staticmethod @staticmethod
def contact(update): def contact(message):
return bool(update.message.contact) return bool(message.contact)
@staticmethod @staticmethod
def location(update): def location(message):
return bool(update.message.location) return bool(message.location)
@staticmethod @staticmethod
def venue(update): def venue(message):
return bool(update.message.venue) return bool(message.venue)
@staticmethod @staticmethod
def status_update(update): def status_update(message):
return bool(update.message.new_chat_member or update.message.left_chat_member return bool(message.new_chat_member or message.left_chat_member or message.new_chat_title
or update.message.new_chat_title or update.message.new_chat_photo or message.new_chat_photo or message.delete_chat_photo
or update.message.delete_chat_photo or update.message.group_chat_created or message.group_chat_created or message.supergroup_chat_created
or update.message.supergroup_chat_created or message.channel_chat_created or message.migrate_to_chat_id
or update.message.channel_chat_created or update.message.migrate_to_chat_id or message.migrate_from_chat_id or message.pinned_message)
or update.message.migrate_from_chat_id or update.message.pinned_message)
class MessageHandler(Handler): class MessageHandler(Handler):
@ -99,23 +98,32 @@ class MessageHandler(Handler):
callback (function): A function that takes ``bot, update`` as callback (function): A function that takes ``bot, update`` as
positional arguments. It will be called when the ``check_update`` positional arguments. It will be called when the ``check_update``
has determined that an update should be processed by this handler. has determined that an update should be processed by this handler.
allow_edited (Optional[bool]): If the handler should also accept edited messages.
Default is ``False``
pass_update_queue (optional[bool]): If the handler should be passed the pass_update_queue (optional[bool]): If the handler should be passed the
update queue as a keyword argument called ``update_queue``. It can update queue as a keyword argument called ``update_queue``. It can
be used to insert updates. Default is ``False`` be used to insert updates. Default is ``False``
""" """
def __init__(self, filters, callback, pass_update_queue=False): def __init__(self, filters, callback, allow_edited=False, pass_update_queue=False):
super(MessageHandler, self).__init__(callback, pass_update_queue) super(MessageHandler, self).__init__(callback, pass_update_queue)
self.filters = filters self.filters = filters
self.allow_edited = allow_edited
def check_update(self, update): def check_update(self, update):
if isinstance(update, Update) and update.message: if (isinstance(update, Update)
and (update.message or update.edited_message and self.allow_edited)):
if not self.filters: if not self.filters:
res = True res = True
else: else:
res = any(func(update) for func in self.filters) message = update.message or update.edited_message
res = any(func(message) for func in self.filters)
else: else:
res = False res = False
return res return res
def handle_update(self, update, dispatcher): def handle_update(self, update, dispatcher):

View file

@ -33,7 +33,7 @@ class InlineKeyboardMarkup(ReplyMarkup):
""" """
def __init__(self, inline_keyboard): def __init__(self, inline_keyboard, **kwargs):
# Required # Required
self.inline_keyboard = inline_keyboard self.inline_keyboard = inline_keyboard

View file

@ -25,7 +25,7 @@ from telegram import InputMessageContent
class InputContactMessageContent(InputMessageContent): class InputContactMessageContent(InputMessageContent):
"""Base class for Telegram InputContactMessageContent Objects""" """Base class for Telegram InputContactMessageContent Objects"""
def __init__(self, phone_number, first_name, last_name=None): def __init__(self, phone_number, first_name, last_name=None, **kwargs):
# Required # Required
self.phone_number = phone_number self.phone_number = phone_number
self.first_name = first_name self.first_name = first_name

View file

@ -25,7 +25,7 @@ from telegram import InputMessageContent
class InputLocationMessageContent(InputMessageContent): class InputLocationMessageContent(InputMessageContent):
"""Base class for Telegram InputLocationMessageContent Objects""" """Base class for Telegram InputLocationMessageContent Objects"""
def __init__(self, latitude, longitude): def __init__(self, latitude, longitude, **kwargs):
# Required # Required
self.latitude = latitude self.latitude = latitude
self.longitude = longitude self.longitude = longitude

View file

@ -39,14 +39,14 @@ class InputMessageContent(TelegramObject):
pass pass
try: try:
from telegram import InputLocationMessageContent from telegram import InputVenueMessageContent
return InputLocationMessageContent.de_json(data) return InputVenueMessageContent.de_json(data)
except TypeError: except TypeError:
pass pass
try: try:
from telegram import InputVenueMessageContent from telegram import InputLocationMessageContent
return InputVenueMessageContent.de_json(data) return InputLocationMessageContent.de_json(data)
except TypeError: except TypeError:
pass pass

View file

@ -25,7 +25,7 @@ from telegram import InputMessageContent
class InputTextMessageContent(InputMessageContent): class InputTextMessageContent(InputMessageContent):
"""Base class for Telegram InputTextMessageContent Objects""" """Base class for Telegram InputTextMessageContent Objects"""
def __init__(self, message_text, parse_mode=None, disable_web_page_preview=None): def __init__(self, message_text, parse_mode=None, disable_web_page_preview=None, **kwargs):
# Required # Required
self.message_text = message_text self.message_text = message_text
# Optionals # Optionals

View file

@ -25,7 +25,7 @@ from telegram import InputMessageContent
class InputVenueMessageContent(InputMessageContent): class InputVenueMessageContent(InputMessageContent):
"""Base class for Telegram InputVenueMessageContent Objects""" """Base class for Telegram InputVenueMessageContent Objects"""
def __init__(self, latitude, longitude, title, address, foursquare_id=None): def __init__(self, latitude, longitude, title, address, foursquare_id=None, **kwargs):
# Required # Required
self.latitude = latitude self.latitude = latitude
self.longitude = longitude self.longitude = longitude

View file

@ -33,7 +33,7 @@ class KeyboardButton(TelegramObject):
request_contact (Optional[bool]): request_contact (Optional[bool]):
""" """
def __init__(self, text, request_contact=None, request_location=None): def __init__(self, text, request_contact=None, request_location=None, **kwargs):
# Required # Required
self.text = text self.text = text
# Optionals # Optionals

View file

@ -33,7 +33,7 @@ class Location(TelegramObject):
latitude (float): latitude (float):
""" """
def __init__(self, longitude, latitude): def __init__(self, longitude, latitude, **kwargs):
# Required # Required
self.longitude = float(longitude) self.longitude = float(longitude)
self.latitude = float(latitude) self.latitude = float(latitude)

View file

@ -40,6 +40,7 @@ class Message(TelegramObject):
forward_from_chat (:class:`telegram.Chat`): forward_from_chat (:class:`telegram.Chat`):
forward_date (:class:`datetime.datetime`): forward_date (:class:`datetime.datetime`):
reply_to_message (:class:`telegram.Message`): reply_to_message (:class:`telegram.Message`):
edit_date (:class:`datetime.datetime`):
text (str): text (str):
audio (:class:`telegram.Audio`): audio (:class:`telegram.Audio`):
document (:class:`telegram.Document`): document (:class:`telegram.Document`):
@ -80,6 +81,7 @@ class Message(TelegramObject):
forward_from_chat (:class:`telegram.Chat`): forward_from_chat (:class:`telegram.Chat`):
forward_date (Optional[:class:`datetime.datetime`]): forward_date (Optional[:class:`datetime.datetime`]):
reply_to_message (Optional[:class:`telegram.Message`]): reply_to_message (Optional[:class:`telegram.Message`]):
edit_date (Optional[:class:`datetime.datetime`]):
text (Optional[str]): text (Optional[str]):
audio (Optional[:class:`telegram.Audio`]): audio (Optional[:class:`telegram.Audio`]):
document (Optional[:class:`telegram.Document`]): document (Optional[:class:`telegram.Document`]):
@ -113,6 +115,7 @@ class Message(TelegramObject):
self.forward_from_chat = kwargs.get('forward_from_chat') self.forward_from_chat = kwargs.get('forward_from_chat')
self.forward_date = kwargs.get('forward_date') self.forward_date = kwargs.get('forward_date')
self.reply_to_message = kwargs.get('reply_to_message') self.reply_to_message = kwargs.get('reply_to_message')
self.edit_date = kwargs.get('edit_date')
self.text = kwargs.get('text', '') self.text = kwargs.get('text', '')
self.entities = kwargs.get('entities', list()) self.entities = kwargs.get('entities', list())
self.audio = kwargs.get('audio') self.audio = kwargs.get('audio')
@ -162,6 +165,7 @@ class Message(TelegramObject):
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat')) data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'))
data['forward_date'] = Message._fromtimestamp(data.get('forward_date')) data['forward_date'] = Message._fromtimestamp(data.get('forward_date'))
data['reply_to_message'] = Message.de_json(data.get('reply_to_message')) data['reply_to_message'] = Message.de_json(data.get('reply_to_message'))
data['edit_date'] = Message._fromtimestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio')) data['audio'] = Audio.de_json(data.get('audio'))
data['document'] = Document.de_json(data.get('document')) data['document'] = Document.de_json(data.get('document'))
data['photo'] = PhotoSize.de_list(data.get('photo')) data['photo'] = PhotoSize.de_list(data.get('photo'))
@ -197,6 +201,8 @@ class Message(TelegramObject):
# Optionals # Optionals
if self.forward_date: if self.forward_date:
data['forward_date'] = self._totimestamp(self.forward_date) data['forward_date'] = self._totimestamp(self.forward_date)
if self.edit_date:
data['edit_date'] = self._totimestamp(self.edit_date)
if self.photo: if self.photo:
data['photo'] = [p.to_dict() for p in self.photo] data['photo'] = [p.to_dict() for p in self.photo]
if self.entities: if self.entities:

View file

@ -18,7 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains a object that represents a Telegram MessageEntity.""" """This module contains a object that represents a Telegram MessageEntity."""
from telegram import TelegramObject from telegram import User, TelegramObject
class MessageEntity(TelegramObject): class MessageEntity(TelegramObject):
@ -31,6 +31,7 @@ class MessageEntity(TelegramObject):
offset (int): offset (int):
length (int): length (int):
url (Optional[str]): url (Optional[str]):
user (Optional[:class:`telegram.User`]):
""" """
def __init__(self, type, offset, length, **kwargs): def __init__(self, type, offset, length, **kwargs):
@ -40,11 +41,14 @@ class MessageEntity(TelegramObject):
self.length = length self.length = length
# Optionals # Optionals
self.url = kwargs.get('url') self.url = kwargs.get('url')
self.user = kwargs.get('user')
@staticmethod @staticmethod
def de_json(data): def de_json(data):
data = super(MessageEntity, MessageEntity).de_json(data) data = super(MessageEntity, MessageEntity).de_json(data)
data['user'] = User.de_json(data.get('user'))
return MessageEntity(**data) return MessageEntity(**data)
@staticmethod @staticmethod

View file

@ -27,6 +27,7 @@ class Update(TelegramObject):
Attributes: Attributes:
update_id (int): update_id (int):
message (:class:`telegram.Message`): message (:class:`telegram.Message`):
edited_message (:class:`telegram.Message`):
inline_query (:class:`telegram.InlineQuery`): inline_query (:class:`telegram.InlineQuery`):
chosen_inline_result (:class:`telegram.ChosenInlineResult`): chosen_inline_result (:class:`telegram.ChosenInlineResult`):
callback_query (:class:`telegram.CallbackQuery`): callback_query (:class:`telegram.CallbackQuery`):
@ -37,6 +38,7 @@ class Update(TelegramObject):
Keyword Args: Keyword Args:
message (Optional[:class:`telegram.Message`]): message (Optional[:class:`telegram.Message`]):
edited_message (Optional[:class:`telegram.Message`]):
inline_query (Optional[:class:`telegram.InlineQuery`]): inline_query (Optional[:class:`telegram.InlineQuery`]):
chosen_inline_result (Optional[:class:`telegram.ChosenInlineResult`]) chosen_inline_result (Optional[:class:`telegram.ChosenInlineResult`])
callback_query (Optional[:class:`telegram.CallbackQuery`]): callback_query (Optional[:class:`telegram.CallbackQuery`]):
@ -47,6 +49,7 @@ class Update(TelegramObject):
self.update_id = int(update_id) self.update_id = int(update_id)
# Optionals # Optionals
self.message = kwargs.get('message') self.message = kwargs.get('message')
self.edited_message = kwargs.get('edited_message')
self.inline_query = kwargs.get('inline_query') self.inline_query = kwargs.get('inline_query')
self.chosen_inline_result = kwargs.get('chosen_inline_result') self.chosen_inline_result = kwargs.get('chosen_inline_result')
self.callback_query = kwargs.get('callback_query') self.callback_query = kwargs.get('callback_query')
@ -64,6 +67,7 @@ class Update(TelegramObject):
return None return None
data['message'] = Message.de_json(data.get('message')) data['message'] = Message.de_json(data.get('message'))
data['edited_message'] = Message.de_json(data.get('edited_message'))
data['inline_query'] = InlineQuery.de_json(data.get('inline_query')) data['inline_query'] = InlineQuery.de_json(data.get('inline_query'))
data['chosen_inline_result'] = ChosenInlineResult.de_json(data.get('chosen_inline_result')) data['chosen_inline_result'] = ChosenInlineResult.de_json(data.get('chosen_inline_result'))
data['callback_query'] = CallbackQuery.de_json(data.get('callback_query')) data['callback_query'] = CallbackQuery.de_json(data.get('callback_query'))

View file

@ -34,7 +34,7 @@ class UserProfilePhotos(TelegramObject):
photos (List[List[:class:`telegram.PhotoSize`]]): photos (List[List[:class:`telegram.PhotoSize`]]):
""" """
def __init__(self, total_count, photos): def __init__(self, total_count, photos, **kwargs):
# Required # Required
self.total_count = int(total_count) self.total_count = int(total_count)
self.photos = photos self.photos = photos

View file

@ -28,7 +28,7 @@ from future.moves.urllib.error import HTTPError, URLError
from future.moves.urllib.request import urlopen, urlretrieve, Request from future.moves.urllib.request import urlopen, urlretrieve, Request
from telegram import (InputFile, TelegramError) from telegram import (InputFile, TelegramError)
from telegram.error import Unauthorized, NetworkError, TimedOut from telegram.error import Unauthorized, NetworkError, TimedOut, BadRequest
def _parse(json_data): def _parse(json_data):
@ -67,13 +67,15 @@ def _try_except_req(func):
# come first. # come first.
errcode = error.getcode() errcode = error.getcode()
if errcode in (401, 403):
raise Unauthorized()
elif errcode == 502:
raise NetworkError('Bad Gateway')
try: try:
message = _parse(error.read()) message = _parse(error.read())
if errcode in (401, 403):
raise Unauthorized()
elif errcode == 400:
raise BadRequest(message)
elif errcode == 502:
raise NetworkError('Bad Gateway')
except ValueError: except ValueError:
message = 'Unknown HTTPError {0}'.format(error.getcode()) message = 'Unknown HTTPError {0}'.format(error.getcode())

View file

@ -32,7 +32,7 @@ class Venue(TelegramObject):
foursquare_id (Optional[str]): foursquare_id (Optional[str]):
""" """
def __init__(self, location, title, address, foursquare_id=None): def __init__(self, location, title, address, foursquare_id=None, **kwargs):
# Required # Required
self.location = location self.location = location
self.title = title self.title = title

View file

@ -40,6 +40,8 @@ class BaseTest(object):
'133505823:AAHZFMHno3mzVLErU5b5jJvaeG--qUyLyG0')) '133505823:AAHZFMHno3mzVLErU5b5jJvaeG--qUyLyG0'))
chat_id = os.environ.get('CHAT_ID', '12173560') chat_id = os.environ.get('CHAT_ID', '12173560')
self._group_id = os.environ.get('GROUP_ID', '-49740850')
self._channel_id = os.environ.get('CHANNEL_ID', '@pythontelegrambottests')
self._bot = bot self._bot = bot
self._chat_id = chat_id self._chat_id = chat_id

View file

@ -45,11 +45,7 @@ class BotTest(BaseTest, unittest.TestCase):
bot = self._bot.getMe() bot = self._bot.getMe()
self.assertTrue(self.is_json(bot.to_json())) self.assertTrue(self.is_json(bot.to_json()))
self.assertEqual(bot.id, 133505823) self._testUserEqualsBot(bot)
self.assertEqual(bot.first_name, 'PythonTelegramBot')
self.assertEqual(bot.last_name, '')
self.assertEqual(bot.username, 'PythonTelegramBot')
self.assertEqual(bot.name, '@PythonTelegramBot')
@flaky(3, 1) @flaky(3, 1)
@timeout(10) @timeout(10)
@ -75,7 +71,7 @@ class BotTest(BaseTest, unittest.TestCase):
@flaky(3, 1) @flaky(3, 1)
@timeout(10) @timeout(10)
def testGetUpdates(self): def testGetUpdates(self):
updates = self._bot.getUpdates() updates = self._bot.getUpdates(timeout=1)
if updates: if updates:
self.assertTrue(self.is_json(updates[0].to_json())) self.assertTrue(self.is_json(updates[0].to_json()))
@ -212,6 +208,63 @@ class BotTest(BaseTest, unittest.TestCase):
bot.getMe() bot.getMe()
@flaky(3, 1)
@timeout(10)
def testLeaveChat(self):
with self.assertRaisesRegexp(telegram.error.BadRequest, 'Chat not found'):
chat = self._bot.leaveChat(-123456)
with self.assertRaisesRegexp(telegram.error.NetworkError, 'Chat not found'):
chat = self._bot.leaveChat(-123456)
@flaky(3, 1)
@timeout(10)
def testGetChat(self):
chat = self._bot.getChat(self._group_id)
self.assertTrue(self.is_json(chat.to_json()))
self.assertEqual(chat.type, "group")
self.assertEqual(chat.title, ">>> telegram.Bot() - Developers")
self.assertEqual(chat.id, int(self._group_id))
@flaky(3, 1)
@timeout(10)
def testGetChatAdministrators(self):
admins = self._bot.getChatAdministrators(self._channel_id)
self.assertTrue(isinstance(admins, list))
self.assertTrue(self.is_json(admins[0].to_json()))
for a in admins:
self.assertTrue(a.status in ("administrator", "creator"))
bot = [a.user for a in admins if a.user.id == 133505823][0]
self._testUserEqualsBot(bot)
@flaky(3, 1)
@timeout(10)
def testGetChatMembersCount(self):
count = self._bot.getChatMembersCount(self._channel_id)
self.assertTrue(isinstance(count, int))
self.assertTrue(count > 3)
@flaky(3, 1)
@timeout(10)
def testGetChatMember(self):
chat_member = self._bot.getChatMember(self._channel_id, 133505823)
bot = chat_member.user
self.assertTrue(self.is_json(chat_member.to_json()))
self.assertEqual(chat_member.status, "administrator")
self._testUserEqualsBot(bot)
def _testUserEqualsBot(self, user):
"""Tests if user is our trusty @PythonTelegramBot."""
self.assertEqual(user.id, 133505823)
self.assertEqual(user.first_name, 'PythonTelegramBot')
self.assertEqual(user.last_name, '')
self.assertEqual(user.username, 'PythonTelegramBot')
self.assertEqual(user.name, '@PythonTelegramBot')
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -26,7 +26,7 @@ from datetime import datetime
sys.path.append('.') sys.path.append('.')
from telegram import Update, Message, User, Chat from telegram import Message, User, Chat
from telegram.ext import Filters from telegram.ext import Filters
from tests.base import BaseTest from tests.base import BaseTest
@ -36,119 +36,118 @@ class FiltersTest(BaseTest, unittest.TestCase):
def setUp(self): def setUp(self):
self.message = Message(0, User(0, "Testuser"), datetime.now(), Chat(0, 'private')) self.message = Message(0, User(0, "Testuser"), datetime.now(), Chat(0, 'private'))
self.update = Update(0, message=self.message)
def test_filters_text(self): def test_filters_text(self):
self.message.text = 'test' self.message.text = 'test'
self.assertTrue(Filters.text(self.update)) self.assertTrue(Filters.text(self.message))
self.message.text = '/test' self.message.text = '/test'
self.assertFalse(Filters.text(self.update)) self.assertFalse(Filters.text(self.message))
def test_filters_command(self): def test_filters_command(self):
self.message.text = 'test' self.message.text = 'test'
self.assertFalse(Filters.command(self.update)) self.assertFalse(Filters.command(self.message))
self.message.text = '/test' self.message.text = '/test'
self.assertTrue(Filters.command(self.update)) self.assertTrue(Filters.command(self.message))
def test_filters_audio(self): def test_filters_audio(self):
self.message.audio = 'test' self.message.audio = 'test'
self.assertTrue(Filters.audio(self.update)) self.assertTrue(Filters.audio(self.message))
self.message.audio = None self.message.audio = None
self.assertFalse(Filters.audio(self.update)) self.assertFalse(Filters.audio(self.message))
def test_filters_document(self): def test_filters_document(self):
self.message.document = 'test' self.message.document = 'test'
self.assertTrue(Filters.document(self.update)) self.assertTrue(Filters.document(self.message))
self.message.document = None self.message.document = None
self.assertFalse(Filters.document(self.update)) self.assertFalse(Filters.document(self.message))
def test_filters_photo(self): def test_filters_photo(self):
self.message.photo = 'test' self.message.photo = 'test'
self.assertTrue(Filters.photo(self.update)) self.assertTrue(Filters.photo(self.message))
self.message.photo = None self.message.photo = None
self.assertFalse(Filters.photo(self.update)) self.assertFalse(Filters.photo(self.message))
def test_filters_sticker(self): def test_filters_sticker(self):
self.message.sticker = 'test' self.message.sticker = 'test'
self.assertTrue(Filters.sticker(self.update)) self.assertTrue(Filters.sticker(self.message))
self.message.sticker = None self.message.sticker = None
self.assertFalse(Filters.sticker(self.update)) self.assertFalse(Filters.sticker(self.message))
def test_filters_video(self): def test_filters_video(self):
self.message.video = 'test' self.message.video = 'test'
self.assertTrue(Filters.video(self.update)) self.assertTrue(Filters.video(self.message))
self.message.video = None self.message.video = None
self.assertFalse(Filters.video(self.update)) self.assertFalse(Filters.video(self.message))
def test_filters_voice(self): def test_filters_voice(self):
self.message.voice = 'test' self.message.voice = 'test'
self.assertTrue(Filters.voice(self.update)) self.assertTrue(Filters.voice(self.message))
self.message.voice = None self.message.voice = None
self.assertFalse(Filters.voice(self.update)) self.assertFalse(Filters.voice(self.message))
def test_filters_contact(self): def test_filters_contact(self):
self.message.contact = 'test' self.message.contact = 'test'
self.assertTrue(Filters.contact(self.update)) self.assertTrue(Filters.contact(self.message))
self.message.contact = None self.message.contact = None
self.assertFalse(Filters.contact(self.update)) self.assertFalse(Filters.contact(self.message))
def test_filters_location(self): def test_filters_location(self):
self.message.location = 'test' self.message.location = 'test'
self.assertTrue(Filters.location(self.update)) self.assertTrue(Filters.location(self.message))
self.message.location = None self.message.location = None
self.assertFalse(Filters.location(self.update)) self.assertFalse(Filters.location(self.message))
def test_filters_venue(self): def test_filters_venue(self):
self.message.venue = 'test' self.message.venue = 'test'
self.assertTrue(Filters.venue(self.update)) self.assertTrue(Filters.venue(self.message))
self.message.venue = None self.message.venue = None
self.assertFalse(Filters.venue(self.update)) self.assertFalse(Filters.venue(self.message))
def test_filters_status_update(self): def test_filters_status_update(self):
self.assertFalse(Filters.status_update(self.update)) self.assertFalse(Filters.status_update(self.message))
self.message.new_chat_member = 'test' self.message.new_chat_member = 'test'
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_member = None self.message.new_chat_member = None
self.message.left_chat_member = 'test' self.message.left_chat_member = 'test'
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.left_chat_member = None self.message.left_chat_member = None
self.message.new_chat_title = 'test' self.message.new_chat_title = 'test'
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_title = '' self.message.new_chat_title = ''
self.message.new_chat_photo = 'test' self.message.new_chat_photo = 'test'
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.new_chat_photo = None self.message.new_chat_photo = None
self.message.delete_chat_photo = True self.message.delete_chat_photo = True
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.delete_chat_photo = False self.message.delete_chat_photo = False
self.message.group_chat_created = True self.message.group_chat_created = True
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.group_chat_created = False self.message.group_chat_created = False
self.message.supergroup_chat_created = True self.message.supergroup_chat_created = True
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.supergroup_chat_created = False self.message.supergroup_chat_created = False
self.message.migrate_to_chat_id = 100 self.message.migrate_to_chat_id = 100
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.migrate_to_chat_id = 0 self.message.migrate_to_chat_id = 0
self.message.migrate_from_chat_id = 100 self.message.migrate_from_chat_id = 100
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.migrate_from_chat_id = 0 self.message.migrate_from_chat_id = 0
self.message.channel_chat_created = True self.message.channel_chat_created = True
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.channel_chat_created = False self.message.channel_chat_created = False
self.message.pinned_message = 'test' self.message.pinned_message = 'test'
self.assertTrue(Filters.status_update(self.update)) self.assertTrue(Filters.status_update(self.message))
self.message.pinned_message = None self.message.pinned_message = None

View file

@ -93,6 +93,10 @@ class UpdaterTest(BaseTest, unittest.TestCase):
self.received_message = update.message.text self.received_message = update.message.text
self.message_count += 1 self.message_count += 1
def telegramHandlerEditedTest(self, bot, update):
self.received_message = update.edited_message.text
self.message_count += 1
def telegramInlineHandlerTest(self, bot, update): def telegramInlineHandlerTest(self, bot, update):
self.received_message = (update.inline_query, update.chosen_inline_result) self.received_message = (update.inline_query, update.chosen_inline_result)
self.message_count += 1 self.message_count += 1
@ -157,6 +161,28 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_editedMessageHandler(self):
self._setup_updater('Test', edited=True)
d = self.updater.dispatcher
from telegram.ext import Filters
handler = MessageHandler([Filters.text], self.telegramHandlerEditedTest, allow_edited=True)
d.addHandler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, 'Test')
# Remove handler
d.removeHandler(handler)
handler = MessageHandler([Filters.text],
self.telegramHandlerEditedTest,
allow_edited=False)
d.addHandler(handler)
self.reset()
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addTelegramMessageHandlerMultipleMessages(self): def test_addTelegramMessageHandlerMultipleMessages(self):
self._setup_updater('Multiple', 100) self._setup_updater('Multiple', 100)
self.updater.dispatcher.addHandler(MessageHandler([], self.telegramHandlerTest)) self.updater.dispatcher.addHandler(MessageHandler([], self.telegramHandlerTest))
@ -200,6 +226,25 @@ class UpdaterTest(BaseTest, unittest.TestCase):
sleep(.1) sleep(.1)
self.assertTrue(None is self.received_message) self.assertTrue(None is self.received_message)
def test_editedCommandHandler(self):
self._setup_updater('/test', edited=True)
d = self.updater.dispatcher
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=True)
d.addHandler(handler)
self.updater.start_polling(0.01)
sleep(.1)
self.assertEqual(self.received_message, '/test')
# Remove handler
d.removeHandler(handler)
handler = CommandHandler('test', self.telegramHandlerEditedTest, allow_edited=False)
d.addHandler(handler)
self.reset()
self.updater.bot.send_messages = 1
sleep(.1)
self.assertTrue(None is self.received_message)
def test_addRemoveStringRegexHandler(self): def test_addRemoveStringRegexHandler(self):
self._setup_updater('', messages=0) self._setup_updater('', messages=0)
d = self.updater.dispatcher d = self.updater.dispatcher
@ -612,7 +657,8 @@ class MockBot(object):
messages=1, messages=1,
raise_error=False, raise_error=False,
bootstrap_retries=None, bootstrap_retries=None,
bootstrap_err=TelegramError('test')): bootstrap_err=TelegramError('test'),
edited=False):
self.text = text self.text = text
self.send_messages = messages self.send_messages = messages
self.raise_error = raise_error self.raise_error = raise_error
@ -620,13 +666,18 @@ class MockBot(object):
self.bootstrap_retries = bootstrap_retries self.bootstrap_retries = bootstrap_retries
self.bootstrap_attempts = 0 self.bootstrap_attempts = 0
self.bootstrap_err = bootstrap_err self.bootstrap_err = bootstrap_err
self.edited = edited
@staticmethod def mockUpdate(self, text):
def mockUpdate(text):
message = Message(0, None, None, None) message = Message(0, None, None, None)
message.text = text message.text = text
update = Update(0) update = Update(0)
if self.edited:
update.edited_message = message
else:
update.message = message update.message = message
return update return update
def setWebhook(self, webhook_url=None, certificate=None): def setWebhook(self, webhook_url=None, certificate=None):