Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
This commit is contained in:
Poolitzer 2022-06-28 19:34:12 +02:00 committed by GitHub
parent 7a7465e8e2
commit debe86aea2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 402 additions and 14 deletions

View file

@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -112,7 +112,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.0** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
==========
Installing

View file

@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.0-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.1-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -105,7 +105,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support
====================
All types and methods of the Telegram Bot API **6.0** are supported.
All types and methods of the Telegram Bot API **6.1** are supported.
==========
Installing

View file

@ -30,6 +30,7 @@ filterwarnings =
; Unfortunately due to https://github.com/pytest-dev/pytest/issues/8343 we can't have this here
; and instead do a trick directly in tests/conftest.py
; ignore::telegram.utils.deprecate.TelegramDeprecationWarning
markers = dev: If you want to test a specific test, use this
[coverage:run]
branch = True

View file

@ -41,7 +41,7 @@ def print_ver_info() -> None: # skipcq: PY-D0003
git_revision = _git_revision()
print(f'python-telegram-bot {telegram_ver}' + (f' ({git_revision})' if git_revision else ''))
print(f'Bot API {BOT_API_VERSION}')
print(f'certifi {certifi.__version__}') # type: ignore[attr-defined]
print('certifi' + certifi.__version__)
sys_version = sys.version.replace('\n', ' ')
print(f'Python {sys_version}')

View file

@ -3080,6 +3080,7 @@ class Bot(TelegramObject):
api_kwargs: JSONDict = None,
ip_address: str = None,
drop_pending_updates: bool = None,
secret_token: str = None,
) -> bool:
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook.
@ -3087,9 +3088,9 @@ class Bot(TelegramObject):
specified url, containing a JSON-serialized Update. In case of an unsuccessful request,
Telegram will give up after a reasonable amount of attempts.
If you'd like to make sure that the Webhook request comes from Telegram, Telegram
recommends using a secret path in the URL, e.g. https://www.example.com/<token>. Since
nobody else knows your bot's token, you can be pretty sure it's us.
If you'd like to make sure that the Webhook was set by you, you can specify secret data in
the parameter ``secret_token``. If specified, the request will contain a header
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
Note:
The certificate argument should be a file from disk ``open(filename, 'rb')``.
@ -3117,6 +3118,12 @@ class Bot(TelegramObject):
a short period of time.
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
updates.
secret_token (:obj:`str`, optional): A secret token to be sent in a header
``X-Telegram-Bot-Api-Secret-Token`` in every webhook request, 1-256 characters.
Only characters ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
The header is useful to ensure that the request comes from a webhook set by you.
.. versionadded:: 13.13
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).
@ -3157,6 +3164,8 @@ class Bot(TelegramObject):
data['ip_address'] = ip_address
if drop_pending_updates:
data['drop_pending_updates'] = drop_pending_updates
if secret_token is not None:
data["secret_token"] = secret_token
result = self._post('setWebhook', data, timeout=timeout, api_kwargs=api_kwargs)
@ -5914,6 +5923,135 @@ class Bot(TelegramObject):
)
return MenuButton.de_json(result, bot=self) # type: ignore[return-value, arg-type]
@log
def create_invoice_link(
self,
title: str,
description: str,
payload: str,
provider_token: str,
currency: str,
prices: List["LabeledPrice"],
max_tip_amount: int = None,
suggested_tip_amounts: List[int] = None,
provider_data: Union[str, object] = None,
photo_url: str = None,
photo_size: int = None,
photo_width: int = None,
photo_height: int = None,
need_name: bool = None,
need_phone_number: bool = None,
need_email: bool = None,
need_shipping_address: bool = None,
send_phone_number_to_provider: bool = None,
send_email_to_provider: bool = None,
is_flexible: bool = None,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> str:
"""Use this method to create a link for an invoice.
.. versionadded:: 13.13
Args:
title (:obj:`str`): Product name. 1-32 characters.
description (:obj:`str`): Product description. 1-255 characters.
payload (:obj:`str`): Bot-defined invoice payload. 1-128 bytes. This will not be
displayed to the user, use for your internal processes.
provider_token (:obj:`str`): Payments provider token, obtained via
`@BotFather <https://t.me/BotFather>`_.
currency (:obj:`str`): Three-letter ISO 4217 currency code, see `more on currencies
<https://core.telegram.org/bots/payments#supported-currencies>`_.
prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a list
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
bonus, etc.).
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
*smallest* units of the currency (integer, **not** float/double). For example, for
a maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
`currencies.json <https://core.telegram.org/bots/payments/currencies.json>`_, it
shows the number of digits past the decimal point for each currency (2 for the
majority of currencies). Defaults to ``0``.
suggested_tip_amounts (List[:obj:`int`], optional): An array of
suggested amounts of tips in the *smallest* units of the currency (integer, **not**
float/double). At most 4 suggested tip amounts can be specified. The suggested tip
amounts must be positive, passed in a strictly increased order and must not exceed
``max_tip_amount``.
provider_data (:obj:`str` | :obj:`object`, optional): Data about the
invoice, which will be shared with the payment provider. A detailed description of
required fields should be provided by the payment provider. When an object is
passed, it will be encoded as JSON.
photo_url (:obj:`str`, optional): URL of the product photo for the invoice. Can be a
photo of the goods or a marketing image for a service.
photo_size (:obj:`int`, optional): Photo size in bytes.
photo_width (:obj:`int`, optional): Photo width.
photo_height (:obj:`int`, optional): Photo height.
need_name (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's full
name to complete the order.
need_phone_number (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's
phone number to complete the order.
need_email (:obj:`bool`, optional): Pass :obj:`True`, if you require the user's email
address to complete the order.
need_shipping_address (:obj:`bool`, optional): Pass :obj:`True`, if you require the
user's shipping address to complete the order.
send_phone_number_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's
phone number should be sent to provider.
send_email_to_provider (:obj:`bool`, optional): Pass :obj:`True`, if user's email
address should be sent to provider.
is_flexible (:obj:`bool`, optional): Pass :obj:`True`, if the final price depends on
the shipping method.
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).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:class:`str`: On success, the created invoice link is returned.
"""
data: JSONDict = {
"title": title,
"description": description,
"payload": payload,
"provider_token": provider_token,
"currency": currency,
"prices": [p.to_dict() for p in prices],
}
if max_tip_amount is not None:
data["max_tip_amount"] = max_tip_amount
if suggested_tip_amounts is not None:
data["suggested_tip_amounts"] = suggested_tip_amounts
if provider_data is not None:
data["provider_data"] = provider_data
if photo_url is not None:
data["photo_url"] = photo_url
if photo_size is not None:
data["photo_size"] = photo_size
if photo_width is not None:
data["photo_width"] = photo_width
if photo_height is not None:
data["photo_height"] = photo_height
if need_name is not None:
data["need_name"] = need_name
if need_phone_number is not None:
data["need_phone_number"] = need_phone_number
if need_email is not None:
data["need_email"] = need_email
if need_shipping_address is not None:
data["need_shipping_address"] = need_shipping_address
if is_flexible is not None:
data["is_flexible"] = is_flexible
if send_phone_number_to_provider is not None:
data["send_phone_number_to_provider"] = send_phone_number_to_provider
if send_email_to_provider is not None:
data["send_email_to_provider"] = send_email_to_provider
return self._post( # type: ignore[return-value]
"createInvoiceLink",
data,
timeout=timeout,
api_kwargs=api_kwargs,
)
def to_dict(self) -> JSONDict:
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {'id': self.id, 'username': self.username, 'first_name': self.first_name}
@ -6106,3 +6244,5 @@ class Bot(TelegramObject):
"""Alias for :meth:`get_my_default_administrator_rights`"""
setMyDefaultAdministratorRights = set_my_default_administrator_rights
"""Alias for :meth:`set_my_default_administrator_rights`"""
createInvoiceLink = create_invoice_link
"""Alias for :meth:`create_invoice_link`"""

View file

@ -116,6 +116,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`, optional): :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
join_by_request (:obj:`bool`, optional): :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes:
@ -160,7 +170,16 @@ class Chat(TelegramObject):
chats. Returned only in :meth:`telegram.Bot.get_chat`.
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
join_to_send_messages (:obj:`bool`): Optional. :obj:`True`, if users need to join the
supergroup before they can send messages. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
join_by_request (:obj:`bool`): Optional. :obj:`True`, if all users directly joining the
supergroup need to be approved by supergroup administrators. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.13
"""
__slots__ = (
@ -186,6 +205,8 @@ class Chat(TelegramObject):
'message_auto_delete_time',
'has_protected_content',
'has_private_forwards',
'join_to_send_messages',
'join_by_request',
'_id_attrs',
)
@ -226,6 +247,8 @@ class Chat(TelegramObject):
message_auto_delete_time: int = None,
has_private_forwards: bool = None,
has_protected_content: bool = None,
join_to_send_messages: bool = None,
join_by_request: bool = None,
**_kwargs: Any,
):
# Required
@ -254,6 +277,8 @@ class Chat(TelegramObject):
self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id
self.location = location
self.join_to_send_messages = join_to_send_messages
self.join_by_request = join_by_request
self.bot = bot
self._id_attrs = (self.id,)

View file

@ -21,7 +21,7 @@ The following constants were extracted from the
`Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes:
BOT_API_VERSION (:obj:`str`): `6.0`. Telegram Bot API version supported by this
BOT_API_VERSION (:obj:`str`): `6.1`. Telegram Bot API version supported by this
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
.. versionadded:: 13.4
@ -247,7 +247,7 @@ Attributes:
"""
from typing import List
BOT_API_VERSION: str = '6.0'
BOT_API_VERSION: str = '6.1'
MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024
ANONYMOUS_ADMIN_ID: int = 1087968824

View file

@ -1004,6 +1004,38 @@ officedocument.wordprocessingml.document")``.
location = _Location()
"""Messages that contain :class:`telegram.Location`."""
class _UserAttachment(UpdateFilter):
__slots__ = ()
name = "Filters.user_attachment"
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(
update.effective_user.added_to_attachment_menu
)
user_attachment = _UserAttachment()
"""This filter filters *any* message that have a user who added the bot to their
:attr:`attachment menu <telegram.User.added_to_attachment_menu>` as
:attr:`telegram.Update.effective_user`.
.. versionadded:: 13.13
"""
class _UserPremium(UpdateFilter):
__slots__ = ()
name = "Filters.premium_user"
def filter(self, update: Update) -> bool:
return bool(update.effective_user) and bool(update.effective_user.is_premium)
premium_user = _UserPremium()
"""This filter filters *any* message from a
:attr:`Telegram Premium user <telegram.User.is_premium>` as
:attr:`telegram.Update.effective_user`.
.. versionadded:: 13.13
"""
class _Venue(MessageFilter):
__slots__ = ()
name = 'Filters.venue'

View file

@ -463,6 +463,11 @@ class Updater(Generic[CCT, UD, CD, BD]):
application. Else, the webhook will be started on
https://listen:port/url_path. Also calls :meth:`telegram.Bot.set_webhook` as required.
Note:
``telegram.Bot.set_webhook.secret_token`` is not checked by this webhook
implementation. If you want to use this new security parameter, either build your own
webhook server or update your code to version 20.0a2+.
.. versionchanged:: 13.4
:meth:`start_webhook` now *always* calls :meth:`telegram.Bot.set_webhook`, so pass
``webhook_url`` instead of calling ``updater.bot.set_webhook(webhook_url)`` manually.

View file

@ -60,6 +60,10 @@ class Sticker(TelegramObject):
position where the mask should be placed.
file_size (:obj:`int`, optional): File size.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
premium_animation (:class:`telegram.File`, optional): Premium animation for the sticker,
if the sticker is premium.
.. versionadded:: 13.13
**kwargs (obj:`dict`): Arbitrary keyword arguments.
Attributes:
@ -80,6 +84,10 @@ class Sticker(TelegramObject):
mask_position (:class:`telegram.MaskPosition`): Optional. For mask stickers, the position
where the mask should be placed.
file_size (:obj:`int`): Optional. File size.
premium_animation (:class:`telegram.File`): Optional. Premium animation for the sticker,
if the sticker is premium.
.. versionadded:: 13.13
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
"""
@ -97,6 +105,7 @@ class Sticker(TelegramObject):
'height',
'file_unique_id',
'emoji',
'premium_animation',
'_id_attrs',
)
@ -114,6 +123,7 @@ class Sticker(TelegramObject):
set_name: str = None,
mask_position: 'MaskPosition' = None,
bot: 'Bot' = None,
premium_animation: 'File' = None,
**_kwargs: Any,
):
# Required
@ -130,12 +140,17 @@ class Sticker(TelegramObject):
self.set_name = set_name
self.mask_position = mask_position
self.bot = bot
self.premium_animation = premium_animation
self._id_attrs = (self.file_unique_id,)
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: 'Bot') -> Optional['Sticker']:
"""See :meth:`telegram.TelegramObject.de_json`."""
# needs to be here to avoid circular imports
# pylint: disable=import-outside-toplevel
from telegram import File
data = cls._parse_data(data)
if not data:
@ -143,6 +158,7 @@ class Sticker(TelegramObject):
data['thumb'] = PhotoSize.de_json(data.get('thumb'), bot)
data['mask_position'] = MaskPosition.de_json(data.get('mask_position'), bot)
data["premium_animation"] = File.de_json(data.get("premium_animation"), bot)
return cls(bot=bot, **data)

View file

@ -40,7 +40,7 @@ class LoginUrl(TelegramObject):
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
Args:
url (:obj:`str`): An HTTP URL to be opened with user authorization data added to the query
url (:obj:`str`): An HTTPS 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
@ -60,7 +60,7 @@ class LoginUrl(TelegramObject):
for your bot to send messages to the user.
Attributes:
url (:obj:`str`): An HTTP URL to be opened with user authorization data.
url (:obj:`str`): An HTTPS 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.

View file

@ -79,6 +79,13 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
is_premium (:obj:`bool`, optional): :obj:`True`, if this user is a Telegram Premium user.
.. versionadded:: 13.13
added_to_attachment_menu (:obj:`bool`, optional): :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 13.13
Attributes:
id (:obj:`int`): Unique identifier for this user or bot.
@ -94,6 +101,14 @@ class User(TelegramObject):
supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
is_premium (:obj:`bool`): Optional. :obj:`True`, if this user is a Telegram
Premium user.
.. versionadded:: 13.13
added_to_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if this user added
the bot to the attachment menu.
.. versionadded:: 13.13
"""
@ -108,6 +123,8 @@ class User(TelegramObject):
'id',
'bot',
'language_code',
'is_premium',
'added_to_attachment_menu',
'_id_attrs',
)
@ -123,6 +140,8 @@ class User(TelegramObject):
can_read_all_group_messages: bool = None,
supports_inline_queries: bool = None,
bot: 'Bot' = None,
is_premium: bool = None,
added_to_attachment_menu: bool = None,
**_kwargs: Any,
):
# Required
@ -136,6 +155,8 @@ class User(TelegramObject):
self.can_join_groups = can_join_groups
self.can_read_all_group_messages = can_read_all_group_messages
self.supports_inline_queries = supports_inline_queries
self.is_premium = is_premium
self.added_to_attachment_menu = added_to_attachment_menu
self.bot = bot
self._id_attrs = (self.id,)

View file

@ -1410,6 +1410,32 @@ class TestBot:
assert bot.set_webhook(drop_pending_updates=drop_pending_updates)
assert bot.delete_webhook(drop_pending_updates=drop_pending_updates)
def test_set_webhook_params(self, bot, monkeypatch):
# actually making calls to TG is done in
# test_set_webhook_get_webhook_info_and_delete_webhook. Sadly secret_token can't be tested
# there so we have this function \o/
def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["url"] == "example.com"
and kwargs["max_connections"] == 7
and kwargs["allowed_updates"] == ["messages"]
and kwargs["ip_address"] == "127.0.0.1"
and kwargs["drop_pending_updates"]
and kwargs["secret_token"] == "SoSecretToken"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert bot.set_webhook(
"example.com",
max_connections=7,
allowed_updates=["messages"],
ip_address="127.0.0.1",
drop_pending_updates=True,
secret_token="SoSecretToken",
)
@flaky(3, 1)
def test_leave_chat(self, bot):
with pytest.raises(BadRequest, match='Chat not found'):

View file

@ -43,6 +43,8 @@ def chat(bot):
location=TestChat.location,
has_private_forwards=True,
has_protected_content=True,
join_to_send_messages=True,
join_by_request=True,
)
@ -66,6 +68,8 @@ class TestChat:
location = ChatLocation(Location(123, 456), 'Barbie World')
has_protected_content = True
has_private_forwards = True
join_to_send_messages = True
join_by_request = True
def test_slot_behaviour(self, chat, recwarn, mro_slots):
for attr in chat.__slots__:
@ -92,6 +96,8 @@ class TestChat:
'has_private_forwards': self.has_private_forwards,
'linked_chat_id': self.linked_chat_id,
'location': self.location.to_dict(),
'join_to_send_messages': self.join_to_send_messages,
'join_by_request': self.join_by_request,
}
chat = Chat.de_json(json_dict, bot)
@ -111,6 +117,8 @@ class TestChat:
assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location
assert chat.location.address == self.location.address
assert chat.join_to_send_messages == self.join_to_send_messages
assert chat.join_by_request == self.join_by_request
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@ -129,6 +137,8 @@ class TestChat:
assert chat_dict['has_protected_content'] == chat.has_protected_content
assert chat_dict['linked_chat_id'] == chat.linked_chat_id
assert chat_dict['location'] == chat.location.to_dict()
assert chat_dict["join_to_send_messages"] == chat.join_to_send_messages
assert chat_dict["join_by_request"] == chat.join_by_request
def test_link(self, chat):
assert chat.link == f'https://t.me/{chat.username}'

View file

@ -1189,6 +1189,19 @@ class TestFilters:
with pytest.raises(RuntimeError, match='Cannot set name'):
f.name = 'foo'
def test_filters_user_attributes(self, update):
assert not Filters.user_attachment(update)
assert not Filters.premium_user(update)
update.message.from_user.added_to_attachment_menu = True
assert Filters.user_attachment(update)
assert not Filters.premium_user(update)
update.message.from_user.is_premium = True
assert Filters.user_attachment(update)
assert Filters.premium_user(update)
update.message.from_user.added_to_attachment_menu = False
assert not Filters.user_attachment(update)
assert Filters.premium_user(update)
def test_filters_chat_init(self):
with pytest.raises(RuntimeError, match='in conjunction with'):
Filters.chat(chat_id=1, username='chat')

View file

@ -100,8 +100,19 @@ class TestInvoice:
assert message.invoice.title == self.title
assert message.invoice.total_amount == self.total_amount
link = bot.create_invoice_link(
title=self.title,
description=self.description,
payload=self.payload,
provider_token=provider_token,
currency=self.currency,
prices=self.prices,
)
assert isinstance(link, str)
assert link != ""
@flaky(3, 1)
def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch):
def test_send_all_args_send_invoice(self, bot, chat_id, provider_token, monkeypatch):
message = bot.send_invoice(
chat_id,
self.title,
@ -195,6 +206,56 @@ class TestInvoice:
protect_content=True,
)
def test_send_all_args_create_invoice_link(self, bot, chat_id, provider_token, monkeypatch):
def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["title"] == "title"
and kwargs["description"] == "description"
and kwargs["payload"] == "payload"
and kwargs["provider_token"] == "provider_token"
and kwargs["currency"] == "currency"
and kwargs["prices"] == [p.to_dict() for p in self.prices]
and kwargs["max_tip_amount"] == "max_tip_amount"
and kwargs["suggested_tip_amounts"] == "suggested_tip_amounts"
and kwargs["provider_data"] == "provider_data"
and kwargs["photo_url"] == "photo_url"
and kwargs["photo_size"] == "photo_size"
and kwargs["photo_width"] == "photo_width"
and kwargs["photo_height"] == "photo_height"
and kwargs["need_name"] == "need_name"
and kwargs["need_phone_number"] == "need_phone_number"
and kwargs["need_email"] == "need_email"
and kwargs["need_shipping_address"] == "need_shipping_address"
and kwargs["send_phone_number_to_provider"] == "send_phone_number_to_provider"
and kwargs["send_email_to_provider"] == "send_email_to_provider"
and kwargs["is_flexible"] == "is_flexible"
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert bot.create_invoice_link(
title="title",
description="description",
payload="payload",
provider_token="provider_token",
currency="currency",
prices=self.prices,
max_tip_amount="max_tip_amount",
suggested_tip_amounts="suggested_tip_amounts",
provider_data="provider_data",
photo_url="photo_url",
photo_size="photo_size",
photo_width="photo_width",
photo_height="photo_height",
need_name="need_name",
need_phone_number="need_phone_number",
need_email="need_email",
need_shipping_address="need_shipping_address",
send_phone_number_to_provider="send_phone_number_to_provider",
send_email_to_provider="send_email_to_provider",
is_flexible="is_flexible",
)
def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
def test(url, data, **kwargs):
# depends on whether we're using ujson

View file

@ -23,7 +23,7 @@ from time import sleep
import pytest
from flaky import flaky
from telegram import Sticker, PhotoSize, TelegramError, StickerSet, Audio, MaskPosition, Bot
from telegram import Sticker, PhotoSize, TelegramError, StickerSet, Audio, MaskPosition, Bot, File
from telegram.error import BadRequest
from tests.conftest import check_shortcut_call, check_shortcut_signature, check_defaults_handling
@ -87,6 +87,8 @@ class TestSticker:
sticker_file_id = '5a3128a4d2a04750b5b58397f3b5e812'
sticker_file_unique_id = 'adc3145fd2e84d95b64d68eaa22aa33e'
premium_animation = File("this_is_an_id", "this_is_an_unique_id")
def test_slot_behaviour(self, sticker, mro_slots, recwarn):
for attr in sticker.__slots__:
assert getattr(sticker, attr, 'err') != 'err', f"got extra slot '{attr}'"
@ -117,6 +119,8 @@ class TestSticker:
assert sticker.thumb.width == self.thumb_width
assert sticker.thumb.height == self.thumb_height
assert sticker.thumb.file_size == self.thumb_file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert sticker.premium_animation == self.premium_animation
@flaky(3, 1)
def test_send_all_args(self, bot, chat_id, sticker_file, sticker):
@ -134,6 +138,8 @@ class TestSticker:
assert message.sticker.is_animated == sticker.is_animated
assert message.sticker.is_video == sticker.is_video
assert message.sticker.file_size == sticker.file_size
# we need to be a premium TG user to send a premium sticker, so the below is not tested
# assert message.sticker.premium_animation == sticker.premium_animation
assert isinstance(message.sticker.thumb, PhotoSize)
assert isinstance(message.sticker.thumb.file_id, str)
@ -207,6 +213,7 @@ class TestSticker:
'thumb': sticker.thumb.to_dict(),
'emoji': self.emoji,
'file_size': self.file_size,
'premium_animation': self.premium_animation.to_dict(),
}
json_sticker = Sticker.de_json(json_dict, bot)
@ -219,6 +226,7 @@ class TestSticker:
assert json_sticker.emoji == self.emoji
assert json_sticker.file_size == self.file_size
assert json_sticker.thumb == sticker.thumb
assert json_sticker.premium_animation == self.premium_animation
def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
def test(url, data, **kwargs):
@ -304,6 +312,24 @@ class TestSticker:
with pytest.raises(TypeError):
bot.send_sticker(chat_id)
@flaky(3, 1)
def test_premium_animation(self, bot):
# testing animation sucks a bit since we can't create a premium sticker. What we can do is
# get a sticker set which includes a premium sticker and check that specific one.
premium_sticker_set = bot.get_sticker_set("Flame")
# the first one to appear here is a sticker with unique file id of AQADOBwAAifPOElr
# this could change in the future ofc.
premium_sticker = premium_sticker_set.stickers[20]
assert premium_sticker.premium_animation.file_unique_id == "AQADOBwAAifPOElr"
assert isinstance(premium_sticker.premium_animation.file_id, str)
assert premium_sticker.premium_animation.file_id != ""
premium_sticker_dict = {
"file_unique_id": "AQADOBwAAifPOElr",
"file_id": premium_sticker.premium_animation.file_id,
"file_size": premium_sticker.premium_animation.file_size,
}
assert premium_sticker.premium_animation.to_dict() == premium_sticker_dict
def test_equality(self, sticker):
a = Sticker(
sticker.file_id,

View file

@ -35,6 +35,8 @@ def json_dict():
'can_join_groups': TestUser.can_join_groups,
'can_read_all_group_messages': TestUser.can_read_all_group_messages,
'supports_inline_queries': TestUser.supports_inline_queries,
'is_premium': TestUser.is_premium,
'added_to_attachment_menu': TestUser.added_to_attachment_menu,
}
@ -51,6 +53,8 @@ def user(bot):
can_read_all_group_messages=TestUser.can_read_all_group_messages,
supports_inline_queries=TestUser.supports_inline_queries,
bot=bot,
is_premium=TestUser.is_premium,
added_to_attachment_menu=TestUser.added_to_attachment_menu,
)
@ -64,6 +68,8 @@ class TestUser:
can_join_groups = True
can_read_all_group_messages = True
supports_inline_queries = False
is_premium = True
added_to_attachment_menu = False
def test_slot_behaviour(self, user, mro_slots, recwarn):
for attr in user.__slots__:
@ -85,6 +91,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username(self, json_dict, bot):
del json_dict['username']
@ -100,6 +108,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_de_json_without_username_and_last_name(self, json_dict, bot):
del json_dict['username']
@ -116,6 +126,8 @@ class TestUser:
assert user.can_join_groups == self.can_join_groups
assert user.can_read_all_group_messages == self.can_read_all_group_messages
assert user.supports_inline_queries == self.supports_inline_queries
assert user.is_premium == self.is_premium
assert user.added_to_attachment_menu == self.added_to_attachment_menu
def test_name(self, user):
assert user.name == '@username'