Bot api 3.1 (#698) + minor improvements

- Added all the new and shiny features from API 3.1.
 - Not API 3.1 changes:
   - Use future.utils.string_types for string isinstance checks.
   - Stall between retries of test_set_webhook_get_webhook_info() &
     test_delete_webhook().
This commit is contained in:
Jacob Bom 2017-07-01 17:08:45 +02:00 committed by Noam Meltzer
parent cbafdc289f
commit 94ed4cb38d
11 changed files with 596 additions and 73 deletions

View file

@ -2,6 +2,13 @@
Changes
=======
**DATE TBD**
*WIP 6.2.0*
- Improved filters for user_id/username/chat.
- Internal restructure of files.
- Improved unitests.
- Fully support Bot API 3.1.
**2017-06-18**
*Released 6.1.0*

View file

@ -20,6 +20,7 @@
from .base import TelegramObject
from .user import User
from .files.chatphoto import ChatPhoto
from .chat import Chat
from .chatmember import ChatMember
from .files.photosize import PhotoSize
@ -119,5 +120,5 @@ __all__ = [
'MAX_FILESIZE_DOWNLOAD', 'MAX_FILESIZE_UPLOAD', 'MAX_MESSAGES_PER_SECOND_PER_CHAT',
'MAX_MESSAGES_PER_SECOND', 'MAX_MESSAGES_PER_MINUTE_PER_GROUP', 'WebhookInfo', 'Animation',
'Game', 'GameHighScore', 'VideoNote', 'LabeledPrice', 'SuccessfulPayment', 'ShippingOption',
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery'
'ShippingAddress', 'PreCheckoutQuery', 'OrderInfo', 'Invoice', 'ShippingQuery', 'ChatPhoto'
]

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pylint: disable=E0611,E0213,E1102,C0103,E1101,W0613,R0913,R0904
#
# A library that provides a Python interface to the Telegram Bot API
@ -22,10 +23,12 @@
import functools
import logging
import warnings
from datetime import datetime
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore)
from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request
logging.getLogger(__name__).addHandler(logging.NullHandler())
@ -1039,12 +1042,12 @@ class Bot(TelegramObject):
return File.de_json(result, self)
@log
def kick_chat_member(self, chat_id, user_id, timeout=None, **kwargs):
"""Use this method to kick a user from a group or a supergroup.
def kick_chat_member(self, chat_id, user_id, timeout=None, until_date=None, **kwargs):
"""Use this method to kick a user from a group, a supergroup or a channel.
In the case of supergroups, the user will not be able to return to the group on their own
using invite links, etc., unless unbanned first. The bot must be an administrator in the
group for this to work.
In the case of supergroups and channels, the user will not be able to return to the group
on their own using invite links, etc., unless unbanned first. The bot must be an
administrator in the chat for this to work and must have the appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target group or username of the target
@ -1053,8 +1056,16 @@ class Bot(TelegramObject):
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
until_date (Optional[int|datetime]): Date when the user will be unbanned,
unix time. If user is banned for more than 366 days or less than 30 seconds from
the current time they are considered to be banned forever
**kwargs (dict): Arbitrary keyword arguments.
Note:
In regular groups (non-supergroups), this method will only work if the
'All Members Are Admins' setting is off in the target group. Otherwise
members may only be removed by the group's creator or by the member that added them.
Returns:
bool: On success, `True` is returned.
@ -1066,6 +1077,11 @@ class Bot(TelegramObject):
data = {'chat_id': chat_id, 'user_id': user_id}
if until_date is not None:
if isinstance(until_date, datetime):
until_date = to_timestamp(until_date)
data['until_date'] = until_date
result = self._request.post(url, data, timeout=timeout)
return result
@ -1993,6 +2009,366 @@ class Bot(TelegramObject):
return result
@log
def restrict_chat_member(self, chat_id, user_id, until_date=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None, timeout=None, **kwargs):
"""Use this method to restrict a user in a supergroup.
The bot must be an administrator in the supergroup for this to work and must have the
appropriate admin rights. Pass True for all boolean parameters to lift restrictions
from a user.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
supergroup (in the format @supergroupusername)
user_id (int): Unique identifier of the target user
until_date (Optional[int|datetime]): Date when restrictions will be lifted for the
user, unix time. If user is restricted for more than 366 days or less than 30
seconds from the current time, they are considered to be restricted forever
can_send_messages (Optional[boolean]): Pass True, if the user can send text messages,
contacts, locations and venues
can_send_media_messages (Optional[boolean]): Pass True, if the user can send audios,
documents, photos, videos, video notes and voice notes, implies can_send_messages
can_send_other_messages (Optional[boolean]): Pass True, if the user can send
animations, games, stickers and use inline bots, implies can_send_media_messages
can_add_web_page_previews (Optional[boolean]): Pass True, if the user may add web page
previews to their messages, implies can_send_media_messages
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/restrictChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
if until_date is not None:
if isinstance(until_date, datetime):
until_date = to_timestamp(until_date)
data['until_date'] = until_date
if can_send_messages is not None:
data['can_send_messages'] = can_send_messages
if can_send_media_messages is not None:
data['can_send_media_messages'] = can_send_media_messages
if can_send_other_messages is not None:
data['can_send_other_messages'] = can_send_other_messages
if can_add_web_page_previews is not None:
data['can_add_web_page_previews'] = can_add_web_page_previews
result = self._request.post(url, data, timeout=timeout)
return result
@log
def promote_chat_member(self, chat_id, user_id, can_change_info=None,
can_post_messages=None, can_edit_messages=None,
can_delete_messages=None, can_invite_users=None,
can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, timeout=None, **kwargs):
"""Use this method to promote or demote a user in a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights. Pass False for all boolean parameters to demote a user
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
supergroup (in the format @supergroupusername)
user_id (int): Unique identifier of the target user
can_change_info (Optional[boolean]): Pass True, if the administrator can change chat
title, photo and other settings
can_post_messages (Optional[boolean]): Pass True, if the administrator can create
channel posts, channels only
can_edit_messages (Optional[boolean]): Pass True, if the administrator can edit
messages of other users, channels only
can_delete_messages (Optional[boolean]): Pass True, if the administrator can delete
messages of other users
can_invite_users (Optional[boolean]): Pass True, if the administrator can invite new
users to the chat
can_restrict_members (Optional[boolean]): Pass True, if the administrator can restrict,
ban or unban chat members
can_pin_messages (Optional[boolean]): Pass True, if the administrator can pin messages,
supergroups only
can_promote_members (Optional[boolean]): Pass True, if the administrator can add new
administrators with a subset of his own privileges or demote administrators that
he has promoted, directly or indirectly (promoted by administrators that were
appointed by him)
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/promoteChatMember'.format(self.base_url)
data = {'chat_id': chat_id, 'user_id': user_id}
if can_change_info is not None:
data['can_change_info'] = can_change_info
if can_post_messages is not None:
data['can_post_messages'] = can_post_messages
if can_edit_messages is not None:
data['can_edit_messages'] = can_edit_messages
if can_delete_messages is not None:
data['can_delete_messages'] = can_delete_messages
if can_invite_users is not None:
data['can_invite_users'] = can_invite_users
if can_restrict_members is not None:
data['can_restrict_members'] = can_restrict_members
if can_pin_messages is not None:
data['can_pin_messages'] = can_pin_messages
if can_promote_members is not None:
data['can_promote_members'] = can_promote_members
result = self._request.post(url, data, timeout=timeout)
return result
@log
def export_chat_invite_link(self, chat_id, timeout=None, **kwargs):
"""Use this method to export an invite link to a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
str: On success the exported invite link is returned
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/exportChatInviteLink'.format(self.base_url)
data = {'chat_id': chat_id}
result = self._request.post(url, data, timeout=timeout)
return result
@log
def set_chat_photo(self, chat_id, photo, timeout=None, **kwargs):
"""Use this method to set a new profile photo for the chat.
Photos can't be changed for private chats. The bot must be an administrator in the chat
for this to work and must have the appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
photo (`telegram.InputFile`): New chat photo
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Note:
In regular groups (non-supergroups), this method will only work if the
'All Members Are Admins' setting is off in the target group.
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/setChatPhoto'.format(self.base_url)
data = {'chat_id': chat_id, 'photo': photo}
result = self._request.post(url, data, timeout=timeout)
return result
@log
def delete_chat_photo(self, chat_id, timeout=None, **kwargs):
"""Use this method to delete a chat photo.
Photos can't be changed for private chats. The bot must be an administrator in the chat
for this to work and must have the appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Note:
In regular groups (non-supergroups), this method will only work if the
'All Members Are Admins' setting is off in the target group.
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/deleteChatPhoto'.format(self.base_url)
data = {'chat_id': chat_id}
result = self._request.post(url, data, timeout=timeout)
return result
@log
def set_chat_title(self, chat_id, title, timeout=None, **kwargs):
"""Use this method to change the title of a chat.
Titles can't be changed for private chats. The bot must be an administrator in the chat
for this to work and must have the appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
title (str): New chat title, 1-255 characters
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Note:
In regular groups (non-supergroups), this method will only work if the
'All Members Are Admins' setting is off in the target group.
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/setChatTitle'.format(self.base_url)
data = {'chat_id': chat_id, 'title': title}
result = self._request.post(url, data, timeout=timeout)
return result
@log
def set_chat_description(self, chat_id, description, timeout=None, **kwargs):
"""Use this method to change the description of a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
description (str): New chat description, 1-255 characters
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/setChatDescription'.format(self.base_url)
data = {'chat_id': chat_id, 'description': description}
result = self._request.post(url, data, timeout=timeout)
return result
@log
def pin_chat_message(self, chat_id, message_id, disable_notification=None, timeout=None,
**kwargs):
"""Use this method to pin a message in a supergroup.
The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
message_id (int): Identifier of a message to pin
disable_notification (boolean): Pass True, if it is not necessary to send a
notification to all group members about the new pinned message
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/pinChatMessage'.format(self.base_url)
data = {'chat_id': chat_id, 'message_id': message_id}
if disable_notification is not None:
data['disable_notification'] = disable_notification
result = self._request.post(url, data, timeout=timeout)
return result
@log
def unpin_chat_message(self, chat_id, timeout=None, **kwargs):
"""Use this method to unpin a message in a supergroup.
The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights.
Args:
chat_id (int|str): Unique identifier for the target chat or username of the target
channel (in the format @channelusername)
timeout (Optional[int|float]): If this value is specified, use it as the read timeout
from the server (instead of the one specified during creation of the connection
pool).
**kwargs (dict): Arbitrary keyword arguments
Returns:
bool: On success, `True` is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/unpinChatMessage'.format(self.base_url)
data = {'chat_id': chat_id}
result = self._request.post(url, data, timeout=timeout)
return result
@staticmethod
def de_json(data, bot):
data = super(Bot, Bot).de_json(data, bot)
@ -2051,3 +2427,12 @@ class Bot(TelegramObject):
sendInvoice = send_invoice
answerShippingQuery = answer_shipping_query
answerPreCheckoutQuery = answer_pre_checkout_query
restrictChatMember = restrict_chat_member
promoteChatMember = promote_chat_member
exportChatInviteLink = export_chat_invite_link
setChatPhoto = set_chat_photo
deleteChatPhoto = delete_chat_photo
setChatTitle = set_chat_title
setChatDescription = set_chat_description
pinChatMessage = pin_chat_message
unpinChatMessage = unpin_chat_message

View file

@ -19,7 +19,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Chat."""
from telegram import TelegramObject
from telegram import TelegramObject, ChatPhoto
class Chat(TelegramObject):
@ -33,14 +33,13 @@ class Chat(TelegramObject):
first_name (str): First name of the other party in a private chat
last_name (str): Last name of the other party in a private chat
all_members_are_administrators (bool): True if group has 'All Members Are Administrators'
photo (Optional[`telegram.ChatPhoto`]): Chat photo. Returned only in getChat.
description (str): Description, for supergroups and channel chats. Returned
only in getChat.
invite_link (str): Chat invite link, for supergroups and channel chats. Returned
only in getChat.
Args:
id (int):
type (str):
title (Optional[str]):
username(Optional[str]):
first_name(Optional[str]):
last_name(Optional[str]):
bot (Optional[telegram.Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
@ -59,6 +58,9 @@ class Chat(TelegramObject):
last_name=None,
all_members_are_administrators=None,
bot=None,
photo=None,
description=None,
invite_link=None,
**kwargs):
# Required
self.id = int(id)
@ -69,6 +71,9 @@ class Chat(TelegramObject):
self.first_name = first_name
self.last_name = last_name
self.all_members_are_administrators = all_members_are_administrators
self.photo = photo
self.description = description
self.invite_link = invite_link
self.bot = bot
self._id_attrs = (self.id,)
@ -86,6 +91,8 @@ class Chat(TelegramObject):
if not data:
return None
data['photo'] = ChatPhoto.de_json(data.get('photo'), bot)
return Chat(bot=bot, **data)
def send_action(self, *args, **kwargs):

View file

@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram ChatMember."""
from telegram import User, TelegramObject
from telegram.utils.helpers import to_timestamp, from_timestamp
class ChatMember(TelegramObject):
@ -28,10 +29,39 @@ class ChatMember(TelegramObject):
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'.
until_date (Optional[:class:`datetime.datetime`]): Restricted and kicked only. Date when
restrictions will be lifted for this user.
can_be_edited (Optional[boolean]): Administrators only. True, if the bot is allowed to
edit administrator privileges of that user
can_change_info (Optional[boolean]): Administrators only. True, if the administrator can
change the chat title, photo and other settings
can_post_messages (Optional[boolean]): Administrators only. True, if the administrator can
post in the channel, channels only
can_edit_messages (Optional[boolean]): Administrators only. True, if the administrator can
edit messages of other users, channels only
can_delete_messages (Optional[boolean]): Administrators only. True, if the administrator
can delete messages of other user
can_invite_users (Optional[boolean]): Administrators only. True, if the administrator can
invite new users to the chat
can_restrict_members (Optional[boolean]): Administrators only. True, if the administrator
can restrict, ban or unban chat members
can_pin_messages (Optional[boolean]): Administrators only. True, if the administrator can
pin messages, supergroups only
can_promote_members (Optional[boolean]): Administrators only. True, if the administrator
can add new administrators with a subset of his own privileges or demote administrators
that he has promoted, directly or indirectly (promoted by administrators that were
appointed by the user)
can_send_messages (Optional[boolean]): Restricted only. True, if the user can send text
messages, contacts, locations and venues
can_send_media_messages (Optional[boolean]): Restricted only. True, if the user can send
audios, documents, photos, videos, video notes and voice notes,
implies can_send_messages
can_send_other_messages (Optional[boolean]): Restricted only. True, if the user can send
animations, games, stickers and use inline bots, implies can_send_media_messages
can_add_web_page_previews (Optional[boolean]): Restricted only. True, if user may add
web page previews to his messages, implies can_send_media_messages
Args:
user (:class:`telegram.User`):
status (str):
**kwargs (dict): Arbitrary keyword arguments.
"""
@ -41,10 +71,30 @@ class ChatMember(TelegramObject):
LEFT = 'left'
KICKED = 'kicked'
def __init__(self, user, status, **kwargs):
def __init__(self, user, status, until_date=None, can_be_edited=None,
can_change_info=None, can_post_messages=None, can_edit_messages=None,
can_delete_messages=None, can_invite_users=None,
can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None, **kwargs):
# Required
self.user = user
self.status = status
self.until_date = until_date
self.can_be_edited = can_be_edited
self.can_change_info = can_change_info
self.can_post_messages = can_post_messages
self.can_edit_messages = can_edit_messages
self.can_delete_messages = can_delete_messages
self.can_invite_users = can_invite_users
self.can_restrict_members = can_restrict_members
self.can_pin_messages = can_pin_messages
self.can_promote_members = can_promote_members
self.can_send_messages = can_send_messages
self.can_send_media_messages = can_send_media_messages
self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews
self._id_attrs = (self.user, self.status)
@ -64,5 +114,17 @@ class ChatMember(TelegramObject):
data = super(ChatMember, ChatMember).de_json(data, bot)
data['user'] = User.de_json(data.get('user'), bot)
data['until_date'] = from_timestamp(data.get('until_date', None))
return ChatMember(**data)
def to_dict(self):
"""
Returns:
dict:
"""
data = super(ChatMember, self).to_dict()
data['until_date'] = to_timestamp(self.until_date)
return data

View file

@ -19,6 +19,8 @@
""" This module contains the CommandHandler class """
import warnings
from future.utils import string_types
from .handler import Handler
from telegram import Update
@ -80,12 +82,8 @@ class CommandHandler(Handler):
pass_job_queue=pass_job_queue,
pass_user_data=pass_user_data,
pass_chat_data=pass_chat_data)
try:
_str = basestring # Python 2
except NameError:
_str = str # Python 3
if isinstance(command, _str):
if isinstance(command, string_types):
self.command = [command.lower()]
else:
self.command = [x.lower() for x in command]

View file

@ -18,11 +18,7 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
""" This module contains the Filters for use with the MessageHandler class """
from telegram import Chat
try:
str_type = base_string
except NameError:
str_type = str
from future.utils import string_types
class BaseFilter(object):
@ -383,7 +379,7 @@ class Filters(object):
self.user_ids = user_id
if username is None:
self.usernames = username
elif isinstance(username, str_type):
elif isinstance(username, string_types):
self.usernames = [username.replace('@', '')]
else:
self.usernames = [user.replace('@', '') for user in username]
@ -420,7 +416,7 @@ class Filters(object):
self.chat_ids = chat_id
if username is None:
self.usernames = username
elif isinstance(username, str_type):
elif isinstance(username, string_types):
self.usernames = [username.replace('@', '')]
else:
self.usernames = [chat.replace('@', '') for chat in username]
@ -460,7 +456,7 @@ class Filters(object):
"""
def __init__(self, lang):
if isinstance(lang, str_type):
if isinstance(lang, string_types):
self.lang = [lang]
else:
self.lang = lang

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2017
# 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 an object that represents a Telegram ChatPhoto."""
from telegram import TelegramObject
class ChatPhoto(TelegramObject):
""" This object represents a Telegram ChatPhoto
Attributes:
small_file_id (str): Unique file identifier of small (160x160) chat photo. This file_id
can be used only for photo download.
big_file_id (str): Unique file identifier of big (640x640) chat photo. This file_id
can be used only for photo download.
Args:
bot (Optional[telegram.Bot]): The Bot to use for instance methods
**kwargs (dict): Arbitrary keyword arguments.
"""
def __init__(self, small_file_id, big_file_id, bot=None, **kwargs):
self.small_file_id = small_file_id
self.big_file_id = big_file_id
@staticmethod
def de_json(data, bot):
"""
Args:
data (dict):
bot (telegram.Bot):
Returns:
telegram.ChatPhoto:
"""
if not data:
return None
return ChatPhoto(bot=bot, **data)

View file

@ -19,14 +19,12 @@
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represents a Telegram Message."""
import sys
from datetime import datetime
from time import mktime
from telegram import (Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, TelegramObject,
User, Video, Voice, Venue, MessageEntity, Game, Invoice, SuccessfulPayment,
VideoNote)
from telegram.utils.deprecate import warn_deprecate_obj
from telegram.utils.helpers import escape_html, escape_markdown
from telegram.utils.helpers import escape_html, escape_markdown, to_timestamp, from_timestamp
class Message(TelegramObject):
@ -215,14 +213,14 @@ class Message(TelegramObject):
data = super(Message, Message).de_json(data, bot)
data['from_user'] = User.de_json(data.get('from'), bot)
data['date'] = Message._fromtimestamp(data['date'])
data['date'] = from_timestamp(data['date'])
data['chat'] = Chat.de_json(data.get('chat'), bot)
data['entities'] = MessageEntity.de_list(data.get('entities'), bot)
data['forward_from'] = User.de_json(data.get('forward_from'), bot)
data['forward_from_chat'] = Chat.de_json(data.get('forward_from_chat'), bot)
data['forward_date'] = Message._fromtimestamp(data.get('forward_date'))
data['forward_date'] = from_timestamp(data.get('forward_date'))
data['reply_to_message'] = Message.de_json(data.get('reply_to_message'), bot)
data['edit_date'] = Message._fromtimestamp(data.get('edit_date'))
data['edit_date'] = from_timestamp(data.get('edit_date'))
data['audio'] = Audio.de_json(data.get('audio'), bot)
data['document'] = Document.de_json(data.get('document'), bot)
data['game'] = Game.de_json(data.get('game'), bot)
@ -259,12 +257,12 @@ class Message(TelegramObject):
# Required
data['from'] = data.pop('from_user', None)
data['date'] = self._totimestamp(self.date)
data['date'] = to_timestamp(self.date)
# Optionals
if self.forward_date:
data['forward_date'] = self._totimestamp(self.forward_date)
data['forward_date'] = to_timestamp(self.forward_date)
if self.edit_date:
data['edit_date'] = self._totimestamp(self.edit_date)
data['edit_date'] = to_timestamp(self.edit_date)
if self.photo:
data['photo'] = [p.to_dict() for p in self.photo]
if self.entities:
@ -277,39 +275,6 @@ class Message(TelegramObject):
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):
"""
Args:
dt_obj (:class:`datetime.datetime`):
Returns:
int:
"""
if not dt_obj:
return None
try:
# Python 3.3+
return int(dt_obj.timestamp())
except AttributeError:
# Python 3 (< 3.3) and Python 2
return int(mktime(dt_obj.timetuple()))
def _quote(self, kwargs):
"""Modify kwargs for replying with or without quoting"""

View file

@ -19,14 +19,56 @@
""" This module contains helper functions """
import re
from datetime import datetime
try:
from html import escape as escape_html # noqa: F401
except ImportError:
from cgi import escape as escape_html # noqa: F401
# Not using future.backports.datetime here as datetime value might be an input from the user,
# making every isinstace() call more delicate. So we just use our own compat layer.
if hasattr(datetime, 'timestamp'):
# Python 3.3+
def _timestamp(dt_obj):
return dt_obj.timestamp()
else:
# Python < 3.3 (incl 2.7)
from time import mktime
def _timestamp(dt_obj):
return mktime(dt_obj.timetuple())
def escape_markdown(text):
"""Helper function to escape telegram markup symbols"""
escape_chars = '\*_`\['
return re.sub(r'([%s])' % escape_chars, r'\\\1', text)
def to_timestamp(dt_obj):
"""
Args:
dt_obj (:class:`datetime.datetime`):
Returns:
int:
"""
if not dt_obj:
return None
return int(_timestamp(dt_obj))
def from_timestamp(unixtime):
"""
Args:
unixtime (int):
Returns:
datetime.datetime:
"""
if not unixtime:
return None
return datetime.fromtimestamp(unixtime)

View file

@ -39,6 +39,11 @@ BASE_TIME = time.time()
HIGHSCORE_DELTA = 1450000000
def _stall_retry(*_args, **_kwargs):
time.sleep(3)
return True
class BotTest(BaseTest, unittest.TestCase):
"""This object represents Tests for Telegram Bot."""
@ -246,7 +251,7 @@ class BotTest(BaseTest, unittest.TestCase):
self.assertEqual(text, fwdmsg.text)
self.assertEqual(fwdmsg.forward_from_message_id, msg.message_id)
@flaky(20, 1)
@flaky(20, 1, _stall_retry)
@timeout(10)
def test_set_webhook_get_webhook_info(self):
url = 'https://python-telegram-bot.org/test/webhook'
@ -259,7 +264,7 @@ class BotTest(BaseTest, unittest.TestCase):
self.assertEqual(max_connections, info.max_connections)
self.assertListEqual(allowed_updates, info.allowed_updates)
@flaky(3, 1)
@flaky(20, 1, _stall_retry)
@timeout(10)
def test_delete_webhook(self):
url = 'https://python-telegram-bot.org/test/webhook'