mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2024-12-29 15:49:02 +01:00
API 6.1 (#3112)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com> Co-authored-by: Bibo-Joshi <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
parent
01d643913e
commit
08e223ba90
23 changed files with 652 additions and 40 deletions
|
@ -14,7 +14,7 @@
|
||||||
:target: https://pypi.org/project/python-telegram-bot/
|
:target: https://pypi.org/project/python-telegram-bot/
|
||||||
:alt: Supported Python versions
|
: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
|
:target: https://core.telegram.org/bots/api-changelog
|
||||||
:alt: Supported Bot API versions
|
:alt: Supported Bot API versions
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
|
||||||
Telegram API support
|
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
|
Installing
|
||||||
==========
|
==========
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
:target: https://pypi.org/project/python-telegram-bot-raw/
|
:target: https://pypi.org/project/python-telegram-bot-raw/
|
||||||
:alt: Supported Python versions
|
: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
|
:target: https://core.telegram.org/bots/api-changelog
|
||||||
:alt: Supported Bot API versions
|
:alt: Supported Bot API versions
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
|
||||||
Telegram API support
|
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
|
Installing
|
||||||
==========
|
==========
|
||||||
|
|
|
@ -263,6 +263,8 @@
|
||||||
:align: left
|
:align: left
|
||||||
:widths: 1 4
|
:widths: 1 4
|
||||||
|
|
||||||
|
* - :meth:`~telegram.Bot.create_invoice_link`
|
||||||
|
- Used to generate an HTTP link for an invoice
|
||||||
* - :meth:`~telegram.Bot.close`
|
* - :meth:`~telegram.Bot.close`
|
||||||
- Used for closing server instance when switching to another local server
|
- Used for closing server instance when switching to another local server
|
||||||
* - :meth:`~telegram.Bot.log_out`
|
* - :meth:`~telegram.Bot.log_out`
|
||||||
|
|
192
telegram/_bot.py
192
telegram/_bot.py
|
@ -3795,6 +3795,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
allowed_updates: List[str] = None,
|
allowed_updates: List[str] = None,
|
||||||
ip_address: str = None,
|
ip_address: str = None,
|
||||||
drop_pending_updates: bool = None,
|
drop_pending_updates: bool = None,
|
||||||
|
secret_token: str = None,
|
||||||
*,
|
*,
|
||||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
|
@ -3808,9 +3809,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
specified url, containing An Update. In case of an unsuccessful request,
|
specified url, containing An Update. In case of an unsuccessful request,
|
||||||
Telegram will give up after a reasonable amount of attempts.
|
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
|
If you'd like to make sure that the Webhook was set by you, you can specify secret data in
|
||||||
recommends using a secret path in the URL, e.g. https://www.example.com/<token>. Since
|
the parameter :paramref:`secret_token`. If specified, the request will contain a header
|
||||||
nobody else knows your bot's token, you can be pretty sure it's them.
|
``X-Telegram-Bot-Api-Secret-Token`` with the secret token as content.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
The certificate argument should be a file from disk ``open(filename, 'rb')``.
|
The certificate argument should be a file from disk ``open(filename, 'rb')``.
|
||||||
|
@ -3839,6 +3840,14 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
a short period of time.
|
a short period of time.
|
||||||
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
|
drop_pending_updates (:obj:`bool`, optional): Pass :obj:`True` to drop all pending
|
||||||
updates.
|
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,
|
||||||
|
:tg-const:`telegram.constants.WebhookLimit.MIN_SECRET_TOKEN_LENGTH`-
|
||||||
|
:tg-const:`telegram.constants.WebhookLimit.MAX_SECRET_TOKEN_LENGTH` 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:: 20.0
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||||
|
@ -3889,6 +3898,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
data["ip_address"] = ip_address
|
data["ip_address"] = ip_address
|
||||||
if drop_pending_updates:
|
if drop_pending_updates:
|
||||||
data["drop_pending_updates"] = drop_pending_updates
|
data["drop_pending_updates"] = drop_pending_updates
|
||||||
|
if secret_token is not None:
|
||||||
|
data["secret_token"] = secret_token
|
||||||
|
|
||||||
result = await self._post(
|
result = await self._post(
|
||||||
"setWebhook",
|
"setWebhook",
|
||||||
|
@ -4593,26 +4604,32 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
Args:
|
Args:
|
||||||
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
|
||||||
of the target channel (in the format ``@channelusername``).
|
of the target channel (in the format ``@channelusername``).
|
||||||
title (:obj:`str`): Product name, 1-32 characters.
|
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
|
||||||
description (:obj:`str`): Product description, 1-255 characters.
|
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
|
||||||
payload (:obj:`str`): Bot-defined invoice payload, 1-128 bytes. This will not be
|
description (:obj:`str`): Product description.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
|
||||||
|
payload (:obj:`str`): Bot-defined invoice payload.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be
|
||||||
displayed to the user, use for your internal processes.
|
displayed to the user, use for your internal processes.
|
||||||
provider_token (:obj:`str`): Payments provider token, obtained via
|
provider_token (:obj:`str`): Payments provider token, obtained via
|
||||||
`@BotFather <https://t.me/BotFather>`_.
|
`@BotFather <https://t.me/BotFather>`_.
|
||||||
currency (:obj:`str`): Three-letter ISO 4217 currency code.
|
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
|
prices (List[:class:`telegram.LabeledPrice`)]: Price breakdown, a list
|
||||||
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
|
of components (e.g. product price, tax, discount, delivery cost, delivery tax,
|
||||||
bonus, etc.).
|
bonus, etc.).
|
||||||
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
|
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
|
*smallest* units of the currency (integer, **not** float/double). For example, for
|
||||||
maximum tip of US$ 1.45 pass ``max_tip_amount = 145``. See the exp parameter in
|
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
|
`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
|
shows the number of digits past the decimal point for each currency (2 for the
|
||||||
majority of currencies). Defaults to ``0``.
|
majority of currencies). Defaults to ``0``.
|
||||||
|
|
||||||
.. versionadded:: 13.5
|
.. versionadded:: 13.5
|
||||||
suggested_tip_amounts (List[:obj:`int`], optional): An array of
|
suggested_tip_amounts (List[:obj:`int`], optional): An array of
|
||||||
suggested amounts of tips in the smallest units of the currency (integer, not
|
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
|
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
|
amounts must be positive, passed in a strictly increased order and must not exceed
|
||||||
``max_tip_amount``.
|
``max_tip_amount``.
|
||||||
|
@ -7693,6 +7710,159 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
)
|
)
|
||||||
return MenuButton.de_json(result, bot=self) # type: ignore[return-value, arg-type]
|
return MenuButton.de_json(result, bot=self) # type: ignore[return-value, arg-type]
|
||||||
|
|
||||||
|
@_log
|
||||||
|
async 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,
|
||||||
|
*,
|
||||||
|
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
|
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
|
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
|
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||||
|
api_kwargs: JSONDict = None,
|
||||||
|
) -> str:
|
||||||
|
"""Use this method to create a link for an invoice.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
|
||||||
|
Args:
|
||||||
|
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
|
||||||
|
description (:obj:`str`): Product description.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
|
||||||
|
payload (:obj:`str`): Bot-defined invoice payload.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` 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
|
||||||
|
:paramref:`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.
|
||||||
|
|
||||||
|
Keyword Args:
|
||||||
|
read_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||||
|
:paramref:`telegram.request.BaseRequest.post.read_timeout`. Defaults to
|
||||||
|
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
|
||||||
|
write_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||||
|
:paramref:`telegram.request.BaseRequest.post.write_timeout`. Defaults to
|
||||||
|
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
|
||||||
|
connect_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||||
|
:paramref:`telegram.request.BaseRequest.post.connect_timeout`. Defaults to
|
||||||
|
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
|
||||||
|
pool_timeout (:obj:`float` | :obj:`None`, optional): Value to pass to
|
||||||
|
:paramref:`telegram.request.BaseRequest.post.pool_timeout`. Defaults to
|
||||||
|
:attr:`~telegram.request.BaseRequest.DEFAULT_NONE`.
|
||||||
|
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": 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 await self._post( # type: ignore[return-value]
|
||||||
|
"createInvoiceLink",
|
||||||
|
data,
|
||||||
|
read_timeout=read_timeout,
|
||||||
|
write_timeout=write_timeout,
|
||||||
|
connect_timeout=connect_timeout,
|
||||||
|
pool_timeout=pool_timeout,
|
||||||
|
api_kwargs=api_kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
def to_dict(self) -> JSONDict:
|
def to_dict(self) -> JSONDict:
|
||||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||||
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
|
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
|
||||||
|
@ -7881,3 +8051,5 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
|
||||||
"""Alias for :meth:`get_my_default_administrator_rights`"""
|
"""Alias for :meth:`get_my_default_administrator_rights`"""
|
||||||
setMyDefaultAdministratorRights = set_my_default_administrator_rights
|
setMyDefaultAdministratorRights = set_my_default_administrator_rights
|
||||||
"""Alias for :meth:`set_my_default_administrator_rights`"""
|
"""Alias for :meth:`set_my_default_administrator_rights`"""
|
||||||
|
createInvoiceLink = create_invoice_link
|
||||||
|
"""Alias for :meth:`create_invoice_link`"""
|
||||||
|
|
|
@ -124,6 +124,16 @@ class Chat(TelegramObject):
|
||||||
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||||
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
|
location (:class:`telegram.ChatLocation`, optional): For supergroups, the location to which
|
||||||
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
|
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:: 20.0
|
||||||
|
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:: 20.0
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -168,6 +178,16 @@ class Chat(TelegramObject):
|
||||||
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
chats. Returned only in :meth:`telegram.Bot.get_chat`.
|
||||||
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
|
location (:class:`telegram.ChatLocation`): Optional. For supergroups, the location to which
|
||||||
the supergroup is connected. Returned only in :meth:`telegram.Bot.get_chat`.
|
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:: 20.0
|
||||||
|
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:: 20.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -193,6 +213,8 @@ class Chat(TelegramObject):
|
||||||
"message_auto_delete_time",
|
"message_auto_delete_time",
|
||||||
"has_protected_content",
|
"has_protected_content",
|
||||||
"has_private_forwards",
|
"has_private_forwards",
|
||||||
|
"join_to_send_messages",
|
||||||
|
"join_by_request",
|
||||||
)
|
)
|
||||||
|
|
||||||
SENDER: ClassVar[str] = constants.ChatType.SENDER
|
SENDER: ClassVar[str] = constants.ChatType.SENDER
|
||||||
|
@ -232,6 +254,8 @@ class Chat(TelegramObject):
|
||||||
message_auto_delete_time: int = None,
|
message_auto_delete_time: int = None,
|
||||||
has_private_forwards: bool = None,
|
has_private_forwards: bool = None,
|
||||||
has_protected_content: bool = None,
|
has_protected_content: bool = None,
|
||||||
|
join_to_send_messages: bool = None,
|
||||||
|
join_by_request: bool = None,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
):
|
):
|
||||||
# Required
|
# Required
|
||||||
|
@ -260,6 +284,8 @@ class Chat(TelegramObject):
|
||||||
self.can_set_sticker_set = can_set_sticker_set
|
self.can_set_sticker_set = can_set_sticker_set
|
||||||
self.linked_chat_id = linked_chat_id
|
self.linked_chat_id = linked_chat_id
|
||||||
self.location = location
|
self.location = location
|
||||||
|
self.join_to_send_messages = join_to_send_messages
|
||||||
|
self.join_by_request = join_by_request
|
||||||
|
|
||||||
self.set_bot(bot)
|
self.set_bot(bot)
|
||||||
self._id_attrs = (self.id,)
|
self._id_attrs = (self.id,)
|
||||||
|
|
|
@ -22,6 +22,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, List, Optional
|
||||||
|
|
||||||
from telegram import constants
|
from telegram import constants
|
||||||
from telegram._files._basethumbedmedium import _BaseThumbedMedium
|
from telegram._files._basethumbedmedium import _BaseThumbedMedium
|
||||||
|
from telegram._files.file import File
|
||||||
from telegram._files.photosize import PhotoSize
|
from telegram._files.photosize import PhotoSize
|
||||||
from telegram._telegramobject import TelegramObject
|
from telegram._telegramobject import TelegramObject
|
||||||
from telegram._utils.types import JSONDict
|
from telegram._utils.types import JSONDict
|
||||||
|
@ -62,6 +63,10 @@ class Sticker(_BaseThumbedMedium):
|
||||||
position where the mask should be placed.
|
position where the mask should be placed.
|
||||||
file_size (:obj:`int`, optional): File size in bytes.
|
file_size (:obj:`int`, optional): File size in bytes.
|
||||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
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:: 20.0
|
||||||
_kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
_kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
@ -83,6 +88,10 @@ class Sticker(_BaseThumbedMedium):
|
||||||
where the mask should be placed.
|
where the mask should be placed.
|
||||||
file_size (:obj:`int`): Optional. File size in bytes.
|
file_size (:obj:`int`): Optional. File size in bytes.
|
||||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
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:: 20.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -94,6 +103,7 @@ class Sticker(_BaseThumbedMedium):
|
||||||
"mask_position",
|
"mask_position",
|
||||||
"set_name",
|
"set_name",
|
||||||
"width",
|
"width",
|
||||||
|
"premium_animation",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -110,6 +120,7 @@ class Sticker(_BaseThumbedMedium):
|
||||||
set_name: str = None,
|
set_name: str = None,
|
||||||
mask_position: "MaskPosition" = None,
|
mask_position: "MaskPosition" = None,
|
||||||
bot: "Bot" = None,
|
bot: "Bot" = None,
|
||||||
|
premium_animation: "File" = None,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
):
|
):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
|
@ -128,6 +139,7 @@ class Sticker(_BaseThumbedMedium):
|
||||||
self.emoji = emoji
|
self.emoji = emoji
|
||||||
self.set_name = set_name
|
self.set_name = set_name
|
||||||
self.mask_position = mask_position
|
self.mask_position = mask_position
|
||||||
|
self.premium_animation = premium_animation
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Sticker"]:
|
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Sticker"]:
|
||||||
|
@ -139,6 +151,7 @@ class Sticker(_BaseThumbedMedium):
|
||||||
|
|
||||||
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
|
data["thumb"] = PhotoSize.de_json(data.get("thumb"), bot)
|
||||||
data["mask_position"] = MaskPosition.de_json(data.get("mask_position"), 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)
|
return cls(bot=bot, **data)
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,14 @@ class InputInvoiceMessageContent(InputMessageContent):
|
||||||
.. versionadded:: 13.5
|
.. versionadded:: 13.5
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
title (:obj:`str`): Product name, 1-32 characters
|
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
|
||||||
description (:obj:`str`): Product description, 1-255 characters
|
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
|
||||||
payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed
|
description (:obj:`str`): Product description.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
|
||||||
|
payload (:obj:`str`): Bot-defined invoice payload.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
|
||||||
to the user, use for your internal processes.
|
to the user, use for your internal processes.
|
||||||
provider_token (:obj:`str`): Payment provider token, obtained via
|
provider_token (:obj:`str`): Payment provider token, obtained via
|
||||||
`@Botfather <https://t.me/Botfather>`_.
|
`@Botfather <https://t.me/Botfather>`_.
|
||||||
|
@ -50,15 +55,15 @@ class InputInvoiceMessageContent(InputMessageContent):
|
||||||
prices (List[:class:`telegram.LabeledPrice`]): Price breakdown, a list of
|
prices (List[:class:`telegram.LabeledPrice`]): Price breakdown, a list of
|
||||||
components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus,
|
components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus,
|
||||||
etc.)
|
etc.)
|
||||||
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the smallest
|
max_tip_amount (:obj:`int`, optional): The maximum accepted amount for tips in the
|
||||||
units of the currency (integer, not float/double). For example, for a maximum tip of
|
*smallest* units of the currency (integer, **not** float/double). For example, for a
|
||||||
US$ 1.45 pass ``max_tip_amount = 145``. See the ``exp`` parameter in
|
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
|
`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
|
shows the number of digits past the decimal point for each currency (2 for the majority
|
||||||
of currencies). Defaults to ``0``.
|
of currencies). Defaults to ``0``.
|
||||||
suggested_tip_amounts (List[:obj:`int`], optional): An array of suggested
|
suggested_tip_amounts (List[:obj:`int`], optional): An array of suggested
|
||||||
amounts of tip in the smallest units of the currency (integer, not float/double). At
|
amounts of tip in the *smallest* units of the currency (integer, **not** float/double).
|
||||||
most 4 suggested tip amounts can be specified. The suggested tip amounts must be
|
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
|
positive, passed in a strictly increased order and must not exceed
|
||||||
:attr:`max_tip_amount`.
|
:attr:`max_tip_amount`.
|
||||||
provider_data (:obj:`str`, optional): An object for data about the invoice,
|
provider_data (:obj:`str`, optional): An object for data about the invoice,
|
||||||
|
@ -87,9 +92,14 @@ class InputInvoiceMessageContent(InputMessageContent):
|
||||||
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
**kwargs (:obj:`dict`): Arbitrary keyword arguments.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
title (:obj:`str`): Product name, 1-32 characters
|
title (:obj:`str`): Product name. :tg-const:`telegram.Invoice.MIN_TITLE_LENGTH`-
|
||||||
description (:obj:`str`): Product description, 1-255 characters
|
:tg-const:`telegram.Invoice.MAX_TITLE_LENGTH` characters.
|
||||||
payload (:obj:`str`):Bot-defined invoice payload, 1-128 bytes. This will not be displayed
|
description (:obj:`str`): Product description.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_DESCRIPTION_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_DESCRIPTION_LENGTH` characters.
|
||||||
|
payload (:obj:`str`): Bot-defined invoice payload.
|
||||||
|
:tg-const:`telegram.Invoice.MIN_PAYLOAD_LENGTH`-
|
||||||
|
:tg-const:`telegram.Invoice.MAX_PAYLOAD_LENGTH` bytes. This will not be displayed
|
||||||
to the user, use for your internal processes.
|
to the user, use for your internal processes.
|
||||||
provider_token (:obj:`str`): Payment provider token, obtained via
|
provider_token (:obj:`str`): Payment provider token, obtained via
|
||||||
`@Botfather <https://t.me/Botfather>`_.
|
`@Botfather <https://t.me/Botfather>`_.
|
||||||
|
|
|
@ -39,7 +39,7 @@ class LoginUrl(TelegramObject):
|
||||||
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
|
`Checking authorization <https://core.telegram.org/widgets/login#checking-authorization>`_
|
||||||
|
|
||||||
Args:
|
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,
|
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 original URL without information about the user will be opened. The data added is
|
||||||
the same as described in
|
the same as described in
|
||||||
|
@ -59,7 +59,7 @@ class LoginUrl(TelegramObject):
|
||||||
for your bot to send messages to the user.
|
for your bot to send messages to the user.
|
||||||
|
|
||||||
Attributes:
|
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.
|
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
|
bot_username (:obj:`str`): Optional. Username of a bot, which will be used for user
|
||||||
authorization.
|
authorization.
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
# 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 Invoice."""
|
"""This module contains an object that represents a Telegram Invoice."""
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any, ClassVar
|
||||||
|
|
||||||
|
from telegram import constants
|
||||||
from telegram._telegramobject import TelegramObject
|
from telegram._telegramobject import TelegramObject
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,3 +84,34 @@ class Invoice(TelegramObject):
|
||||||
self.currency,
|
self.currency,
|
||||||
self.total_amount,
|
self.total_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
MIN_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_TITLE_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MIN_TITLE_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
MAX_TITLE_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_TITLE_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MAX_TITLE_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
MIN_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MIN_DESCRIPTION_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
MAX_DESCRIPTION_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MAX_DESCRIPTION_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
MIN_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MIN_PAYLOAD_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MIN_PAYLOAD_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
MAX_PAYLOAD_LENGTH: ClassVar[int] = constants.InvoiceLimit.MAX_PAYLOAD_LENGTH
|
||||||
|
""":const:`telegram.constants.InvoiceLimit.MAX_PAYLOAD_LENGTH`
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
|
|
@ -83,6 +83,13 @@ class User(TelegramObject):
|
||||||
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
|
supports_inline_queries (:obj:`str`, optional): :obj:`True`, if the bot supports inline
|
||||||
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
|
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||||
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
|
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:: 20.0
|
||||||
|
added_to_attachment_menu (:obj:`bool`, optional): :obj:`True`, if this user added
|
||||||
|
the bot to the attachment menu.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
id (:obj:`int`): Unique identifier for this user or bot.
|
id (:obj:`int`): Unique identifier for this user or bot.
|
||||||
|
@ -98,7 +105,14 @@ class User(TelegramObject):
|
||||||
supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline
|
supports_inline_queries (:obj:`str`): Optional. :obj:`True`, if the bot supports inline
|
||||||
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
|
queries. Returned only in :attr:`telegram.Bot.get_me` requests.
|
||||||
bot (:class:`telegram.Bot`): Optional. The Bot to use for instance methods.
|
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:: 20.0
|
||||||
|
added_to_attachment_menu (:obj:`bool`): Optional. :obj:`True`, if this user added
|
||||||
|
the bot to the attachment menu.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
|
@ -111,6 +125,8 @@ class User(TelegramObject):
|
||||||
"supports_inline_queries",
|
"supports_inline_queries",
|
||||||
"id",
|
"id",
|
||||||
"language_code",
|
"language_code",
|
||||||
|
"is_premium",
|
||||||
|
"added_to_attachment_menu",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -125,6 +141,8 @@ class User(TelegramObject):
|
||||||
can_read_all_group_messages: bool = None,
|
can_read_all_group_messages: bool = None,
|
||||||
supports_inline_queries: bool = None,
|
supports_inline_queries: bool = None,
|
||||||
bot: "Bot" = None,
|
bot: "Bot" = None,
|
||||||
|
is_premium: bool = None,
|
||||||
|
added_to_attachment_menu: bool = None,
|
||||||
**_kwargs: Any,
|
**_kwargs: Any,
|
||||||
):
|
):
|
||||||
# Required
|
# Required
|
||||||
|
@ -138,6 +156,8 @@ class User(TelegramObject):
|
||||||
self.can_join_groups = can_join_groups
|
self.can_join_groups = can_join_groups
|
||||||
self.can_read_all_group_messages = can_read_all_group_messages
|
self.can_read_all_group_messages = can_read_all_group_messages
|
||||||
self.supports_inline_queries = supports_inline_queries
|
self.supports_inline_queries = supports_inline_queries
|
||||||
|
self.is_premium = is_premium
|
||||||
|
self.added_to_attachment_menu = added_to_attachment_menu
|
||||||
self.set_bot(bot)
|
self.set_bot(bot)
|
||||||
|
|
||||||
self._id_attrs = (self.id,)
|
self._id_attrs = (self.id,)
|
||||||
|
|
|
@ -45,6 +45,7 @@ __all__ = [
|
||||||
"InlineQueryLimit",
|
"InlineQueryLimit",
|
||||||
"InlineQueryResultType",
|
"InlineQueryResultType",
|
||||||
"InputMediaType",
|
"InputMediaType",
|
||||||
|
"InvoiceLimit",
|
||||||
"LocationLimit",
|
"LocationLimit",
|
||||||
"MaskPosition",
|
"MaskPosition",
|
||||||
"MenuButtonType",
|
"MenuButtonType",
|
||||||
|
@ -56,6 +57,7 @@ __all__ = [
|
||||||
"PollLimit",
|
"PollLimit",
|
||||||
"PollType",
|
"PollType",
|
||||||
"SUPPORTED_WEBHOOK_PORTS",
|
"SUPPORTED_WEBHOOK_PORTS",
|
||||||
|
"WebhookLimit",
|
||||||
"UpdateType",
|
"UpdateType",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ class _BotAPIVersion(NamedTuple):
|
||||||
#: :data:`telegram.__bot_api_version_info__`.
|
#: :data:`telegram.__bot_api_version_info__`.
|
||||||
#:
|
#:
|
||||||
#: .. versionadded:: 20.0
|
#: .. versionadded:: 20.0
|
||||||
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=0)
|
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=1)
|
||||||
#: :obj:`str`: Telegram Bot API
|
#: :obj:`str`: Telegram Bot API
|
||||||
#: version supported by this version of `python-telegram-bot`. Also available as
|
#: version supported by this version of `python-telegram-bot`. Also available as
|
||||||
#: :data:`telegram.__bot_api_version__`.
|
#: :data:`telegram.__bot_api_version__`.
|
||||||
|
@ -809,3 +811,41 @@ class UpdateType(StringEnum):
|
||||||
""":obj:`str`: Updates with :attr:`telegram.Update.chat_member`."""
|
""":obj:`str`: Updates with :attr:`telegram.Update.chat_member`."""
|
||||||
CHAT_JOIN_REQUEST = "chat_join_request"
|
CHAT_JOIN_REQUEST = "chat_join_request"
|
||||||
""":obj:`str`: Updates with :attr:`telegram.Update.chat_join_request`."""
|
""":obj:`str`: Updates with :attr:`telegram.Update.chat_join_request`."""
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceLimit(IntEnum):
|
||||||
|
"""This enum contains limitations for :meth:`telegram.Bot.create_invoice_link`. The enum
|
||||||
|
members of this enumeration are instances of :class:`int` and can be treated as such.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
MIN_TITLE_LENGTH = 1
|
||||||
|
""":obj:`int`: Minimum number of characters of the invoice title."""
|
||||||
|
MAX_TITLE_LENGTH = 32
|
||||||
|
""":obj:`int`: Maximum number of characters of the invoice title."""
|
||||||
|
MIN_DESCRIPTION_LENGTH = 1
|
||||||
|
""":obj:`int`: Minimum number of characters of the invoice description."""
|
||||||
|
MAX_DESCRIPTION_LENGTH = 255
|
||||||
|
""":obj:`int`: Maximum number of characters of the invoice description."""
|
||||||
|
MIN_PAYLOAD_LENGTH = 1
|
||||||
|
""":obj:`int`: Minimum amount of bytes for the internal payload."""
|
||||||
|
MAX_PAYLOAD_LENGTH = 128
|
||||||
|
""":obj:`int`: Maximum amount of bytes for the internal payload."""
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookLimit(IntEnum):
|
||||||
|
"""This enum contains limitations for :paramref:`telegram.Bot.set_webhook.secret_token`. The
|
||||||
|
enum members of this enumeration are instances of :class:`int` and can be treated as such.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
MIN_SECRET_TOKEN_LENGTH = 1
|
||||||
|
""":obj:`int`: Minimum length of the secret token."""
|
||||||
|
MAX_SECRET_TOKEN_LENGTH = 256
|
||||||
|
""":obj:`int`: Maximum length of the secret token."""
|
||||||
|
|
|
@ -662,6 +662,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
|
||||||
max_connections: int = 40,
|
max_connections: int = 40,
|
||||||
close_loop: bool = True,
|
close_loop: bool = True,
|
||||||
stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
|
stop_signals: ODVInput[Sequence[int]] = DEFAULT_NONE,
|
||||||
|
secret_token: str = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Convenience method that takes care of initializing and starting the app,
|
"""Convenience method that takes care of initializing and starting the app,
|
||||||
polling updates from Telegram using :meth:`telegram.ext.Updater.start_webhook` and
|
polling updates from Telegram using :meth:`telegram.ext.Updater.start_webhook` and
|
||||||
|
@ -724,6 +725,16 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
|
||||||
:meth:`asyncio.loop.add_signal_handler`. Most notably, the standard event loop
|
:meth:`asyncio.loop.add_signal_handler`. Most notably, the standard event loop
|
||||||
on Windows, :class:`asyncio.ProactorEventLoop`, does not implement this method.
|
on Windows, :class:`asyncio.ProactorEventLoop`, does not implement this method.
|
||||||
If this method is not available, stop signals can not be set.
|
If this method is not available, stop signals can not be set.
|
||||||
|
secret_token (:obj:`str`, optional): Secret token to ensure webhook requests originate
|
||||||
|
from Telegram. See :paramref:`telegram.Bot.set_webhook.secret_token` for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
When added, the web server started by this call will expect the token to be set in
|
||||||
|
the ``X-Telegram-Bot-Api-Secret-Token`` header of an incoming request and will
|
||||||
|
raise a :class:`http.HTTPStatus.FORBIDDEN <http.HTTPStatus>` error if either the
|
||||||
|
header isn't set or it is set to a wrong token.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
"""
|
"""
|
||||||
if not self.updater:
|
if not self.updater:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
@ -743,6 +754,7 @@ class Application(Generic[BT, CCT, UD, CD, BD, JQ], AbstractAsyncContextManager)
|
||||||
allowed_updates=allowed_updates,
|
allowed_updates=allowed_updates,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
max_connections=max_connections,
|
max_connections=max_connections,
|
||||||
|
secret_token=secret_token,
|
||||||
),
|
),
|
||||||
close_loop=close_loop,
|
close_loop=close_loop,
|
||||||
stop_signals=stop_signals,
|
stop_signals=stop_signals,
|
||||||
|
|
|
@ -369,6 +369,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
drop_pending_updates: bool = None,
|
drop_pending_updates: bool = None,
|
||||||
ip_address: str = None,
|
ip_address: str = None,
|
||||||
max_connections: int = 40,
|
max_connections: int = 40,
|
||||||
|
secret_token: str = None,
|
||||||
) -> asyncio.Queue:
|
) -> asyncio.Queue:
|
||||||
"""
|
"""
|
||||||
Starts a small http server to listen for updates via webhook. If :paramref:`cert`
|
Starts a small http server to listen for updates via webhook. If :paramref:`cert`
|
||||||
|
@ -395,6 +396,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
key (:class:`pathlib.Path` | :obj:`str`, optional): Path to the SSL key file.
|
key (:class:`pathlib.Path` | :obj:`str`, optional): Path to the SSL key file.
|
||||||
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
|
drop_pending_updates (:obj:`bool`, optional): Whether to clean any pending updates on
|
||||||
Telegram servers before actually starting to poll. Default is :obj:`False`.
|
Telegram servers before actually starting to poll. Default is :obj:`False`.
|
||||||
|
|
||||||
.. versionadded :: 13.4
|
.. versionadded :: 13.4
|
||||||
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
|
bootstrap_retries (:obj:`int`, optional): Whether the bootstrapping phase of the
|
||||||
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
|
:class:`telegram.ext.Updater` will retry on failures on the Telegram server.
|
||||||
|
@ -407,12 +409,23 @@ class Updater(AbstractAsyncContextManager):
|
||||||
:paramref:`port`, :paramref:`url_path`, :paramref:`cert`, and :paramref:`key`.
|
:paramref:`port`, :paramref:`url_path`, :paramref:`cert`, and :paramref:`key`.
|
||||||
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
|
ip_address (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
|
||||||
Defaults to :obj:`None`.
|
Defaults to :obj:`None`.
|
||||||
|
|
||||||
.. versionadded :: 13.4
|
.. versionadded :: 13.4
|
||||||
allowed_updates (List[:obj:`str`], optional): Passed to
|
allowed_updates (List[:obj:`str`], optional): Passed to
|
||||||
:meth:`telegram.Bot.set_webhook`. Defaults to :obj:`None`.
|
:meth:`telegram.Bot.set_webhook`. Defaults to :obj:`None`.
|
||||||
max_connections (:obj:`int`, optional): Passed to
|
max_connections (:obj:`int`, optional): Passed to
|
||||||
:meth:`telegram.Bot.set_webhook`. Defaults to ``40``.
|
:meth:`telegram.Bot.set_webhook`. Defaults to ``40``.
|
||||||
|
|
||||||
.. versionadded:: 13.6
|
.. versionadded:: 13.6
|
||||||
|
secret_token (:obj:`str`, optional): Passed to :meth:`telegram.Bot.set_webhook`.
|
||||||
|
Defaults to :obj:`None`.
|
||||||
|
|
||||||
|
When added, the web server started by this call will expect the token to be set in
|
||||||
|
the ``X-Telegram-Bot-Api-Secret-Token`` header of an incoming request and will
|
||||||
|
raise a :class:`http.HTTPStatus.FORBIDDEN <http.HTTPStatus>` error if either the
|
||||||
|
header isn't set or it is set to a wrong token.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
Returns:
|
Returns:
|
||||||
:class:`queue.Queue`: The update queue that can be filled from the main thread.
|
:class:`queue.Queue`: The update queue that can be filled from the main thread.
|
||||||
|
|
||||||
|
@ -444,6 +457,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
ready=webhook_ready,
|
ready=webhook_ready,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
max_connections=max_connections,
|
max_connections=max_connections,
|
||||||
|
secret_token=secret_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._logger.debug("Waiting for webhook server to start")
|
self._logger.debug("Waiting for webhook server to start")
|
||||||
|
@ -470,6 +484,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
ready: asyncio.Event = None,
|
ready: asyncio.Event = None,
|
||||||
ip_address: str = None,
|
ip_address: str = None,
|
||||||
max_connections: int = 40,
|
max_connections: int = 40,
|
||||||
|
secret_token: str = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._logger.debug("Updater thread started (webhook)")
|
self._logger.debug("Updater thread started (webhook)")
|
||||||
|
|
||||||
|
@ -477,7 +492,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
url_path = f"/{url_path}"
|
url_path = f"/{url_path}"
|
||||||
|
|
||||||
# Create Tornado app instance
|
# Create Tornado app instance
|
||||||
app = WebhookAppClass(url_path, self.bot, self.update_queue)
|
app = WebhookAppClass(url_path, self.bot, self.update_queue, secret_token)
|
||||||
|
|
||||||
# Form SSL Context
|
# Form SSL Context
|
||||||
# An SSLError is raised if the private key does not match with the certificate
|
# An SSLError is raised if the private key does not match with the certificate
|
||||||
|
@ -517,6 +532,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
allowed_updates=allowed_updates,
|
allowed_updates=allowed_updates,
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
max_connections=max_connections,
|
max_connections=max_connections,
|
||||||
|
secret_token=secret_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
await self._httpd.serve_forever(ready=ready)
|
await self._httpd.serve_forever(ready=ready)
|
||||||
|
@ -591,6 +607,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
bootstrap_interval: float = 1,
|
bootstrap_interval: float = 1,
|
||||||
ip_address: str = None,
|
ip_address: str = None,
|
||||||
max_connections: int = 40,
|
max_connections: int = 40,
|
||||||
|
secret_token: str = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Prepares the setup for fetching updates: delete or set the webhook and drop pending
|
"""Prepares the setup for fetching updates: delete or set the webhook and drop pending
|
||||||
updates if appropriate. If there are unsuccessful attempts, this will retry as specified by
|
updates if appropriate. If there are unsuccessful attempts, this will retry as specified by
|
||||||
|
@ -616,6 +633,7 @@ class Updater(AbstractAsyncContextManager):
|
||||||
ip_address=ip_address,
|
ip_address=ip_address,
|
||||||
drop_pending_updates=drop_pending_updates,
|
drop_pending_updates=drop_pending_updates,
|
||||||
max_connections=max_connections,
|
max_connections=max_connections,
|
||||||
|
secret_token=secret_token,
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
|
@ -83,8 +83,14 @@ class WebhookServer:
|
||||||
class WebhookAppClass(tornado.web.Application):
|
class WebhookAppClass(tornado.web.Application):
|
||||||
"""Application used in the Webserver"""
|
"""Application used in the Webserver"""
|
||||||
|
|
||||||
def __init__(self, webhook_path: str, bot: "Bot", update_queue: asyncio.Queue):
|
def __init__(
|
||||||
self.shared_objects = {"bot": bot, "update_queue": update_queue}
|
self, webhook_path: str, bot: "Bot", update_queue: asyncio.Queue, secret_token: str = None
|
||||||
|
):
|
||||||
|
self.shared_objects = {
|
||||||
|
"bot": bot,
|
||||||
|
"update_queue": update_queue,
|
||||||
|
"secret_token": secret_token,
|
||||||
|
}
|
||||||
handlers = [(rf"{webhook_path}/?", TelegramHandler, self.shared_objects)] # noqa
|
handlers = [(rf"{webhook_path}/?", TelegramHandler, self.shared_objects)] # noqa
|
||||||
tornado.web.Application.__init__(self, handlers) # type: ignore
|
tornado.web.Application.__init__(self, handlers) # type: ignore
|
||||||
|
|
||||||
|
@ -96,16 +102,21 @@ class WebhookAppClass(tornado.web.Application):
|
||||||
class TelegramHandler(tornado.web.RequestHandler):
|
class TelegramHandler(tornado.web.RequestHandler):
|
||||||
"""BaseHandler that processes incoming requests from Telegram"""
|
"""BaseHandler that processes incoming requests from Telegram"""
|
||||||
|
|
||||||
__slots__ = ("bot", "update_queue", "_logger")
|
__slots__ = ("bot", "update_queue", "_logger", "secret_token")
|
||||||
|
|
||||||
SUPPORTED_METHODS = ("POST",) # type: ignore[assignment]
|
SUPPORTED_METHODS = ("POST",) # type: ignore[assignment]
|
||||||
|
|
||||||
def initialize(self, bot: "Bot", update_queue: asyncio.Queue) -> None:
|
def initialize(self, bot: "Bot", update_queue: asyncio.Queue, secret_token: str) -> None:
|
||||||
"""Initialize for each request - that's the interface provided by tornado"""
|
"""Initialize for each request - that's the interface provided by tornado"""
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.update_queue = update_queue
|
self.update_queue = update_queue
|
||||||
self._logger = logging.getLogger(__name__)
|
self._logger = logging.getLogger(__name__)
|
||||||
|
self.secret_token = secret_token
|
||||||
|
if secret_token:
|
||||||
|
self._logger.debug(
|
||||||
|
"The webhook server has a secret token, " "expecting it in incoming requests now"
|
||||||
|
)
|
||||||
|
|
||||||
def set_default_headers(self) -> None:
|
def set_default_headers(self) -> None:
|
||||||
"""Sets default headers"""
|
"""Sets default headers"""
|
||||||
|
@ -144,6 +155,19 @@ class TelegramHandler(tornado.web.RequestHandler):
|
||||||
ct_header = self.request.headers.get("Content-Type", None)
|
ct_header = self.request.headers.get("Content-Type", None)
|
||||||
if ct_header != "application/json":
|
if ct_header != "application/json":
|
||||||
raise tornado.web.HTTPError(HTTPStatus.FORBIDDEN)
|
raise tornado.web.HTTPError(HTTPStatus.FORBIDDEN)
|
||||||
|
# verifying that the secret token is the one the user set when the user set one
|
||||||
|
if self.secret_token is not None:
|
||||||
|
token = self.request.headers.get("X-Telegram-Bot-Api-Secret-Token")
|
||||||
|
if not token:
|
||||||
|
self._logger.debug("Request did not include the secret token")
|
||||||
|
raise tornado.web.HTTPError(
|
||||||
|
HTTPStatus.FORBIDDEN, reason="Request did not include the secret token"
|
||||||
|
)
|
||||||
|
if token != self.secret_token:
|
||||||
|
self._logger.debug("Request had the wrong secret token: %s", token)
|
||||||
|
raise tornado.web.HTTPError(
|
||||||
|
HTTPStatus.FORBIDDEN, reason="Request had the wrong secret token"
|
||||||
|
)
|
||||||
|
|
||||||
def log_exception(
|
def log_exception(
|
||||||
self,
|
self,
|
||||||
|
|
|
@ -76,6 +76,8 @@ __all__ = (
|
||||||
"TEXT",
|
"TEXT",
|
||||||
"Text",
|
"Text",
|
||||||
"USER",
|
"USER",
|
||||||
|
"USER_ATTACHMENT",
|
||||||
|
"PREMIUM_USER",
|
||||||
"UpdateFilter",
|
"UpdateFilter",
|
||||||
"UpdateType",
|
"UpdateType",
|
||||||
"User",
|
"User",
|
||||||
|
@ -1949,6 +1951,21 @@ class Sticker:
|
||||||
.. versionadded:: 20.0
|
.. versionadded:: 20.0
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
class _Premium(MessageFilter):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def filter(self, message: Message) -> bool:
|
||||||
|
return bool(message.sticker) and bool(
|
||||||
|
message.sticker.premium_animation # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
PREMIUM = _Premium(name="filters.Sticker.PREMIUM")
|
||||||
|
"""Messages that contain :attr:`telegram.Message.sticker` and have a
|
||||||
|
:attr:`premium animation <telegram.Sticker.premium_animation>`.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class _SuccessfulPayment(MessageFilter):
|
class _SuccessfulPayment(MessageFilter):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
@ -2185,6 +2202,41 @@ USER = _User(name="filters.USER")
|
||||||
"""This filter filters *any* message that has a :attr:`telegram.Message.from_user`."""
|
"""This filter filters *any* message that has a :attr:`telegram.Message.from_user`."""
|
||||||
|
|
||||||
|
|
||||||
|
class _UserAttachment(UpdateFilter):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def filter(self, update: Update) -> bool:
|
||||||
|
return bool(update.effective_user) and bool(
|
||||||
|
update.effective_user.added_to_attachment_menu # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
USER_ATTACHMENT = _UserAttachment(name="filters.USER_ATTACHMENT")
|
||||||
|
"""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:: 20.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class _UserPremium(UpdateFilter):
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
|
def filter(self, update: Update) -> bool:
|
||||||
|
return bool(update.effective_user) and bool(
|
||||||
|
update.effective_user.is_premium # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PREMIUM_USER = _UserPremium(name="filters.PREMIUM_USER")
|
||||||
|
"""This filter filters *any* message from a
|
||||||
|
:attr:`Telegram Premium user <telegram.User.is_premium>` as :attr:`telegram.Update.effective_user`.
|
||||||
|
|
||||||
|
.. versionadded:: 20.0
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class _Venue(MessageFilter):
|
class _Venue(MessageFilter):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
|
@ -830,10 +830,13 @@ async def send_webhook_message(
|
||||||
content_len: int = -1,
|
content_len: int = -1,
|
||||||
content_type: str = "application/json",
|
content_type: str = "application/json",
|
||||||
get_method: str = None,
|
get_method: str = None,
|
||||||
|
secret_token: str = None,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
headers = {
|
headers = {
|
||||||
"content-type": content_type,
|
"content-type": content_type,
|
||||||
}
|
}
|
||||||
|
if secret_token:
|
||||||
|
headers["X-Telegram-Bot-Api-Secret-Token"] = secret_token
|
||||||
|
|
||||||
if not payload_str:
|
if not payload_str:
|
||||||
content_len = None
|
content_len = None
|
||||||
|
|
|
@ -1634,6 +1634,35 @@ class TestBot:
|
||||||
assert await bot.set_webhook("", drop_pending_updates=drop_pending_updates)
|
assert await bot.set_webhook("", drop_pending_updates=drop_pending_updates)
|
||||||
assert await bot.delete_webhook(drop_pending_updates=drop_pending_updates)
|
assert await bot.delete_webhook(drop_pending_updates=drop_pending_updates)
|
||||||
|
|
||||||
|
async 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/
|
||||||
|
async def make_assertion(*args, **_):
|
||||||
|
kwargs = args[1]
|
||||||
|
return (
|
||||||
|
kwargs["url"] == "example.com"
|
||||||
|
and kwargs["certificate"].input_file_content
|
||||||
|
== data_file("sslcert.pem").read_bytes()
|
||||||
|
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 await bot.set_webhook(
|
||||||
|
"example.com",
|
||||||
|
data_file("sslcert.pem").read_bytes(),
|
||||||
|
7,
|
||||||
|
["messages"],
|
||||||
|
"127.0.0.1",
|
||||||
|
True,
|
||||||
|
"SoSecretToken",
|
||||||
|
)
|
||||||
|
|
||||||
@flaky(3, 1)
|
@flaky(3, 1)
|
||||||
async def test_leave_chat(self, bot):
|
async def test_leave_chat(self, bot):
|
||||||
with pytest.raises(BadRequest, match="Chat not found"):
|
with pytest.raises(BadRequest, match="Chat not found"):
|
||||||
|
@ -1833,7 +1862,7 @@ class TestBot:
|
||||||
# We assume that the other game score tests ran within 20 sec
|
# We assume that the other game score tests ran within 20 sec
|
||||||
assert high_scores[0].score == BASE_GAME_SCORE - 10
|
assert high_scores[0].score == BASE_GAME_SCORE - 10
|
||||||
|
|
||||||
# send_invoice is tested in test_invoice
|
# send_invoice and create_invoice_link is tested in test_invoice
|
||||||
|
|
||||||
# TODO: Needs improvement. Need incoming shipping queries to test
|
# TODO: Needs improvement. Need incoming shipping queries to test
|
||||||
async def test_answer_shipping_query_ok(self, monkeypatch, bot):
|
async def test_answer_shipping_query_ok(self, monkeypatch, bot):
|
||||||
|
|
|
@ -42,6 +42,8 @@ def chat(bot):
|
||||||
location=TestChat.location,
|
location=TestChat.location,
|
||||||
has_private_forwards=True,
|
has_private_forwards=True,
|
||||||
has_protected_content=True,
|
has_protected_content=True,
|
||||||
|
join_to_send_messages=True,
|
||||||
|
join_by_request=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,6 +66,8 @@ class TestChat:
|
||||||
location = ChatLocation(Location(123, 456), "Barbie World")
|
location = ChatLocation(Location(123, 456), "Barbie World")
|
||||||
has_protected_content = True
|
has_protected_content = True
|
||||||
has_private_forwards = True
|
has_private_forwards = True
|
||||||
|
join_to_send_messages = True
|
||||||
|
join_by_request = True
|
||||||
|
|
||||||
def test_slot_behaviour(self, chat, mro_slots):
|
def test_slot_behaviour(self, chat, mro_slots):
|
||||||
for attr in chat.__slots__:
|
for attr in chat.__slots__:
|
||||||
|
@ -86,6 +90,8 @@ class TestChat:
|
||||||
"has_private_forwards": self.has_private_forwards,
|
"has_private_forwards": self.has_private_forwards,
|
||||||
"linked_chat_id": self.linked_chat_id,
|
"linked_chat_id": self.linked_chat_id,
|
||||||
"location": self.location.to_dict(),
|
"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)
|
chat = Chat.de_json(json_dict, bot)
|
||||||
|
|
||||||
|
@ -104,6 +110,8 @@ class TestChat:
|
||||||
assert chat.linked_chat_id == self.linked_chat_id
|
assert chat.linked_chat_id == self.linked_chat_id
|
||||||
assert chat.location.location == self.location.location
|
assert chat.location.location == self.location.location
|
||||||
assert chat.location.address == self.location.address
|
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):
|
def test_to_dict(self, chat):
|
||||||
chat_dict = chat.to_dict()
|
chat_dict = chat.to_dict()
|
||||||
|
@ -121,6 +129,8 @@ class TestChat:
|
||||||
assert chat_dict["has_protected_content"] == chat.has_protected_content
|
assert chat_dict["has_protected_content"] == chat.has_protected_content
|
||||||
assert chat_dict["linked_chat_id"] == chat.linked_chat_id
|
assert chat_dict["linked_chat_id"] == chat.linked_chat_id
|
||||||
assert chat_dict["location"] == chat.location.to_dict()
|
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_enum_init(self):
|
def test_enum_init(self):
|
||||||
chat = Chat(id=1, type="foo")
|
chat = Chat(id=1, type="foo")
|
||||||
|
|
|
@ -27,6 +27,7 @@ from telegram import (
|
||||||
Chat,
|
Chat,
|
||||||
Dice,
|
Dice,
|
||||||
Document,
|
Document,
|
||||||
|
File,
|
||||||
Message,
|
Message,
|
||||||
MessageEntity,
|
MessageEntity,
|
||||||
Sticker,
|
Sticker,
|
||||||
|
@ -830,15 +831,26 @@ class TestFilters:
|
||||||
update.message.sticker = Sticker("1", "uniq", 1, 2, False, False)
|
update.message.sticker = Sticker("1", "uniq", 1, 2, False, False)
|
||||||
assert filters.Sticker.ALL.check_update(update)
|
assert filters.Sticker.ALL.check_update(update)
|
||||||
assert filters.Sticker.STATIC.check_update(update)
|
assert filters.Sticker.STATIC.check_update(update)
|
||||||
|
assert not filters.Sticker.VIDEO.check_update(update)
|
||||||
|
assert not filters.Sticker.PREMIUM.check_update(update)
|
||||||
update.message.sticker.is_animated = True
|
update.message.sticker.is_animated = True
|
||||||
assert filters.Sticker.ANIMATED.check_update(update)
|
assert filters.Sticker.ANIMATED.check_update(update)
|
||||||
assert not filters.Sticker.VIDEO.check_update(update)
|
assert not filters.Sticker.VIDEO.check_update(update)
|
||||||
assert not filters.Sticker.STATIC.check_update(update)
|
assert not filters.Sticker.STATIC.check_update(update)
|
||||||
|
assert not filters.Sticker.PREMIUM.check_update(update)
|
||||||
update.message.sticker.is_animated = False
|
update.message.sticker.is_animated = False
|
||||||
update.message.sticker.is_video = True
|
update.message.sticker.is_video = True
|
||||||
assert not filters.Sticker.ANIMATED.check_update(update)
|
assert not filters.Sticker.ANIMATED.check_update(update)
|
||||||
assert not filters.Sticker.STATIC.check_update(update)
|
assert not filters.Sticker.STATIC.check_update(update)
|
||||||
assert filters.Sticker.VIDEO.check_update(update)
|
assert filters.Sticker.VIDEO.check_update(update)
|
||||||
|
assert not filters.Sticker.PREMIUM.check_update(update)
|
||||||
|
update.message.sticker.premium_animation = File("string", "uniqueString")
|
||||||
|
assert not filters.Sticker.ANIMATED.check_update(update)
|
||||||
|
# premium stickers can be animated, video, or probably also static,
|
||||||
|
# it doesn't really matter for the test
|
||||||
|
assert not filters.Sticker.STATIC.check_update(update)
|
||||||
|
assert filters.Sticker.VIDEO.check_update(update)
|
||||||
|
assert filters.Sticker.PREMIUM.check_update(update)
|
||||||
|
|
||||||
def test_filters_video(self, update):
|
def test_filters_video(self, update):
|
||||||
assert not filters.VIDEO.check_update(update)
|
assert not filters.VIDEO.check_update(update)
|
||||||
|
@ -1168,6 +1180,19 @@ class TestFilters:
|
||||||
with pytest.raises(RuntimeError, match="Cannot set name"):
|
with pytest.raises(RuntimeError, match="Cannot set name"):
|
||||||
f.name = "foo"
|
f.name = "foo"
|
||||||
|
|
||||||
|
def test_filters_user_attributes(self, update):
|
||||||
|
assert not filters.USER_ATTACHMENT.check_update(update)
|
||||||
|
assert not filters.PREMIUM_USER.check_update(update)
|
||||||
|
update.message.from_user.added_to_attachment_menu = True
|
||||||
|
assert filters.USER_ATTACHMENT.check_update(update)
|
||||||
|
assert not filters.PREMIUM_USER.check_update(update)
|
||||||
|
update.message.from_user.is_premium = True
|
||||||
|
assert filters.USER_ATTACHMENT.check_update(update)
|
||||||
|
assert filters.PREMIUM_USER.check_update(update)
|
||||||
|
update.message.from_user.added_to_attachment_menu = False
|
||||||
|
assert not filters.USER_ATTACHMENT.check_update(update)
|
||||||
|
assert filters.PREMIUM_USER.check_update(update)
|
||||||
|
|
||||||
def test_filters_chat_init(self):
|
def test_filters_chat_init(self):
|
||||||
with pytest.raises(RuntimeError, match="in conjunction with"):
|
with pytest.raises(RuntimeError, match="in conjunction with"):
|
||||||
filters.Chat(chat_id=1, username="chat")
|
filters.Chat(chat_id=1, username="chat")
|
||||||
|
|
|
@ -98,8 +98,18 @@ class TestInvoice:
|
||||||
assert message.invoice.title == self.title
|
assert message.invoice.title == self.title
|
||||||
assert message.invoice.total_amount == self.total_amount
|
assert message.invoice.total_amount == self.total_amount
|
||||||
|
|
||||||
@flaky(3, 1)
|
link = await bot.create_invoice_link(
|
||||||
async def test_send_all_args(self, bot, chat_id, provider_token, monkeypatch):
|
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 != ""
|
||||||
|
|
||||||
|
async def test_send_all_args_send_invoice(self, bot, chat_id, provider_token, monkeypatch):
|
||||||
message = await bot.send_invoice(
|
message = await bot.send_invoice(
|
||||||
chat_id,
|
chat_id,
|
||||||
self.title,
|
self.title,
|
||||||
|
@ -193,6 +203,58 @@ class TestInvoice:
|
||||||
protect_content=True,
|
protect_content=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def test_send_all_args_create_invoice_link(
|
||||||
|
self, bot, chat_id, provider_token, monkeypatch
|
||||||
|
):
|
||||||
|
async 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"] == 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 await 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",
|
||||||
|
)
|
||||||
|
|
||||||
async def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
|
async def test_send_object_as_provider_data(self, monkeypatch, bot, chat_id, provider_token):
|
||||||
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
|
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
|
||||||
return request_data.json_parameters["provider_data"] == '{"test_data": 123456789}'
|
return request_data.json_parameters["provider_data"] == '{"test_data": 123456789}'
|
||||||
|
|
|
@ -23,7 +23,7 @@ from pathlib import Path
|
||||||
import pytest
|
import pytest
|
||||||
from flaky import flaky
|
from flaky import flaky
|
||||||
|
|
||||||
from telegram import Audio, Bot, MaskPosition, PhotoSize, Sticker, StickerSet
|
from telegram import Audio, Bot, File, MaskPosition, PhotoSize, Sticker, StickerSet
|
||||||
from telegram.error import BadRequest, TelegramError
|
from telegram.error import BadRequest, TelegramError
|
||||||
from telegram.request import RequestData
|
from telegram.request import RequestData
|
||||||
from tests.conftest import (
|
from tests.conftest import (
|
||||||
|
@ -91,6 +91,8 @@ class TestSticker:
|
||||||
sticker_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
|
sticker_file_id = "5a3128a4d2a04750b5b58397f3b5e812"
|
||||||
sticker_file_unique_id = "adc3145fd2e84d95b64d68eaa22aa33e"
|
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):
|
def test_slot_behaviour(self, sticker, mro_slots, recwarn):
|
||||||
for attr in sticker.__slots__:
|
for attr in sticker.__slots__:
|
||||||
assert getattr(sticker, attr, "err") != "err", f"got extra slot '{attr}'"
|
assert getattr(sticker, attr, "err") != "err", f"got extra slot '{attr}'"
|
||||||
|
@ -118,6 +120,8 @@ class TestSticker:
|
||||||
assert sticker.thumb.width == self.thumb_width
|
assert sticker.thumb.width == self.thumb_width
|
||||||
assert sticker.thumb.height == self.thumb_height
|
assert sticker.thumb.height == self.thumb_height
|
||||||
assert sticker.thumb.file_size == self.thumb_file_size
|
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)
|
@flaky(3, 1)
|
||||||
async def test_send_all_args(self, bot, chat_id, sticker_file, sticker):
|
async def test_send_all_args(self, bot, chat_id, sticker_file, sticker):
|
||||||
|
@ -135,6 +139,8 @@ class TestSticker:
|
||||||
assert message.sticker.is_animated == sticker.is_animated
|
assert message.sticker.is_animated == sticker.is_animated
|
||||||
assert message.sticker.is_video == sticker.is_video
|
assert message.sticker.is_video == sticker.is_video
|
||||||
assert message.sticker.file_size == sticker.file_size
|
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, PhotoSize)
|
||||||
assert isinstance(message.sticker.thumb.file_id, str)
|
assert isinstance(message.sticker.thumb.file_id, str)
|
||||||
|
@ -212,6 +218,7 @@ class TestSticker:
|
||||||
"thumb": sticker.thumb.to_dict(),
|
"thumb": sticker.thumb.to_dict(),
|
||||||
"emoji": self.emoji,
|
"emoji": self.emoji,
|
||||||
"file_size": self.file_size,
|
"file_size": self.file_size,
|
||||||
|
"premium_animation": self.premium_animation.to_dict(),
|
||||||
}
|
}
|
||||||
json_sticker = Sticker.de_json(json_dict, bot)
|
json_sticker = Sticker.de_json(json_dict, bot)
|
||||||
|
|
||||||
|
@ -224,6 +231,7 @@ class TestSticker:
|
||||||
assert json_sticker.emoji == self.emoji
|
assert json_sticker.emoji == self.emoji
|
||||||
assert json_sticker.file_size == self.file_size
|
assert json_sticker.file_size == self.file_size
|
||||||
assert json_sticker.thumb == sticker.thumb
|
assert json_sticker.thumb == sticker.thumb
|
||||||
|
assert json_sticker.premium_animation == self.premium_animation
|
||||||
|
|
||||||
async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
|
async def test_send_with_sticker(self, monkeypatch, bot, chat_id, sticker):
|
||||||
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
|
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
|
||||||
|
@ -317,6 +325,24 @@ class TestSticker:
|
||||||
with pytest.raises(TypeError):
|
with pytest.raises(TypeError):
|
||||||
await bot.send_sticker(chat_id)
|
await bot.send_sticker(chat_id)
|
||||||
|
|
||||||
|
@flaky(3, 1)
|
||||||
|
async 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 = await 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):
|
def test_equality(self, sticker):
|
||||||
a = Sticker(
|
a = Sticker(
|
||||||
sticker.file_id,
|
sticker.file_id,
|
||||||
|
|
|
@ -504,7 +504,10 @@ class TestUpdater:
|
||||||
|
|
||||||
@pytest.mark.parametrize("ext_bot", [True, False])
|
@pytest.mark.parametrize("ext_bot", [True, False])
|
||||||
@pytest.mark.parametrize("drop_pending_updates", (True, False))
|
@pytest.mark.parametrize("drop_pending_updates", (True, False))
|
||||||
async def test_webhook_basic(self, monkeypatch, updater, drop_pending_updates, ext_bot):
|
@pytest.mark.parametrize("secret_token", ["SecretToken", None])
|
||||||
|
async def test_webhook_basic(
|
||||||
|
self, monkeypatch, updater, drop_pending_updates, ext_bot, secret_token
|
||||||
|
):
|
||||||
# Testing with both ExtBot and Bot to make sure any logic in WebhookHandler
|
# Testing with both ExtBot and Bot to make sure any logic in WebhookHandler
|
||||||
# that depends on this distinction works
|
# that depends on this distinction works
|
||||||
if ext_bot and not isinstance(updater.bot, ExtBot):
|
if ext_bot and not isinstance(updater.bot, ExtBot):
|
||||||
|
@ -533,13 +536,16 @@ class TestUpdater:
|
||||||
ip_address=ip,
|
ip_address=ip,
|
||||||
port=port,
|
port=port,
|
||||||
url_path="TOKEN",
|
url_path="TOKEN",
|
||||||
|
secret_token=secret_token,
|
||||||
)
|
)
|
||||||
assert return_value is updater.update_queue
|
assert return_value is updater.update_queue
|
||||||
assert updater.running
|
assert updater.running
|
||||||
|
|
||||||
# Now, we send an update to the server
|
# Now, we send an update to the server
|
||||||
update = make_message_update("Webhook")
|
update = make_message_update("Webhook")
|
||||||
await send_webhook_message(ip, port, update.to_json(), "TOKEN")
|
await send_webhook_message(
|
||||||
|
ip, port, update.to_json(), "TOKEN", secret_token=secret_token
|
||||||
|
)
|
||||||
assert (await updater.update_queue.get()).to_dict() == update.to_dict()
|
assert (await updater.update_queue.get()).to_dict() == update.to_dict()
|
||||||
|
|
||||||
# Returns Not Found if path is incorrect
|
# Returns Not Found if path is incorrect
|
||||||
|
@ -550,6 +556,22 @@ class TestUpdater:
|
||||||
response = await send_webhook_message(ip, port, None, "TOKEN", get_method="HEAD")
|
response = await send_webhook_message(ip, port, None, "TOKEN", get_method="HEAD")
|
||||||
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED
|
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED
|
||||||
|
|
||||||
|
if secret_token:
|
||||||
|
# Returns Forbidden if no secret token is set
|
||||||
|
response_text = "<html><title>403: {0}</title><body>403: {0}</body></html>"
|
||||||
|
response = await send_webhook_message(ip, port, update.to_json(), "TOKEN")
|
||||||
|
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||||
|
assert response.text == response_text.format(
|
||||||
|
"Request did not include the secret token"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Returns Forbidden if the secret token is wrong
|
||||||
|
response = await send_webhook_message(
|
||||||
|
ip, port, update.to_json(), "TOKEN", secret_token="NotTheSecretToken"
|
||||||
|
)
|
||||||
|
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||||
|
assert response.text == response_text.format("Request had the wrong secret token")
|
||||||
|
|
||||||
await updater.stop()
|
await updater.stop()
|
||||||
assert not updater.running
|
assert not updater.running
|
||||||
|
|
||||||
|
@ -600,6 +622,7 @@ class TestUpdater:
|
||||||
max_connections=40,
|
max_connections=40,
|
||||||
allowed_updates=None,
|
allowed_updates=None,
|
||||||
ip_address=None,
|
ip_address=None,
|
||||||
|
secret_token=None,
|
||||||
**expected_delete_webhook,
|
**expected_delete_webhook,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -641,6 +664,7 @@ class TestUpdater:
|
||||||
max_connections=47,
|
max_connections=47,
|
||||||
allowed_updates=["message"],
|
allowed_updates=["message"],
|
||||||
ip_address="123.456.789",
|
ip_address="123.456.789",
|
||||||
|
secret_token=None,
|
||||||
**expected_delete_webhook,
|
**expected_delete_webhook,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ def json_dict():
|
||||||
"can_join_groups": TestUser.can_join_groups,
|
"can_join_groups": TestUser.can_join_groups,
|
||||||
"can_read_all_group_messages": TestUser.can_read_all_group_messages,
|
"can_read_all_group_messages": TestUser.can_read_all_group_messages,
|
||||||
"supports_inline_queries": TestUser.supports_inline_queries,
|
"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,
|
can_read_all_group_messages=TestUser.can_read_all_group_messages,
|
||||||
supports_inline_queries=TestUser.supports_inline_queries,
|
supports_inline_queries=TestUser.supports_inline_queries,
|
||||||
bot=bot,
|
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_join_groups = True
|
||||||
can_read_all_group_messages = True
|
can_read_all_group_messages = True
|
||||||
supports_inline_queries = False
|
supports_inline_queries = False
|
||||||
|
is_premium = True
|
||||||
|
added_to_attachment_menu = False
|
||||||
|
|
||||||
def test_slot_behaviour(self, user, mro_slots):
|
def test_slot_behaviour(self, user, mro_slots):
|
||||||
for attr in user.__slots__:
|
for attr in user.__slots__:
|
||||||
|
@ -82,6 +88,8 @@ class TestUser:
|
||||||
assert user.can_join_groups == self.can_join_groups
|
assert user.can_join_groups == self.can_join_groups
|
||||||
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
||||||
assert user.supports_inline_queries == self.supports_inline_queries
|
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):
|
def test_de_json_without_username(self, json_dict, bot):
|
||||||
del json_dict["username"]
|
del json_dict["username"]
|
||||||
|
@ -97,6 +105,8 @@ class TestUser:
|
||||||
assert user.can_join_groups == self.can_join_groups
|
assert user.can_join_groups == self.can_join_groups
|
||||||
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
||||||
assert user.supports_inline_queries == self.supports_inline_queries
|
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):
|
def test_de_json_without_username_and_last_name(self, json_dict, bot):
|
||||||
del json_dict["username"]
|
del json_dict["username"]
|
||||||
|
@ -113,6 +123,8 @@ class TestUser:
|
||||||
assert user.can_join_groups == self.can_join_groups
|
assert user.can_join_groups == self.can_join_groups
|
||||||
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
assert user.can_read_all_group_messages == self.can_read_all_group_messages
|
||||||
assert user.supports_inline_queries == self.supports_inline_queries
|
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):
|
def test_name(self, user):
|
||||||
assert user.name == "@username"
|
assert user.name == "@username"
|
||||||
|
|
Loading…
Reference in a new issue