Merge branch '4.3' into RC1

This commit is contained in:
Pieter Schutz 2019-06-05 22:19:48 +02:00
commit 3fc57479f3
9 changed files with 246 additions and 18 deletions

View file

@ -48,6 +48,7 @@ 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 .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
@ -58,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
@ -153,5 +154,6 @@ __all__ = [
'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation', 'PersonalDetails', 'ResidentialAddress', 'InputMediaVideo', 'InputMediaAnimation',
'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError', 'InputMediaAudio', 'InputMediaDocument', 'TelegramDecryptionError',
'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile', 'PassportElementErrorSelfie', 'PassportElementErrorTranslationFile',
'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll', 'PollOption' 'PassportElementErrorTranslationFiles', 'PassportElementErrorUnspecified', 'Poll',
'PollOption', 'LoginUrl'
] ]

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, Poll) 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
@ -106,6 +106,8 @@ class Message(TelegramObject):
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, poll (:class:`telegram.Poll`): Optional. Message is a native poll,
information about the 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:
@ -210,6 +212,9 @@ class Message(TelegramObject):
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, poll (:class:`telegram.Poll`, optional): Message is a native poll,
information about the 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
@ -270,6 +275,7 @@ class Message(TelegramObject):
passport_data=None, passport_data=None,
poll=None, poll=None,
forward_sender_name=None, forward_sender_name=None,
reply_markup=None,
bot=None, bot=None,
**kwargs): **kwargs):
# Required # Required
@ -320,7 +326,7 @@ class Message(TelegramObject):
self.animation = animation self.animation = animation
self.passport_data = passport_data self.passport_data = passport_data
self.poll = poll 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,)
@ -375,6 +381,7 @@ class Message(TelegramObject):
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['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)

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, Poll, PollOption
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
@ -91,7 +90,11 @@ def message(bot):
{'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?', {'poll': Poll(id='abc', question='What is this?',
options=[PollOption(text='a', voter_count=1), options=[PollOption(text='a', voter_count=1),
PollOption(text='b', voter_count=2)], is_closed=False)} 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',
@ -100,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', 'poll']) '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,