mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-22 14:35:00 +01:00
All api 4.2 and 4.3 changes (#1418)
This commit is contained in:
parent
e492d5b97b
commit
ac60d057a5
25 changed files with 702 additions and 60 deletions
|
@ -1,20 +1,20 @@
|
|||
repos:
|
||||
- repo: git://github.com/python-telegram-bot/mirrors-yapf
|
||||
rev: master
|
||||
sha: 5769e088ef6e0a0d1eb63bd6d0c1fe9f3606d6c8
|
||||
hooks:
|
||||
- id: yapf
|
||||
files: ^(telegram|tests)/.*\.py$
|
||||
args:
|
||||
- --diff
|
||||
- repo: git://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.0.0
|
||||
sha: 0b70e285e369bcb24b57b74929490ea7be9c4b19
|
||||
hooks:
|
||||
- id: flake8
|
||||
exclude: ^(setup.py|docs/source/conf.py)$
|
||||
args:
|
||||
- --ignore=W605,W503
|
||||
- repo: git://github.com/pre-commit/mirrors-pylint
|
||||
rev: v2.3.0
|
||||
sha: 9d8dcbc2b86c796275680f239c1e90dcd50bd398
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^telegram/.*\.py$
|
||||
|
|
|
@ -7,7 +7,6 @@ environment:
|
|||
# isn't covered by this document) at the time of writing.
|
||||
|
||||
- PYTHON: "C:\\Python27"
|
||||
- PYTHON: "C:\\Python34"
|
||||
- PYTHON: "C:\\Python35"
|
||||
- PYTHON: "C:\\Python36"
|
||||
- PYTHON: "C:\\Python37"
|
||||
|
|
6
docs/source/telegram.poll.rst
Normal file
6
docs/source/telegram.poll.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.Poll
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.Poll
|
||||
:members:
|
||||
:show-inheritance:
|
6
docs/source/telegram.polloption.rst
Normal file
6
docs/source/telegram.polloption.rst
Normal file
|
@ -0,0 +1,6 @@
|
|||
telegram.PollOption
|
||||
===================
|
||||
|
||||
.. autoclass:: telegram.PollOption
|
||||
:members:
|
||||
:show-inheritance:
|
|
@ -35,6 +35,8 @@ telegram package
|
|||
telegram.messageentity
|
||||
telegram.parsemode
|
||||
telegram.photosize
|
||||
telegram.poll
|
||||
telegram.polloption
|
||||
telegram.replykeyboardremove
|
||||
telegram.replykeyboardmarkup
|
||||
telegram.replymarkup
|
||||
|
|
|
@ -47,6 +47,8 @@ from .files.file import File
|
|||
from .parsemode import ParseMode
|
||||
from .messageentity import MessageEntity
|
||||
from .games.game import Game
|
||||
from .poll import Poll, PollOption
|
||||
from .loginurl import LoginUrl
|
||||
from .games.callbackgame import CallbackGame
|
||||
from .payment.shippingaddress import ShippingAddress
|
||||
from .payment.orderinfo import OrderInfo
|
||||
|
@ -57,11 +59,11 @@ from .passport.passportfile import PassportFile
|
|||
from .passport.data import IdDocumentData, PersonalDetails, ResidentialAddress
|
||||
from .passport.encryptedpassportelement import EncryptedPassportElement
|
||||
from .passport.passportdata import PassportData
|
||||
from .inline.inlinekeyboardbutton import InlineKeyboardButton
|
||||
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
|
||||
from .message import Message
|
||||
from .callbackquery import CallbackQuery
|
||||
from .choseninlineresult import ChosenInlineResult
|
||||
from .inline.inlinekeyboardbutton import InlineKeyboardButton
|
||||
from .inline.inlinekeyboardmarkup import InlineKeyboardMarkup
|
||||
from .inline.inputmessagecontent import InputMessageContent
|
||||
from .inline.inlinequery import InlineQuery
|
||||
from .inline.inlinequeryresult import InlineQueryResult
|
||||
|
@ -152,5 +154,6 @@ __all__ = [
|
|||
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
|
||||
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
|
||||
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified'
|
||||
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
|
||||
'PollOption', 'LoginUrl'
|
||||
]
|
||||
|
|
118
telegram/bot.py
118
telegram/bot.py
|
@ -37,7 +37,7 @@ from future.utils import string_types
|
|||
from telegram import (User, Message, Update, Chat, ChatMember, UserProfilePhotos, File,
|
||||
ReplyMarkup, TelegramObject, WebhookInfo, GameHighScore, StickerSet,
|
||||
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.utils.helpers import to_timestamp
|
||||
from telegram.utils.request import Request
|
||||
|
@ -260,13 +260,16 @@ class Bot(TelegramObject):
|
|||
@log
|
||||
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
|
||||
than 48 hours ago. Any such recently sent outgoing message may be deleted. Additionally,
|
||||
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
|
||||
messages about people joining or leaving the group (other types of service messages may
|
||||
only be removed by the group creator). In channels, bots can only remove their own
|
||||
messages.
|
||||
Use this method to delete a message, including service messages, with the following
|
||||
limitations:
|
||||
|
||||
- A message can only be deleted if it was sent less than 48 hours ago.
|
||||
- Bots can delete outgoing messages in private chats, groups, and supergroups.
|
||||
- Bots can delete incoming messages in private chats.
|
||||
- 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:
|
||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||
|
@ -3319,6 +3322,101 @@ class Bot(TelegramObject):
|
|||
|
||||
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):
|
||||
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`"""
|
||||
setPassportDataErrors = 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`"""
|
||||
|
|
|
@ -337,3 +337,16 @@ class Chat(TelegramObject):
|
|||
|
||||
"""
|
||||
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)
|
||||
|
|
|
@ -46,6 +46,8 @@ class ChatMember(TelegramObject):
|
|||
can_pin_messages (:obj:`bool`): Optional. If the administrator can pin messages.
|
||||
can_promote_members (:obj:`bool`): Optional. If the administrator can add new
|
||||
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,
|
||||
locations and venues.
|
||||
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
|
||||
administrators that he has promoted, directly or indirectly (promoted by administrators
|
||||
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
|
||||
messages, contacts, locations and venues.
|
||||
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_promote_members=None, can_send_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
|
||||
self.user = user
|
||||
self.status = status
|
||||
|
@ -129,6 +133,7 @@ class ChatMember(TelegramObject):
|
|||
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.is_member = is_member
|
||||
|
||||
self._id_attrs = (self.user, self.status)
|
||||
|
||||
|
|
|
@ -31,6 +31,8 @@ class InlineKeyboardButton(TelegramObject):
|
|||
Attributes:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
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
|
||||
button is pressed, UTF-8 1-64 bytes.
|
||||
switch_inline_query (:obj:`str`): Optional. Will prompt the user to select one of their
|
||||
|
@ -45,6 +47,8 @@ class InlineKeyboardButton(TelegramObject):
|
|||
Args:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
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
|
||||
button is pressed, 1-64 UTF-8 bytes.
|
||||
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,
|
||||
callback_game=None,
|
||||
pay=None,
|
||||
login_url=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.text = text
|
||||
|
||||
# Optionals
|
||||
if url:
|
||||
self.url = url
|
||||
if login_url:
|
||||
self.login_url = login_url
|
||||
if callback_data:
|
||||
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)
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram InlineKeyboardMarkup."""
|
||||
|
||||
from telegram import ReplyMarkup
|
||||
from telegram import ReplyMarkup, InlineKeyboardButton
|
||||
|
||||
|
||||
class InlineKeyboardMarkup(ReplyMarkup):
|
||||
|
@ -49,6 +49,19 @@ class InlineKeyboardMarkup(ReplyMarkup):
|
|||
|
||||
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
|
||||
def from_button(cls, button, **kwargs):
|
||||
"""Shortcut for::
|
||||
|
|
65
telegram/loginurl.py
Normal file
65
telegram/loginurl.py
Normal 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,)
|
|
@ -23,7 +23,7 @@ from html import escape
|
|||
|
||||
from telegram import (Animation, Audio, Contact, Document, Chat, Location, PhotoSize, Sticker,
|
||||
TelegramObject, User, Video, Voice, Venue, MessageEntity, Game, Invoice,
|
||||
SuccessfulPayment, VideoNote, PassportData)
|
||||
SuccessfulPayment, VideoNote, PassportData, Poll, InlineKeyboardMarkup)
|
||||
from telegram import ParseMode
|
||||
from telegram.utils.helpers import escape_markdown, to_timestamp, from_timestamp
|
||||
|
||||
|
@ -99,9 +99,15 @@ class Message(TelegramObject):
|
|||
has logged in.
|
||||
forward_signature (:obj:`str`): Optional. Signature of the post author for messages
|
||||
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
|
||||
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.
|
||||
|
||||
Args:
|
||||
|
@ -117,6 +123,8 @@ class Message(TelegramObject):
|
|||
channel, information about the original channel.
|
||||
forward_from_message_id (:obj:`int`, optional): For forwarded channel posts, identifier of
|
||||
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
|
||||
original message was sent in Unix time. Converted to :class:`datetime.datetime`.
|
||||
reply_to_message (:class:`telegram.Message`, optional): For replies, the original message.
|
||||
|
@ -201,7 +209,12 @@ class Message(TelegramObject):
|
|||
forwarded from channels.
|
||||
author_signature (:obj:`str`, optional): Signature of the post author for messages
|
||||
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
|
||||
|
@ -260,6 +273,9 @@ class Message(TelegramObject):
|
|||
connected_website=None,
|
||||
animation=None,
|
||||
passport_data=None,
|
||||
poll=None,
|
||||
forward_sender_name=None,
|
||||
reply_markup=None,
|
||||
bot=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
|
@ -304,11 +320,13 @@ class Message(TelegramObject):
|
|||
self.successful_payment = successful_payment
|
||||
self.connected_website = connected_website
|
||||
self.forward_signature = forward_signature
|
||||
self.forward_sender_name = forward_sender_name
|
||||
self.author_signature = author_signature
|
||||
self.media_group_id = media_group_id
|
||||
self.animation = animation
|
||||
self.passport_data = passport_data
|
||||
|
||||
self.poll = poll
|
||||
self.reply_markup = reply_markup
|
||||
self.bot = bot
|
||||
|
||||
self._id_attrs = (self.message_id,)
|
||||
|
@ -362,6 +380,8 @@ class Message(TelegramObject):
|
|||
data['invoice'] = Invoice.de_json(data.get('invoice'), bot)
|
||||
data['successful_payment'] = SuccessfulPayment.de_json(data.get('successful_payment'), 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)
|
||||
|
||||
|
@ -705,6 +725,23 @@ class Message(TelegramObject):
|
|||
self._quote(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):
|
||||
"""Shortcut for::
|
||||
|
||||
|
|
93
telegram/poll.py
Normal file
93
telegram/poll.py
Normal 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
|
|
@ -19,7 +19,7 @@
|
|||
"""This module contains an object that represents a Telegram Update."""
|
||||
|
||||
from telegram import (Message, TelegramObject, InlineQuery, ChosenInlineResult,
|
||||
CallbackQuery, ShippingQuery, PreCheckoutQuery)
|
||||
CallbackQuery, ShippingQuery, PreCheckoutQuery, Poll)
|
||||
|
||||
|
||||
class Update(TelegramObject):
|
||||
|
@ -41,6 +41,8 @@ class Update(TelegramObject):
|
|||
shipping_query (:class:`telegram.ShippingQuery`): Optional. New incoming shipping query.
|
||||
pre_checkout_query (:class:`telegram.PreCheckoutQuery`): Optional. New incoming
|
||||
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:
|
||||
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.
|
||||
pre_checkout_query (:class:`telegram.PreCheckoutQuery`, optional): New incoming
|
||||
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.
|
||||
|
||||
"""
|
||||
|
@ -78,6 +82,7 @@ class Update(TelegramObject):
|
|||
callback_query=None,
|
||||
shipping_query=None,
|
||||
pre_checkout_query=None,
|
||||
poll=None,
|
||||
**kwargs):
|
||||
# Required
|
||||
self.update_id = int(update_id)
|
||||
|
@ -91,6 +96,7 @@ class Update(TelegramObject):
|
|||
self.pre_checkout_query = pre_checkout_query
|
||||
self.channel_post = channel_post
|
||||
self.edited_channel_post = edited_channel_post
|
||||
self.poll = poll
|
||||
|
||||
self._effective_user = None
|
||||
self._effective_chat = None
|
||||
|
@ -102,7 +108,7 @@ class Update(TelegramObject):
|
|||
def effective_user(self):
|
||||
"""
|
||||
: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:
|
||||
|
@ -140,7 +146,7 @@ class Update(TelegramObject):
|
|||
: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`,
|
||||
: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:
|
||||
|
@ -172,7 +178,7 @@ class Update(TelegramObject):
|
|||
:class:`telegram.Message`: The message included in this update, no matter what kind of
|
||||
update this is. Will be ``None`` for :attr:`inline_query`,
|
||||
: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:
|
||||
|
@ -215,5 +221,6 @@ class Update(TelegramObject):
|
|||
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['edited_channel_post'] = Message.de_json(data.get('edited_channel_post'), bot)
|
||||
data['poll'] = Poll.de_json(data.get('poll'), bot)
|
||||
|
||||
return cls(**data)
|
||||
|
|
|
@ -31,13 +31,13 @@ FALLBACKS = [
|
|||
'token': '579694714:AAHRLL5zBVy4Blx2jRFKe1HlfnXCg08WuLY',
|
||||
'payment_provider_token': '284685063:TEST:NjQ0NjZlNzI5YjJi',
|
||||
'chat_id': '675666224',
|
||||
'group_id': '-269513406',
|
||||
'super_group_id': '-1001493296829',
|
||||
'channel_id': '@pythontelegrambottests'
|
||||
}, {
|
||||
'token': '558194066:AAEEylntuKSLXj9odiv3TnX7Z5KY2J3zY3M',
|
||||
'payment_provider_token': '284685063:TEST:YjEwODQwMTFmNDcy',
|
||||
'chat_id': '675666224',
|
||||
'group_id': '-269513406',
|
||||
'super_group_id': '-1001493296829',
|
||||
'channel_id': '@pythontelegrambottests'
|
||||
}
|
||||
]
|
||||
|
|
|
@ -55,8 +55,8 @@ def chat_id(bot_info):
|
|||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def group_id(bot_info):
|
||||
return bot_info['group_id']
|
||||
def super_group_id(bot_info):
|
||||
return bot_info['super_group_id']
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
|
|
|
@ -28,7 +28,7 @@ from future.utils import string_types
|
|||
|
||||
from telegram import (Bot, Update, ChatAction, TelegramError, User, InlineKeyboardMarkup,
|
||||
InlineKeyboardButton, InlineQueryResultArticle, InputTextMessageContent,
|
||||
ShippingOption, LabeledPrice)
|
||||
ShippingOption, LabeledPrice, Poll)
|
||||
from telegram.error import BadRequest, InvalidToken, NetworkError, RetryAfter
|
||||
from telegram.utils.helpers import from_timestamp
|
||||
|
||||
|
@ -149,6 +149,34 @@ class TestBot(object):
|
|||
assert message.contact.first_name == first_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)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_send_game(self, bot, chat_id):
|
||||
|
@ -346,12 +374,12 @@ class TestBot(object):
|
|||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_get_chat(self, bot, group_id):
|
||||
chat = bot.get_chat(group_id)
|
||||
def test_get_chat(self, bot, super_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.id == int(group_id)
|
||||
assert chat.id == int(super_group_id)
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
|
@ -609,15 +637,16 @@ class TestBot(object):
|
|||
def test_set_chat_description(self, bot, channel_id):
|
||||
assert bot.set_chat_description(channel_id, 'Time: ' + str(time.time()))
|
||||
|
||||
@flaky(3, 1)
|
||||
@pytest.mark.timeout(10)
|
||||
def test_error_pin_unpin_message(self, bot, message):
|
||||
# TODO: Add bot to supergroup so this can be tested properly
|
||||
with pytest.raises(BadRequest, match='Method is available only for supergroups'):
|
||||
bot.pin_chat_message(message.chat_id, message.message_id, disable_notification=True)
|
||||
# TODO: Add bot to group to test there too
|
||||
def test_pin_and_unpin_message(self, bot, super_group_id):
|
||||
message = bot.send_message(super_group_id, text="test_pin_message")
|
||||
assert bot.pin_chat_message(chat_id=super_group_id, message_id=message.message_id,
|
||||
disable_notification=True)
|
||||
|
||||
with pytest.raises(BadRequest, match='Method is available only for supergroups'):
|
||||
bot.unpin_chat_message(message.chat_id)
|
||||
chat = bot.get_chat(super_group_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,
|
||||
# set_sticker_position_in_set and delete_sticker_from_set are tested in the
|
||||
|
|
|
@ -196,6 +196,13 @@ class TestChat(object):
|
|||
monkeypatch.setattr('telegram.Bot.send_animation', test)
|
||||
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):
|
||||
a = Chat(self.id, self.title, self.type)
|
||||
b = Chat(self.id, self.title, self.type)
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import InlineKeyboardButton
|
||||
from telegram import InlineKeyboardButton, LoginUrl
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
|
@ -31,7 +31,8 @@ def inline_keyboard_button():
|
|||
switch_inline_query_current_chat=TestInlineKeyboardButton
|
||||
.switch_inline_query_current_chat,
|
||||
callback_game=TestInlineKeyboardButton.callback_game,
|
||||
pay=TestInlineKeyboardButton.pay)
|
||||
pay=TestInlineKeyboardButton.pay,
|
||||
login_url=TestInlineKeyboardButton.login_url)
|
||||
|
||||
|
||||
class TestInlineKeyboardButton(object):
|
||||
|
@ -42,6 +43,7 @@ class TestInlineKeyboardButton(object):
|
|||
switch_inline_query_current_chat = 'switch_inline_query_current_chat'
|
||||
callback_game = 'callback_game'
|
||||
pay = 'pay'
|
||||
login_url = LoginUrl("http://google.com")
|
||||
|
||||
def test_expected_values(self, inline_keyboard_button):
|
||||
assert inline_keyboard_button.text == self.text
|
||||
|
@ -52,6 +54,7 @@ class TestInlineKeyboardButton(object):
|
|||
== self.switch_inline_query_current_chat)
|
||||
assert inline_keyboard_button.callback_game == self.callback_game
|
||||
assert inline_keyboard_button.pay == self.pay
|
||||
assert inline_keyboard_button.login_url == self.login_url
|
||||
|
||||
def test_to_dict(self, inline_keyboard_button):
|
||||
inline_keyboard_button_dict = inline_keyboard_button.to_dict()
|
||||
|
@ -66,3 +69,26 @@ class TestInlineKeyboardButton(object):
|
|||
== 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['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
|
||||
|
|
|
@ -78,3 +78,34 @@ class TestInlineKeyboardMarkup(object):
|
|||
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
61
tests/test_loginurl.py
Normal 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)
|
|
@ -20,10 +20,9 @@ from datetime import datetime
|
|||
|
||||
import pytest
|
||||
|
||||
from telegram import ParseMode
|
||||
from telegram import (Update, Message, User, MessageEntity, Chat, Audio, Document, Animation,
|
||||
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
|
||||
|
||||
|
||||
|
@ -88,7 +87,14 @@ def message(bot):
|
|||
{'photo': [PhotoSize('photo_id', 50, 50)],
|
||||
'caption': 'photo_file',
|
||||
'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',
|
||||
'caption_entities', 'audio', 'document', 'animation', 'game', 'photo',
|
||||
|
@ -97,7 +103,7 @@ def message(bot):
|
|||
'group_created', 'supergroup_created', 'channel_created', 'migrated_to',
|
||||
'migrated_from', 'pinned', 'invoice', 'successful_payment',
|
||||
'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):
|
||||
return Message(message_id=TestMessage.id,
|
||||
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', 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(*args, **kwargs):
|
||||
chat_id = kwargs['chat_id'] == 123456
|
||||
|
|
92
tests/test_poll.py
Normal file
92
tests/test_poll.py
Normal 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
|
|
@ -20,7 +20,7 @@
|
|||
import pytest
|
||||
|
||||
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')
|
||||
|
||||
|
@ -34,12 +34,13 @@ params = [
|
|||
{'chosen_inline_result': ChosenInlineResult('id', User(1, '', False), '')},
|
||||
{'shipping_query': ShippingQuery('id', User(1, '', False), '', None)},
|
||||
{'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',
|
||||
'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',)
|
||||
|
||||
|
@ -91,7 +92,8 @@ class TestUpdate(object):
|
|||
or (update.callback_query is not None
|
||||
and update.callback_query.message is 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
|
||||
else:
|
||||
assert chat is None
|
||||
|
@ -99,7 +101,9 @@ class TestUpdate(object):
|
|||
def test_effective_user(self, update):
|
||||
# Test that it's sometimes None per docstring
|
||||
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
|
||||
else:
|
||||
assert user is None
|
||||
|
@ -112,7 +116,8 @@ class TestUpdate(object):
|
|||
or (update.callback_query is not None
|
||||
and update.callback_query.message is 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
|
||||
else:
|
||||
assert eff_message is None
|
||||
|
|
Loading…
Reference in a new issue