All api 4.2 and 4.3 changes (#1418)

This commit is contained in:
Eldinnie 2019-08-23 21:20:41 +02:00 committed by Noam Meltzer
parent e492d5b97b
commit ac60d057a5
25 changed files with 702 additions and 60 deletions

View file

@ -1,20 +1,20 @@
repos: repos:
- repo: git://github.com/python-telegram-bot/mirrors-yapf - repo: git://github.com/python-telegram-bot/mirrors-yapf
rev: master sha: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
hooks: hooks:
- id: yapf - id: yapf
files: ^(telegram|tests)/.*\.py$ files: ^(telegram|tests)/.*\.py$
args: args:
- --diff - --diff
- repo: git://github.com/pre-commit/pre-commit-hooks - repo: git://github.com/pre-commit/pre-commit-hooks
rev: v2.0.0 sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
hooks: hooks:
- id: flake8 - id: flake8
exclude: ^(setup.py|docs/source/conf.py)$ exclude: ^(setup.py|docs/source/conf.py)$
args: args:
- --ignore=W605,W503 - --ignore=W605,W503
- repo: git://github.com/pre-commit/mirrors-pylint - repo: git://github.com/pre-commit/mirrors-pylint
rev: v2.3.0 sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
hooks: hooks:
- id: pylint - id: pylint
files: ^telegram/.*\.py$ files: ^telegram/.*\.py$

View file

@ -7,7 +7,6 @@ environment:
# isn't covered by this document) at the time of writing. # isn't covered by this document) at the time of writing.
- PYTHON: "C:\\Python27" - PYTHON: "C:\\Python27"
- PYTHON: "C:\\Python34"
- PYTHON: "C:\\Python35" - PYTHON: "C:\\Python35"
- PYTHON: "C:\\Python36" - PYTHON: "C:\\Python36"
- PYTHON: "C:\\Python37" - PYTHON: "C:\\Python37"

View file

@ -0,0 +1,6 @@
telegram.Poll
=============
.. autoclass:: telegram.Poll
:members:
:show-inheritance:

View file

@ -0,0 +1,6 @@
telegram.PollOption
===================
.. autoclass:: telegram.PollOption
:members:
:show-inheritance:

View file

@ -35,6 +35,8 @@ telegram package
telegram.messageentity telegram.messageentity
telegram.parsemode telegram.parsemode
telegram.photosize telegram.photosize
telegram.poll
telegram.polloption
telegram.replykeyboardremove telegram.replykeyboardremove
telegram.replykeyboardmarkup telegram.replykeyboardmarkup
telegram.replymarkup telegram.replymarkup

View file

@ -47,6 +47,8 @@ from .files.file import File
from .parsemode import ParseMode from .parsemode import ParseMode
from .messageentity import MessageEntity from .messageentity import MessageEntity
from .games.game import Game from .games.game import Game
from .poll import Poll, PollOption
from .loginurl import LoginUrl
from .games.callbackgame import CallbackGame from .games.callbackgame import CallbackGame
from .payment.shippingaddress import ShippingAddress from .payment.shippingaddress import ShippingAddress
from .payment.orderinfo import OrderInfo from .payment.orderinfo import OrderInfo
@ -57,11 +59,11 @@ from .passport.passportfile import PassportFile
from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
from .passport.encryptedpassportelement import EncryptedPassportElement from .passport.encryptedpassportelement import EncryptedPassportElement
from .passport.passportdata import PassportData from .passport.passportdata import PassportData
from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .message import Message from .message import Message
from .callbackquery import CallbackQuery from .callbackquery import CallbackQuery
from .choseninlineresult import ChosenInlineResult from .choseninlineresult import ChosenInlineResult
from .inline.inlinekeyboardbutton import InlineKeyboardButton
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from .inline.inputmessagecontent import InputMessageContent from .inline.inputmessagecontent import InputMessageContent
from .inline.inlinequery import InlineQuery from .inline.inlinequery import InlineQuery
from .inline.inlinequeryresult import InlineQueryResult from .inline.inlinequeryresult import InlineQueryResult
@ -152,5 +154,6 @@ __all__ = [
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation', 'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError', 'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile', 'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified' 'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
'PollOption', 'LoginUrl'
] ]

View file

@ -37,7 +37,7 @@ from future.utils import string_types
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File, from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet, ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote, PhotoSize, Audio, Document, Sticker, Video, Animation, Voice, VideoNote,
Location, Venue, Contact, InputFile) Location, Venue, Contact, InputFile, Poll)
from telegram.error import InvalidToken, TelegramError from telegram.error import InvalidToken, TelegramError
from telegram.utils.helpers import to_timestamp from telegram.utils.helpers import to_timestamp
from telegram.utils.request import Request from telegram.utils.request import Request
@ -260,13 +260,16 @@ class Bot(TelegramObject):
@log @log
def delete_message(self, chat_id, message_id, timeout=None, **kwargs): def delete_message(self, chat_id, message_id, timeout=None, **kwargs):
""" """
Use this method to delete a message. A message can only be deleted if it was sent less Use this method to delete a message, including service messages, with the following
than 48 hours ago. Any such recently sent outgoing message may be deleted. Additionally, limitations:
if the bot is an administrator in a group chat, it can delete any message. If the bot is
an administrator in a supergroup, it can delete messages from any other user and service - A message can only be deleted if it was sent less than 48 hours ago.
messages about people joining or leaving the group (other types of service messages may - Bots can delete outgoing messages in private chats, groups, and supergroups.
only be removed by the group creator). In channels, bots can only remove their own - Bots can delete incoming messages in private chats.
messages. - Bots granted can_post_messages permissions can delete outgoing messages in channels.
- If the bot is an administrator of a group, it can delete any message there.
- If the bot has can_delete_messages permission in a supergroup or a channel, it can
delete any message there.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
@ -3319,6 +3322,101 @@ class Bot(TelegramObject):
return result return result
@log
def send_poll(self,
chat_id,
question,
options,
disable_notification=None,
reply_to_message_id=None,
reply_markup=None,
timeout=None,
**kwargs):
"""
Use this method to send a native poll. A native poll can't be sent to a private chat.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target private chat.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:obj:`str`]): List of answer options, 2-10 strings 1-100 characters each.
disable_notification (:obj:`bool`, optional): Sends the message silently. Users will
receive a notification with no sound.
reply_to_message_id (:obj:`int`, optional): If the message is a reply, ID of the
original message.
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user.
timeout (:obj:`int` | :obj:`float`, optional): 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 (:obj:`dict`): Arbitrary keyword arguments.
Returns:
:class:`telegram.Message`: On success, the sent Message is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/sendPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
'question': question,
'options': options
}
return self._message(url, data, timeout=timeout, disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id, reply_markup=reply_markup,
**kwargs)
@log
def stop_poll(self,
chat_id,
message_id,
reply_markup=None,
timeout=None,
**kwargs):
"""
Use this method to stop a poll which was sent by the bot.
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format @channelusername).
message_id (:obj:`int`): Identifier of the original message with the poll.
reply_markup (:class:`telegram.ReplyMarkup`, optional): Additional interface options. A
JSON-serialized object for an inline keyboard, custom reply keyboard, instructions
to remove reply keyboard or to force a reply from the user.
timeout (:obj:`int` | :obj:`float`, optional): 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 (:obj:`dict`): Arbitrary keyword arguments.
Returns:
:class:`telegram.Poll`: On success, the stopped Poll with the
final results is returned.
Raises:
:class:`telegram.TelegramError`
"""
url = '{0}/stopPoll'.format(self.base_url)
data = {
'chat_id': chat_id,
'message_id': message_id
}
if reply_markup:
if isinstance(reply_markup, ReplyMarkup):
data['reply_markup'] = reply_markup.to_json()
else:
data['reply_markup'] = reply_markup
result = self._request.post(url, data, timeout=timeout)
return Poll.de_json(result, self)
def to_dict(self): def to_dict(self):
data = {'id': self.id, 'username': self.username, 'first_name': self.username} data = {'id': self.id, 'username': self.username, 'first_name': self.username}
@ -3456,3 +3554,7 @@ class Bot(TelegramObject):
"""Alias for :attr:`delete_sticker_from_set`""" """Alias for :attr:`delete_sticker_from_set`"""
setPassportDataErrors = set_passport_data_errors setPassportDataErrors = set_passport_data_errors
"""Alias for :attr:`set_passport_data_errors`""" """Alias for :attr:`set_passport_data_errors`"""
sendPoll = send_poll
"""Alias for :attr:`send_poll`"""
stopPoll = stop_poll
"""Alias for :attr:`stop_poll`"""

View file

@ -337,3 +337,16 @@ class Chat(TelegramObject):
""" """
return self.bot.send_voice(self.id, *args, **kwargs) return self.bot.send_voice(self.id, *args, **kwargs)
def send_poll(self, *args, **kwargs):
"""Shortcut for::
bot.send_poll(Chat.id, *args, **kwargs)
Where Chat is the current instance.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
return self.bot.send_poll(self.id, *args, **kwargs)

View file

@ -46,6 +46,8 @@ class ChatMember(TelegramObject):
can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages. can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages.
can_promote_members (:obj:`bool`): Optional. If the administrator can add new can_promote_members (:obj:`bool`): Optional. If the administrator can add new
administrators. administrators.
is_member (:obj:`bool`): Optional. Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts, can_send_messages (:obj:`bool`): Optional. If the user can send text messages, contacts,
locations and venues. locations and venues.
can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages, can_send_media_messages (:obj:`bool`): Optional. If the user can send media messages,
@ -81,6 +83,8 @@ class ChatMember(TelegramObject):
administrator can add new administrators with a subset of his own privileges or demote 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 administrators that he has promoted, directly or indirectly (promoted by administrators
that were appointed by the user). that were appointed by the user).
is_member (:obj:`bool`, optional): Restricted only. True, if the user is a member of the
chat at the moment of the request.
can_send_messages (:obj:`bool`, optional): Restricted only. True, if the user can send text can_send_messages (:obj:`bool`, optional): Restricted only. True, if the user can send text
messages, contacts, locations and venues. messages, contacts, locations and venues.
can_send_media_messages (:obj:`bool`, optional): Restricted only. True, if the user can can_send_media_messages (:obj:`bool`, optional): Restricted only. True, if the user can
@ -111,7 +115,7 @@ class ChatMember(TelegramObject):
can_restrict_members=None, can_pin_messages=None, can_restrict_members=None, can_pin_messages=None,
can_promote_members=None, can_send_messages=None, can_promote_members=None, can_send_messages=None,
can_send_media_messages=None, can_send_other_messages=None, can_send_media_messages=None, can_send_other_messages=None,
can_add_web_page_previews=None, **kwargs): can_add_web_page_previews=None, is_member=None, **kwargs):
# Required # Required
self.user = user self.user = user
self.status = status self.status = status
@ -129,6 +133,7 @@ class ChatMember(TelegramObject):
self.can_send_media_messages = can_send_media_messages self.can_send_media_messages = can_send_media_messages
self.can_send_other_messages = can_send_other_messages self.can_send_other_messages = can_send_other_messages
self.can_add_web_page_previews = can_add_web_page_previews self.can_add_web_page_previews = can_add_web_page_previews
self.is_member = is_member
self._id_attrs = (self.user, self.status) self._id_attrs = (self.user, self.status)

View file

@ -31,6 +31,8 @@ class InlineKeyboardButton(TelegramObject):
Attributes: Attributes:
text (:obj:`str`): Label text on the button. text (:obj:`str`): Label text on the button.
url (:obj:`str`): Optional. HTTP url to be opened when button is pressed. url (:obj:`str`): Optional. HTTP url to be opened when button is pressed.
login_url (:class:`telegram.LoginUrl`) Optional. An HTTP URL used to automatically
authorize the user.
callback_data (:obj:`str`): Optional. Data to be sent in a callback query to the bot when callback_data (:obj:`str`): Optional. Data to be sent in a callback query to the bot when
button is pressed, UTF-8 1-64 bytes. button is pressed, UTF-8 1-64 bytes.
switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their
@ -45,6 +47,8 @@ class InlineKeyboardButton(TelegramObject):
Args: Args:
text (:obj:`str`): Label text on the button. text (:obj:`str`): Label text on the button.
url (:obj:`str`): HTTP url to be opened when button is pressed. url (:obj:`str`): HTTP url to be opened when button is pressed.
login_url (:class:`telegram.LoginUrl`, optional) An HTTP URL used to automatically
authorize the user.
callback_data (:obj:`str`, optional): Data to be sent in a callback query to the bot when callback_data (:obj:`str`, optional): Data to be sent in a callback query to the bot when
button is pressed, 1-64 UTF-8 bytes. button is pressed, 1-64 UTF-8 bytes.
switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the switch_inline_query (:obj:`str`, optional): If set, pressing the button will prompt the
@ -76,14 +80,30 @@ class InlineKeyboardButton(TelegramObject):
switch_inline_query_current_chat=None, switch_inline_query_current_chat=None,
callback_game=None, callback_game=None,
pay=None, pay=None,
login_url=None,
**kwargs): **kwargs):
# Required # Required
self.text = text self.text = text
# Optionals # Optionals
self.url = url if url:
self.callback_data = callback_data self.url = url
self.switch_inline_query = switch_inline_query if login_url:
self.switch_inline_query_current_chat = switch_inline_query_current_chat self.login_url = login_url
self.callback_game = callback_game if callback_data:
self.pay = pay self.callback_data = callback_data
if switch_inline_query:
self.switch_inline_query = switch_inline_query
if switch_inline_query_current_chat:
self.switch_inline_query_current_chat = switch_inline_query_current_chat
if callback_game:
self.callback_game = callback_game
if pay:
self.pay = pay
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)

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 an object that represents a Telegram InlineKeyboardMarkup.""" """This module contains an object that represents a Telegram InlineKeyboardMarkup."""
from telegram import ReplyMarkup from telegram import ReplyMarkup, InlineKeyboardButton
class InlineKeyboardMarkup(ReplyMarkup): class InlineKeyboardMarkup(ReplyMarkup):
@ -49,6 +49,19 @@ class InlineKeyboardMarkup(ReplyMarkup):
return data return data
@classmethod
def de_json(cls, data, bot):
if not data:
return None
keyboard = []
for row in data['inline_keyboard']:
tmp = []
for col in row:
tmp.append(InlineKeyboardButton.de_json(col, bot))
keyboard.append(tmp)
return cls(keyboard)
@classmethod @classmethod
def from_button(cls, button, **kwargs): def from_button(cls, button, **kwargs):
"""Shortcut for:: """Shortcut for::

65
telegram/loginurl.py Normal file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2019
# 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 LoginUrl."""
from telegram import TelegramObject
class LoginUrl(TelegramObject):
"""This object represents a parameter of the inline keyboard button used to automatically
authorize a user. Serves as a great replacement for the Telegram Login Widget when the user is
coming from Telegram. All the user needs to do is tap/click a button and confirm that they want
to log in. Telegram apps support these buttons as of version 5.7.
Sample bot: @discussbot
Attributes:
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
forward_text (:obj:`str`): Optional. New text of the button in forwarded messages.
bot_username (:obj:`str`): Optional. Username of a bot, which will be used for user
authorization.
request_write_access (:obj:`bool`): Optional. Pass True to request the permission for your
bot to send messages to the user.
Args:
url (:obj:`str`): An HTTP URL to be opened with user authorization data added to the query
string when the button is pressed. If the user refuses to provide authorization data,
the original URL without information about the user will be opened. The data added is
the same as described in Receiving authorization data.
NOTE: You must always check the hash of the received data to verify the authentication
and the integrity of the data as described in Checking authorization.
forward_text (:obj:`str`, optional): New text of the button in forwarded messages.
bot_username (:obj:`str`, optional): Username of a bot, which will be used for user
authorization. See Setting up a bot for more details. If not specified, the current
bot's username will be assumed. The url's domain must be the same as the domain linked
with the bot. See Linking your domain to the bot for more details.
request_write_access (:obj:`bool`, optional): Pass True to request the permission for your
bot to send messages to the user.
"""
def __init__(self, url, forward_text=None, bot_username=None, request_write_access=None):
self.url = url
if forward_text:
self.forward_text = forward_text
if bot_username:
self.bot_username = bot_username
if request_write_access:
self.request_write_access = request_write_access
self._id_attrs = (self.url,)

View file

@ -23,7 +23,7 @@ from html import escape
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker, from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice, TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
SuccessfulPayment, VideoNote, PassportData) SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup)
from telegram import ParseMode from telegram import ParseMode
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
@ -99,9 +99,15 @@ class Message(TelegramObject):
has logged in. has logged in.
forward_signature (:obj:`str`): Optional. Signature of the post author for messages forward_signature (:obj:`str`): Optional. Signature of the post author for messages
forwarded from channels. forwarded from channels.
forward_sender_name (:obj:`str`): Optional. Sender's name for messages forwarded from users
who disallow adding a link to their account in forwarded messages.
author_signature (:obj:`str`): Optional. Signature of the post author for messages author_signature (:obj:`str`): Optional. Signature of the post author for messages
in channels. in channels.
passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data passport_data (:class:`telegram.PassportData`): Optional. Telegram Passport data.
poll (:class:`telegram.Poll`): Optional. Message is a native poll,
information about the poll.
reply_markup (:class:`telegram.InlineKeyboardMarkup`): Optional. Inline keyboard attached
to the message.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods. bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
Args: Args:
@ -117,6 +123,8 @@ class Message(TelegramObject):
channel, information about the original channel. channel, information about the original channel.
forward_from_message_id (:obj:`int`, optional): For forwarded channel posts, identifier of forward_from_message_id (:obj:`int`, optional): For forwarded channel posts, identifier of
the original message in the channel. the original message in the channel.
forward_sender_name (:obj:`str`, optional): Sender's name for messages forwarded from users
who disallow adding a link to their account in forwarded messages.
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`. original message was sent in Unix time. Converted to :class:`datetime.datetime`.
reply_to_message (:class:`telegram.Message`, optional): For replies, the original message. reply_to_message (:class:`telegram.Message`, optional): For replies, the original message.
@ -201,7 +209,12 @@ class Message(TelegramObject):
forwarded from channels. forwarded from channels.
author_signature (:obj:`str`, optional): Signature of the post author for messages author_signature (:obj:`str`, optional): Signature of the post author for messages
in channels. in channels.
passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data passport_data (:class:`telegram.PassportData`, optional): Telegram Passport data.
poll (:class:`telegram.Poll`, optional): Message is a native poll,
information about the poll.
reply_markup (:class:`telegram.InlineKeyboardMarkup`, optional): Inline keyboard attached
to the message. login_url buttons are represented as ordinary url buttons.
""" """
_effective_attachment = _UNDEFINED _effective_attachment = _UNDEFINED
@ -260,6 +273,9 @@ class Message(TelegramObject):
connected_website=None, connected_website=None,
animation=None, animation=None,
passport_data=None, passport_data=None,
poll=None,
forward_sender_name=None,
reply_markup=None,
bot=None, bot=None,
**kwargs): **kwargs):
# Required # Required
@ -304,11 +320,13 @@ class Message(TelegramObject):
self.successful_payment = successful_payment self.successful_payment = successful_payment
self.connected_website = connected_website self.connected_website = connected_website
self.forward_signature = forward_signature self.forward_signature = forward_signature
self.forward_sender_name = forward_sender_name
self.author_signature = author_signature self.author_signature = author_signature
self.media_group_id = media_group_id self.media_group_id = media_group_id
self.animation = animation self.animation = animation
self.passport_data = passport_data self.passport_data = passport_data
self.poll = poll
self.reply_markup = reply_markup
self.bot = bot self.bot = bot
self._id_attrs = (self.message_id,) self._id_attrs = (self.message_id,)
@ -362,6 +380,8 @@ class Message(TelegramObject):
data['invoice'] = Invoice.de_json(data.get('invoice'), bot) data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot) data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), bot)
data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot) data['passport_data'] = PassportData.de_json(data.get('passport_data'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
data['reply_markup'] = InlineKeyboardMarkup.de_json(data.get('reply_markup'), bot)
return cls(bot=bot, **data) return cls(bot=bot, **data)
@ -705,6 +725,23 @@ class Message(TelegramObject):
self._quote(kwargs) self._quote(kwargs)
return self.bot.send_contact(self.chat_id, *args, **kwargs) return self.bot.send_contact(self.chat_id, *args, **kwargs)
def reply_poll(self, *args, **kwargs):
"""Shortcut for::
bot.send_poll(update.message.chat_id, *args, **kwargs)
Keyword Args:
quote (:obj:`bool`, optional): If set to ``True``, the photo is sent as an actual reply
to this message. If ``reply_to_message_id`` is passed in ``kwargs``, this parameter
will be ignored. Default: ``True`` in group chats and ``False`` in private chats.
Returns:
:class:`telegram.Message`: On success, instance representing the message posted.
"""
self._quote(kwargs)
return self.bot.send_poll(self.chat_id, *args, **kwargs)
def forward(self, chat_id, disable_notification=False): def forward(self, chat_id, disable_notification=False):
"""Shortcut for:: """Shortcut for::

93
telegram/poll.py Normal file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env python
# pylint: disable=R0903
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# 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 Poll."""
from telegram import (TelegramObject)
class PollOption(TelegramObject):
"""
This object contains information about one answer option in a poll.
Attributes:
text (:obj:`str`): Option text, 1-100 characters.
voter_count (:obj:`int`): Number of users that voted for this option.
Args:
text (:obj:`str`): Option text, 1-100 characters.
voter_count (:obj:`int`): Number of users that voted for this option.
"""
def __init__(self, text, voter_count, **kwargs):
self.text = text
self.voter_count = voter_count
@classmethod
def de_json(cls, data, bot):
if not data:
return None
return cls(**data)
class Poll(TelegramObject):
"""
This object contains information about a poll.
Attributes:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:class:`PollOption`]): List of poll options.
is_closed (:obj:`bool`): True, if the poll is closed.
Args:
id (:obj:`str`): Unique poll identifier.
question (:obj:`str`): Poll question, 1-255 characters.
options (List[:class:`PollOption`]): List of poll options.
is_closed (:obj:`bool`): True, if the poll is closed.
"""
def __init__(self, id, question, options, is_closed, **kwargs):
self.id = id
self.question = question
self.options = options
self.is_closed = is_closed
self._id_attrs = (self.id,)
@classmethod
def de_json(cls, data, bot):
if not data:
return None
data = super(Poll, cls).de_json(data, bot)
data['options'] = [PollOption.de_json(option, bot) for option in data['options']]
return cls(**data)
def to_dict(self):
data = super(Poll, self).to_dict()
data['options'] = [x.to_dict() for x in self.options]
return data

View file

@ -19,7 +19,7 @@
"""This module contains an object that represents a Telegram Update.""" """This module contains an object that represents a Telegram Update."""
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult, from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
CallbackQuery, ShippingQuery, PreCheckoutQuery) CallbackQuery, ShippingQuery, PreCheckoutQuery, Poll)
class Update(TelegramObject): class Update(TelegramObject):
@ -41,6 +41,8 @@ class Update(TelegramObject):
shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query. shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
pre-checkout query. pre-checkout query.
poll (:class:`telegram.Poll`): Optional. New poll state. Bots receive only updates
about polls, which are sent or stopped by the bot
Args: Args:
update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a update_id (:obj:`int`): The update's unique identifier. Update identifiers start from a
@ -63,6 +65,8 @@ class Update(TelegramObject):
Only for invoices with flexible price. Only for invoices with flexible price.
pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming
pre-checkout query. Contains full information about checkout pre-checkout query. Contains full information about checkout
poll (:class:`telegram.Poll`, optional): New poll state. Bots receive only updates
about polls, which are sent or stopped by the bot
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
""" """
@ -78,6 +82,7 @@ class Update(TelegramObject):
callback_query=None, callback_query=None,
shipping_query=None, shipping_query=None,
pre_checkout_query=None, pre_checkout_query=None,
poll=None,
**kwargs): **kwargs):
# Required # Required
self.update_id = int(update_id) self.update_id = int(update_id)
@ -91,6 +96,7 @@ class Update(TelegramObject):
self.pre_checkout_query = pre_checkout_query self.pre_checkout_query = pre_checkout_query
self.channel_post = channel_post self.channel_post = channel_post
self.edited_channel_post = edited_channel_post self.edited_channel_post = edited_channel_post
self.poll = poll
self._effective_user = None self._effective_user = None
self._effective_chat = None self._effective_chat = None
@ -102,7 +108,7 @@ class Update(TelegramObject):
def effective_user(self): def effective_user(self):
""" """
:class:`telegram.User`: The user that sent this update, no matter what kind of update this :class:`telegram.User`: The user that sent this update, no matter what kind of update this
is. Will be ``None`` for :attr:`channel_post`. is. Will be ``None`` for :attr:`channel_post` and :attr:`poll`.
""" """
if self._effective_user: if self._effective_user:
@ -140,7 +146,7 @@ class Update(TelegramObject):
:class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of :class:`telegram.Chat`: The chat that this update was sent in, no matter what kind of
update this is. Will be ``None`` for :attr:`inline_query`, update this is. Will be ``None`` for :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages, :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query` and :attr:`pre_checkout_query`. :attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
""" """
if self._effective_chat: if self._effective_chat:
@ -172,7 +178,7 @@ class Update(TelegramObject):
:class:`telegram.Message`: The message included in this update, no matter what kind of :class:`telegram.Message`: The message included in this update, no matter what kind of
update this is. Will be ``None`` for :attr:`inline_query`, update this is. Will be ``None`` for :attr:`inline_query`,
:attr:`chosen_inline_result`, :attr:`callback_query` from inline messages, :attr:`chosen_inline_result`, :attr:`callback_query` from inline messages,
:attr:`shipping_query` and :attr:`pre_checkout_query`. :attr:`shipping_query`, :attr:`pre_checkout_query` and :attr:`poll`.
""" """
if self._effective_message: if self._effective_message:
@ -215,5 +221,6 @@ class Update(TelegramObject):
data['pre_checkout_query'] = PreCheckoutQuery.de_json(data.get('pre_checkout_query'), bot) data['pre_checkout_query'] = PreCheckoutQuery.de_json(data.get('pre_checkout_query'), bot)
data['channel_post'] = Message.de_json(data.get('channel_post'), bot) data['channel_post'] = Message.de_json(data.get('channel_post'), bot)
data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot) data['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
data['poll'] = Poll.de_json(data.get('poll'), bot)
return cls(**data) return cls(**data)

View file

@ -31,13 +31,13 @@ FALLBACKS = [
'token': '579694714:AAHRLL5zBVy4Blx2jRFKe1HlfnXCg08WuLY', 'token': '579694714:AAHRLL5zBVy4Blx2jRFKe1HlfnXCg08WuLY',
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi', 'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
'chat_id': '675666224', 'chat_id': '675666224',
'group_id': '-269513406', 'super_group_id': '-1001493296829',
'channel_id': '@pythontelegrambottests' 'channel_id': '@pythontelegrambottests'
}, { }, {
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M', 'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy', 'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
'chat_id': '675666224', 'chat_id': '675666224',
'group_id': '-269513406', 'super_group_id': '-1001493296829',
'channel_id': '@pythontelegrambottests' 'channel_id': '@pythontelegrambottests'
} }
] ]

View file

@ -55,8 +55,8 @@ def chat_id(bot_info):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def group_id(bot_info): def super_group_id(bot_info):
return bot_info['group_id'] return bot_info['super_group_id']
@pytest.fixture(scope='session') @pytest.fixture(scope='session')

View file

@ -28,7 +28,7 @@ from future.utils import string_types
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup, from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
ShippingOption, LabeledPrice) ShippingOption, LabeledPrice, Poll)
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
from telegram.utils.helpers import from_timestamp from telegram.utils.helpers import from_timestamp
@ -149,6 +149,34 @@ class TestBot(object):
assert message.contact.first_name == first_name assert message.contact.first_name == first_name
assert message.contact.last_name == last_name assert message.contact.last_name == last_name
# TODO: Add bot to group to test polls too
@flaky(3, 1)
@pytest.mark.timeout(10)
def test_send_and_stop_poll(self, bot, super_group_id):
question = 'Is this a test?'
answers = ['Yes', 'No', 'Maybe']
message = bot.send_poll(chat_id=super_group_id, question=question, options=answers,
timeout=60)
assert message.poll
assert message.poll.question == question
assert message.poll.options[0].text == answers[0]
assert message.poll.options[1].text == answers[1]
assert message.poll.options[2].text == answers[2]
assert not message.poll.is_closed
poll = bot.stop_poll(chat_id=super_group_id, message_id=message.message_id, timeout=60)
assert isinstance(poll, Poll)
assert poll.is_closed
assert poll.options[0].text == answers[0]
assert poll.options[0].voter_count == 0
assert poll.options[1].text == answers[1]
assert poll.options[1].voter_count == 0
assert poll.options[2].text == answers[2]
assert poll.options[2].voter_count == 0
assert poll.question == question
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_send_game(self, bot, chat_id): def test_send_game(self, bot, chat_id):
@ -346,12 +374,12 @@ class TestBot(object):
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
def test_get_chat(self, bot, group_id): def test_get_chat(self, bot, super_group_id):
chat = bot.get_chat(group_id) chat = bot.get_chat(super_group_id)
assert chat.type == 'group' assert chat.type == 'supergroup'
assert chat.title == '>>> telegram.Bot(test)' assert chat.title == '>>> telegram.Bot(test)'
assert chat.id == int(group_id) assert chat.id == int(super_group_id)
@flaky(3, 1) @flaky(3, 1)
@pytest.mark.timeout(10) @pytest.mark.timeout(10)
@ -609,15 +637,16 @@ class TestBot(object):
def test_set_chat_description(self, bot, channel_id): def test_set_chat_description(self, bot, channel_id):
assert bot.set_chat_description(channel_id, 'Time: ' + str(time.time())) assert bot.set_chat_description(channel_id, 'Time: ' + str(time.time()))
@flaky(3, 1) # TODO: Add bot to group to test there too
@pytest.mark.timeout(10) def test_pin_and_unpin_message(self, bot, super_group_id):
def test_error_pin_unpin_message(self, bot, message): message = bot.send_message(super_group_id, text="test_pin_message")
# TODO: Add bot to supergroup so this can be tested properly assert bot.pin_chat_message(chat_id=super_group_id, message_id=message.message_id,
with pytest.raises(BadRequest, match='Method is available only for supergroups'): disable_notification=True)
bot.pin_chat_message(message.chat_id, message.message_id, disable_notification=True)
with pytest.raises(BadRequest, match='Method is available only for supergroups'): chat = bot.get_chat(super_group_id)
bot.unpin_chat_message(message.chat_id) assert chat.pinned_message == message
assert bot.unpinChatMessage(super_group_id)
# get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set, # get_sticker_set, upload_sticker_file, create_new_sticker_set, add_sticker_to_set,
# set_sticker_position_in_set and delete_sticker_from_set are tested in the # set_sticker_position_in_set and delete_sticker_from_set are tested in the

View file

@ -196,6 +196,13 @@ class TestChat(object):
monkeypatch.setattr('telegram.Bot.send_animation', test) monkeypatch.setattr('telegram.Bot.send_animation', test)
assert chat.send_animation('test_animation') assert chat.send_animation('test_animation')
def test_instance_method_send_poll(self, monkeypatch, chat):
def test(*args, **kwargs):
return args[1] == chat.id and args[2] == 'test_poll'
monkeypatch.setattr('telegram.Bot.send_poll', test)
assert chat.send_poll('test_poll')
def test_equality(self): def test_equality(self):
a = Chat(self.id, self.title, self.type) a = Chat(self.id, self.title, self.type)
b = Chat(self.id, self.title, self.type) b = Chat(self.id, self.title, self.type)

View file

@ -19,7 +19,7 @@
import pytest import pytest
from telegram import InlineKeyboardButton from telegram import InlineKeyboardButton, LoginUrl
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
@ -31,7 +31,8 @@ def inline_keyboard_button():
switch_inline_query_current_chat=TestInlineKeyboardButton switch_inline_query_current_chat=TestInlineKeyboardButton
.switch_inline_query_current_chat, .switch_inline_query_current_chat,
callback_game=TestInlineKeyboardButton.callback_game, callback_game=TestInlineKeyboardButton.callback_game,
pay=TestInlineKeyboardButton.pay) pay=TestInlineKeyboardButton.pay,
login_url=TestInlineKeyboardButton.login_url)
class TestInlineKeyboardButton(object): class TestInlineKeyboardButton(object):
@ -42,6 +43,7 @@ class TestInlineKeyboardButton(object):
switch_inline_query_current_chat = 'switch_inline_query_current_chat' switch_inline_query_current_chat = 'switch_inline_query_current_chat'
callback_game = 'callback_game' callback_game = 'callback_game'
pay = 'pay' pay = 'pay'
login_url = LoginUrl("http://google.com")
def test_expected_values(self, inline_keyboard_button): def test_expected_values(self, inline_keyboard_button):
assert inline_keyboard_button.text == self.text assert inline_keyboard_button.text == self.text
@ -52,6 +54,7 @@ class TestInlineKeyboardButton(object):
== self.switch_inline_query_current_chat) == self.switch_inline_query_current_chat)
assert inline_keyboard_button.callback_game == self.callback_game assert inline_keyboard_button.callback_game == self.callback_game
assert inline_keyboard_button.pay == self.pay assert inline_keyboard_button.pay == self.pay
assert inline_keyboard_button.login_url == self.login_url
def test_to_dict(self, inline_keyboard_button): def test_to_dict(self, inline_keyboard_button):
inline_keyboard_button_dict = inline_keyboard_button.to_dict() inline_keyboard_button_dict = inline_keyboard_button.to_dict()
@ -66,3 +69,26 @@ class TestInlineKeyboardButton(object):
== inline_keyboard_button.switch_inline_query_current_chat) == inline_keyboard_button.switch_inline_query_current_chat)
assert inline_keyboard_button_dict['callback_game'] == inline_keyboard_button.callback_game assert inline_keyboard_button_dict['callback_game'] == inline_keyboard_button.callback_game
assert inline_keyboard_button_dict['pay'] == inline_keyboard_button.pay assert inline_keyboard_button_dict['pay'] == inline_keyboard_button.pay
assert inline_keyboard_button_dict['login_url'] == \
inline_keyboard_button.login_url.to_dict() # NOQA: E127
def test_de_json(self, bot):
json_dict = {
'text': self.text,
'url': self.url,
'callback_data': self.callback_data,
'switch_inline_query': self.switch_inline_query,
'switch_inline_query_current_chat': self.switch_inline_query_current_chat,
'callback_game': self.callback_game,
'pay': self.pay
}
inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None)
assert inline_keyboard_button.text == self.text
assert inline_keyboard_button.url == self.url
assert inline_keyboard_button.callback_data == self.callback_data
assert inline_keyboard_button.switch_inline_query == self.switch_inline_query
assert (inline_keyboard_button.switch_inline_query_current_chat
== self.switch_inline_query_current_chat)
assert inline_keyboard_button.callback_game == self.callback_game
assert inline_keyboard_button.pay == self.pay

View file

@ -78,3 +78,34 @@ class TestInlineKeyboardMarkup(object):
self.inline_keyboard[0][1].to_dict() self.inline_keyboard[0][1].to_dict()
] ]
] ]
def test_de_json(self):
json_dict = {
'inline_keyboard': [[
{
'text': 'start',
'url': 'http://google.com'
},
{
'text': 'next',
'callback_data': 'abcd'
}],
[{
'text': 'Cancel',
'callback_data': 'Cancel'
}]
]}
inline_keyboard_markup = InlineKeyboardMarkup.de_json(json_dict, None)
assert isinstance(inline_keyboard_markup, InlineKeyboardMarkup)
keyboard = inline_keyboard_markup.inline_keyboard
assert len(keyboard) == 2
assert len(keyboard[0]) == 2
assert len(keyboard[1]) == 1
assert isinstance(keyboard[0][0], InlineKeyboardButton)
assert isinstance(keyboard[0][1], InlineKeyboardButton)
assert isinstance(keyboard[1][0], InlineKeyboardButton)
assert keyboard[0][0].text == 'start'
assert keyboard[0][0].url == 'http://google.com'

61
tests/test_loginurl.py Normal file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2019
# 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/].
import pytest
from telegram import LoginUrl
@pytest.fixture(scope='class')
def login_url():
return LoginUrl(url=TestLoginUrl.url, forward_text=TestLoginUrl.forward_text,
bot_username=TestLoginUrl.bot_username,
request_write_access=TestLoginUrl.request_write_access)
class TestLoginUrl(object):
url = "http://www.google.com"
forward_text = "Send me forward!"
bot_username = "botname"
request_write_access = True
def test_to_dict(self, login_url):
login_url_dict = login_url.to_dict()
assert isinstance(login_url_dict, dict)
assert login_url_dict['url'] == self.url
assert login_url_dict['forward_text'] == self.forward_text
assert login_url_dict['bot_username'] == self.bot_username
assert login_url_dict['request_write_access'] == self.request_write_access
def test_equality(self):
a = LoginUrl(self.url, self.forward_text, self.bot_username, self.request_write_access)
b = LoginUrl(self.url, self.forward_text, self.bot_username, self.request_write_access)
c = LoginUrl(self.url)
d = LoginUrl("text.com", self.forward_text, self.bot_username, self.request_write_access)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a == c
assert hash(a) == hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -20,10 +20,9 @@ from datetime import datetime
import pytest import pytest
from telegram import ParseMode
from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document, Animation, from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document, Animation,
Game, PhotoSize, Sticker, Video, Voice, VideoNote, Contact, Location, Venue, Game, PhotoSize, Sticker, Video, Voice, VideoNote, Contact, Location, Venue,
Invoice, SuccessfulPayment, PassportData) Invoice, SuccessfulPayment, PassportData, ParseMode, Poll, PollOption)
from tests.test_passport import RAW_PASSPORT_DATA from tests.test_passport import RAW_PASSPORT_DATA
@ -88,7 +87,14 @@ def message(bot):
{'photo': [PhotoSize('photo_id', 50, 50)], {'photo': [PhotoSize('photo_id', 50, 50)],
'caption': 'photo_file', 'caption': 'photo_file',
'media_group_id': 1234443322222}, 'media_group_id': 1234443322222},
{'passport_data': PassportData.de_json(RAW_PASSPORT_DATA, None)} {'passport_data': PassportData.de_json(RAW_PASSPORT_DATA, None)},
{'poll': Poll(id='abc', question='What is this?',
options=[PollOption(text='a', voter_count=1),
PollOption(text='b', voter_count=2)], is_closed=False)},
{'text': 'a text message', 'reply_markup': {'inline_keyboard': [[{
'text': 'start', 'url': 'http://google.com'}, {
'text': 'next', 'callback_data': 'abcd'}],
[{'text': 'Cancel', 'callback_data': 'Cancel'}]]}}
], ],
ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text', ids=['forwarded_user', 'forwarded_channel', 'reply', 'edited', 'text',
'caption_entities', 'audio', 'document', 'animation', 'game', 'photo', 'caption_entities', 'audio', 'document', 'animation', 'game', 'photo',
@ -97,7 +103,7 @@ def message(bot):
'group_created', 'supergroup_created', 'channel_created', 'migrated_to', 'group_created', 'supergroup_created', 'channel_created', 'migrated_to',
'migrated_from', 'pinned', 'invoice', 'successful_payment', 'migrated_from', 'pinned', 'invoice', 'successful_payment',
'connected_website', 'forward_signature', 'author_signature', 'connected_website', 'forward_signature', 'author_signature',
'photo_from_media_group', 'passport_data']) 'photo_from_media_group', 'passport_data', 'poll', 'reply_markup'])
def message_params(bot, request): def message_params(bot, request):
return Message(message_id=TestMessage.id, return Message(message_id=TestMessage.id,
from_user=TestMessage.from_user, from_user=TestMessage.from_user,
@ -554,6 +560,20 @@ class TestMessage(object):
assert message.reply_contact(contact='test_contact') assert message.reply_contact(contact='test_contact')
assert message.reply_contact(contact='test_contact', quote=True) assert message.reply_contact(contact='test_contact', quote=True)
def test_reply_poll(self, monkeypatch, message):
def test(*args, **kwargs):
id = args[1] == message.chat_id
contact = kwargs['contact'] == 'test_poll'
if kwargs.get('reply_to_message_id'):
reply = kwargs['reply_to_message_id'] == message.message_id
else:
reply = True
return id and contact and reply
monkeypatch.setattr('telegram.Bot.send_poll', test)
assert message.reply_poll(contact='test_poll')
assert message.reply_poll(contact='test_poll', quote=True)
def test_forward(self, monkeypatch, message): def test_forward(self, monkeypatch, message):
def test(*args, **kwargs): def test(*args, **kwargs):
chat_id = kwargs['chat_id'] == 123456 chat_id = kwargs['chat_id'] == 123456

92
tests/test_poll.py Normal file
View file

@ -0,0 +1,92 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2018
# 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/].
import pytest
from telegram import Poll, PollOption
@pytest.fixture(scope="class")
def poll_option():
return PollOption(text=TestPollOption.text,
voter_count=TestPollOption.voter_count)
class TestPollOption(object):
text = "test option"
voter_count = 3
def test_de_json(self):
json_dict = {
'text': self.text,
'voter_count': self.voter_count
}
poll_option = PollOption.de_json(json_dict, None)
assert poll_option.text == self.text
assert poll_option.voter_count == self.voter_count
def test_to_dict(self, poll_option):
poll_option_dict = poll_option.to_dict()
assert isinstance(poll_option_dict, dict)
assert poll_option_dict['text'] == poll_option.text
assert poll_option_dict['voter_count'] == poll_option.voter_count
@pytest.fixture(scope='class')
def poll():
return Poll(TestPoll.id,
TestPoll.question,
TestPoll.options,
TestPoll.is_closed)
class TestPoll(object):
id = 'id'
question = 'Test?'
options = [PollOption('test', 10), PollOption('test2', 11)]
is_closed = True
def test_de_json(self):
json_dict = {
'id': self.id,
'question': self.question,
'options': [o.to_dict() for o in self.options],
'is_closed': self.is_closed
}
poll = Poll.de_json(json_dict, None)
assert poll.id == self.id
assert poll.question == self.question
assert poll.options == self.options
assert poll.options[0].text == self.options[0].text
assert poll.options[0].voter_count == self.options[0].voter_count
assert poll.options[1].text == self.options[1].text
assert poll.options[1].voter_count == self.options[1].voter_count
assert poll.is_closed == self.is_closed
def test_to_dict(self, poll):
poll_dict = poll.to_dict()
assert isinstance(poll_dict, dict)
assert poll_dict['id'] == poll.id
assert poll_dict['question'] == poll.question
assert poll_dict['options'] == [o.to_dict() for o in poll.options]
assert poll_dict['is_closed'] == poll.is_closed

View file

@ -20,7 +20,7 @@
import pytest import pytest
from telegram import (Message, User, Update, Chat, CallbackQuery, InlineQuery, from telegram import (Message, User, Update, Chat, CallbackQuery, InlineQuery,
ChosenInlineResult, ShippingQuery, PreCheckoutQuery) ChosenInlineResult, ShippingQuery, PreCheckoutQuery, Poll, PollOption)
message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text') message = Message(1, User(1, '', False), None, Chat(1, ''), text='Text')
@ -34,12 +34,13 @@ params = [
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')}, {'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)}, {'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
{'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')}, {'pre_checkout_query': PreCheckoutQuery('id', User(1, '', False), '', 0, '')},
{'callback_query': CallbackQuery(1, User(1, '', False), 'chat')} {'callback_query': CallbackQuery(1, User(1, '', False), 'chat')},
{'poll': Poll('id', '?', [PollOption('.', 1)], False)}
] ]
all_types = ('message', 'edited_message', 'callback_query', 'channel_post', all_types = ('message', 'edited_message', 'callback_query', 'channel_post',
'edited_channel_post', 'inline_query', 'chosen_inline_result', 'edited_channel_post', 'inline_query', 'chosen_inline_result',
'shipping_query', 'pre_checkout_query') 'shipping_query', 'pre_checkout_query', 'poll')
ids = all_types + ('callback_query_without_message',) ids = all_types + ('callback_query_without_message',)
@ -91,7 +92,8 @@ class TestUpdate(object):
or (update.callback_query is not None or (update.callback_query is not None
and update.callback_query.message is None) and update.callback_query.message is None)
or update.shipping_query is not None or update.shipping_query is not None
or update.pre_checkout_query is not None): or update.pre_checkout_query is not None
or update.poll is not None):
assert chat.id == 1 assert chat.id == 1
else: else:
assert chat is None assert chat is None
@ -99,7 +101,9 @@ class TestUpdate(object):
def test_effective_user(self, update): def test_effective_user(self, update):
# Test that it's sometimes None per docstring # Test that it's sometimes None per docstring
user = update.effective_user user = update.effective_user
if not (update.channel_post is not None or update.edited_channel_post is not None): if not (update.channel_post is not None
or update.edited_channel_post is not None
or update.poll is not None):
assert user.id == 1 assert user.id == 1
else: else:
assert user is None assert user is None
@ -112,7 +116,8 @@ class TestUpdate(object):
or (update.callback_query is not None or (update.callback_query is not None
and update.callback_query.message is None) and update.callback_query.message is None)
or update.shipping_query is not None or update.shipping_query is not None
or update.pre_checkout_query is not None): or update.pre_checkout_query is not None
or update.poll is not None):
assert eff_message.message_id == message.message_id assert eff_message.message_id == message.message_id
else: else:
assert eff_message is None assert eff_message is None