Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Harshil Mehta <37377066+harshil21@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Aditya <clot27@apx_managed.vanilla>
This commit is contained in:
Bibo-Joshi 2023-05-07 13:44:34 +02:00 committed by GitHub
parent 450dc2115c
commit 8c252c9822
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 1236 additions and 91 deletions

View file

@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -93,7 +93,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.6** are supported.
All types and methods of the Telegram Bot API **6.7** are supported.
Installing
==========

View file

@ -14,7 +14,7 @@
:target: https://pypi.org/project/python-telegram-bot-raw/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.6-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.7-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions
@ -89,7 +89,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.6** are supported.
All types and methods of the Telegram Bot API **6.7** are supported.
Installing
==========

View file

@ -196,6 +196,10 @@
- Used for setting the short description of the bot
* - :meth:`~telegram.Bot.get_my_short_description`
- Used for obtaining the short description of the bot
* - :meth:`~telegram.Bot.set_my_name`
- Used for setting the name of the bot
* - :meth:`~telegram.Bot.get_my_name`
- Used for obtaining the name of the bot
.. raw:: html

View file

@ -16,6 +16,7 @@ Available Types
telegram.botcommandscopechatmember
telegram.botcommandscopedefault
telegram.botdescription
telegram.botname
telegram.botshortdescription
telegram.callbackquery
telegram.chat
@ -78,6 +79,7 @@ Available Types
telegram.replykeyboardmarkup
telegram.replykeyboardremove
telegram.sentwebappmessage
telegram.switchinlinequerychosenchat
telegram.telegramobject
telegram.update
telegram.user

View file

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

View file

@ -24,6 +24,7 @@ Inline Mode
telegram.inlinequeryresultlocation
telegram.inlinequeryresultmpeg4gif
telegram.inlinequeryresultphoto
telegram.inlinequeryresultsbutton
telegram.inlinequeryresultvenue
telegram.inlinequeryresultvideo
telegram.inlinequeryresultvoice

View file

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

View file

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

View file

@ -38,6 +38,7 @@ __all__ = ( # Keep this alphabetically ordered
"BotCommandScopeChatMember",
"BotCommandScopeDefault",
"BotDescription",
"BotName",
"BotShortDescription",
"CallbackGame",
"CallbackQuery",
@ -102,6 +103,7 @@ __all__ = ( # Keep this alphabetically ordered
"InlineQueryResultLocation",
"InlineQueryResultMpeg4Gif",
"InlineQueryResultPhoto",
"InlineQueryResultsButton",
"InlineQueryResultVenue",
"InlineQueryResultVideo",
"InlineQueryResultVoice",
@ -169,6 +171,7 @@ __all__ = ( # Keep this alphabetically ordered
"Sticker",
"StickerSet",
"SuccessfulPayment",
"SwitchInlineQueryChosenChat",
"TelegramObject",
"Update",
"User",
@ -204,6 +207,7 @@ from ._botcommandscope import (
BotCommandScopeDefault,
)
from ._botdescription import BotDescription, BotShortDescription
from ._botname import BotName
from ._callbackquery import CallbackQuery
from ._chat import Chat
from ._chatadministratorrights import ChatAdministratorRights
@ -280,6 +284,7 @@ from ._inline.inlinequeryresultgif import InlineQueryResultGif
from ._inline.inlinequeryresultlocation import InlineQueryResultLocation
from ._inline.inlinequeryresultmpeg4gif import InlineQueryResultMpeg4Gif
from ._inline.inlinequeryresultphoto import InlineQueryResultPhoto
from ._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from ._inline.inlinequeryresultvenue import InlineQueryResultVenue
from ._inline.inlinequeryresultvideo import InlineQueryResultVideo
from ._inline.inlinequeryresultvoice import InlineQueryResultVoice
@ -336,6 +341,7 @@ from ._replykeyboardmarkup import ReplyKeyboardMarkup
from ._replykeyboardremove import ReplyKeyboardRemove
from ._sentwebappmessage import SentWebAppMessage
from ._shared import ChatShared, UserShared
from ._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from ._telegramobject import TelegramObject
from ._update import Update
from ._user import User

View file

@ -56,6 +56,7 @@ except ImportError:
from telegram._botcommand import BotCommand
from telegram._botcommandscope import BotCommandScope
from telegram._botdescription import BotDescription, BotShortDescription
from telegram._botname import BotName
from telegram._chat import Chat
from telegram._chatadministratorrights import ChatAdministratorRights
from telegram._chatinvitelink import ChatInviteLink
@ -79,6 +80,7 @@ from telegram._files.voice import Voice
from telegram._forumtopic import ForumTopic
from telegram._games.gamehighscore import GameHighScore
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._menubutton import MenuButton
from telegram._message import Message
from telegram._messageid import MessageId
@ -2811,8 +2813,15 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
cache_time: int = None,
is_personal: bool = None,
next_offset: str = None,
# Deprecated params since bot api 6.7
# <----
switch_pm_text: str = None,
switch_pm_parameter: str = None,
# --->
# New params since bot api 6.7
# <----
button: InlineQueryResultsButton = None,
# --->
*,
current_offset: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2825,15 +2834,6 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
Use this method to send answers to an inline query. No more than
:tg-const:`telegram.InlineQuery.MAX_RESULTS` results per query are allowed.
Example:
An inline bot that sends YouTube videos can ask the user to connect the bot to their
YouTube account to adapt search results accordingly. To do this, it displays a
'Connect your YouTube account' button above the results, or even before showing any.
The user presses the button, switches to a private chat with the bot and, in doing so,
passes a start parameter that instructs the bot to return an OAuth link. Once done, the
bot can offer a switch_inline button so that the user can easily return to the chat
where they wanted to use the bot's inline capabilities.
Warning:
In most use cases :paramref:`current_offset` should not be passed manually. Instead of
calling this method directly, use the shortcut :meth:`telegram.InlineQuery.answer` with
@ -2842,6 +2842,9 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
.. seealso:: :wiki:`Working with Files and Media <Working-with-Files-and-Media>`
.. |api6_7_depr| replace:: Since Bot API 6.7, this argument is deprecated in favour of
:paramref:`button`.
Args:
inline_query_id (:obj:`str`): Unique identifier for the answered query.
results (List[:class:`telegram.InlineQueryResult`] | Callable): A list of results for
@ -2862,12 +2865,22 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
switch_pm_text (:obj:`str`, optional): If passed, clients will display a button with
specified text that switches the user to a private chat with the bot and sends the
bot a start message with the parameter :paramref:`switch_pm_parameter`.
.. deprecated:: NEXT.VERSION
|api6_7_depr|
switch_pm_parameter (:obj:`str`, optional): Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
.. deprecated:: NEXT.VERSION
|api6_7_depr|
button (:class:`telegram.InlineQueryResultsButton`, optional): A button to be shown
above the inline query results.
.. versionadded:: NEXT.VERSION
Keyword Args:
current_offset (:obj:`str`, optional): The :attr:`telegram.InlineQuery.offset` of
the inline query to answer. If passed, PTB will automatically take care of
@ -2881,6 +2894,26 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
:class:`telegram.error.TelegramError`
"""
if (switch_pm_text or switch_pm_parameter) and button:
raise TypeError(
"Since Bot API 6.7, the parameter `button is mutually exclusive to the deprecated "
"parameters `switch_pm_text` and `switch_pm_parameter`. Please use the new "
"parameter `button`."
)
if switch_pm_text and switch_pm_parameter:
self._warn(
"Since Bot API 6.7, the parameters `switch_pm_text` and `switch_pm_parameter` are "
"deprecated in favour of the new parameter `button`. Please use the new parameter "
"`button` instead.",
category=PTBDeprecationWarning,
stacklevel=3,
)
button = InlineQueryResultsButton(
text=switch_pm_text,
start_parameter=switch_pm_parameter,
)
effective_results, next_offset = self._effective_inline_results(
results=results, next_offset=next_offset, current_offset=current_offset
)
@ -2896,8 +2929,7 @@ class Bot(TelegramObject, AsyncContextManager["Bot"]):
"next_offset": next_offset,
"cache_time": cache_time,
"is_personal": is_personal,
"switch_pm_text": switch_pm_text,
"switch_pm_parameter": switch_pm_parameter,
"button": button,
}
return await self._post(
@ -8138,6 +8170,94 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
bot=self,
)
@_log
async def set_my_name(
self,
name: str = None,
language_code: str = 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,
) -> bool:
"""
Use this method to change the bot's name.
.. versionadded:: NEXT.VERSION
Args:
name (:obj:`str`, optional): New bot name;
0-:tg-const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH`
characters. Pass an empty string to remove the dedicated name for the given
language.
Caution:
If :paramref:`language_code` is not specified, a :paramref:`name` *must*
be specified.
language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code. If empty,
the name will be applied to all users for whose language there is no
dedicated name.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"name": name, "language_code": language_code}
return await self._post(
"setMyName",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def get_my_name(
self,
language_code: str = 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,
) -> BotName:
"""
Use this method to get the current bot name for the given user language.
Args:
language_code (:obj:`str`, optional): A two-letter ISO 639-1 language code or an empty
string.
Returns:
:class:`telegram.BotName`: On success, the bot name is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data = {"language_code": language_code}
return BotName.de_json( # type: ignore[return-value]
await self._post(
"getMyName",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
),
bot=self,
)
def to_dict(self, recursive: bool = True) -> JSONDict: # skipcq: PYL-W0613
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@ -8382,3 +8502,7 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`set_sticker_keywords`"""
setStickerMaskPosition = set_sticker_mask_position
"""Alias for :meth:`set_sticker_mask_position`"""
setMyName = set_my_name
"""Alias for :meth:`set_my_name`"""
getMyName = get_my_name
"""Alias for :meth:`get_my_name`"""

54
telegram/_botname.py Normal file
View file

@ -0,0 +1,54 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains an object that represent a Telegram bots name."""
from typing import ClassVar
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class BotName(TelegramObject):
"""This object represents the bot's name.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name` is equal.
.. versionadded:: NEXT.VERSION
Args:
name (:obj:`str`): The bot's name.
Attributes:
name (:obj:`str`): The bot's name.
"""
__slots__ = ("name",)
def __init__(self, name: str, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.name = name
self._id_attrs = (self.name,)
self._freeze()
MAX_LENGTH: ClassVar[int] = constants.BotNameLimit.MAX_NAME_LENGTH
""":const:`telegram.constants.BotNameLimit.MAX_NAME_LENGTH`"""

View file

@ -59,6 +59,10 @@ class ChatMemberUpdated(TelegramObject):
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`, optional): Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
via_chat_folder_invite_link (:obj:`bool`, optional): :obj:`True`, if the user joined the
chat via a chat folder invite link
.. versionadded:: NEXT.VERSION
Attributes:
chat (:class:`telegram.Chat`): Chat the user belongs to.
@ -72,6 +76,10 @@ class ChatMemberUpdated(TelegramObject):
new_chat_member (:class:`telegram.ChatMember`): New information about the chat member.
invite_link (:class:`telegram.ChatInviteLink`): Optional. Chat invite link, which was used
by the user to join the chat. For joining by invite link events only.
via_chat_folder_invite_link (:obj:`bool`): Optional. :obj:`True`, if the user joined the
chat via a chat folder invite link
.. versionadded:: NEXT.VERSION
"""
@ -82,6 +90,7 @@ class ChatMemberUpdated(TelegramObject):
"old_chat_member",
"new_chat_member",
"invite_link",
"via_chat_folder_invite_link",
)
def __init__(
@ -92,6 +101,7 @@ class ChatMemberUpdated(TelegramObject):
old_chat_member: ChatMember,
new_chat_member: ChatMember,
invite_link: ChatInviteLink = None,
via_chat_folder_invite_link: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -102,6 +112,7 @@ class ChatMemberUpdated(TelegramObject):
self.date: datetime.datetime = date
self.old_chat_member: ChatMember = old_chat_member
self.new_chat_member: ChatMember = new_chat_member
self.via_chat_folder_invite_link: Optional[bool] = via_chat_folder_invite_link
# Optionals
self.invite_link: Optional[ChatInviteLink] = invite_link

View file

@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, ClassVar, Optional, Union
from telegram import constants
from telegram._games.callbackgame import CallbackGame
from telegram._loginurl import LoginUrl
from telegram._switchinlinequerychosenchat import SwitchInlineQueryChosenChat
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
@ -111,6 +112,10 @@ class InlineKeyboardButton(TelegramObject):
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`, optional): If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
@ -122,6 +127,20 @@ class InlineKeyboardButton(TelegramObject):
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`, optional):
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
.. versionadded:: NEXT.VERSION
Tip:
This is similar to :paramref:`switch_inline_query`, but gives more control on
which chats can be selected.
Caution:
The PTB team has discovered that this field works correctly only if your Telegram
client is released after April 20th 2023.
Attributes:
text (:obj:`str`): Label text on the button.
@ -154,6 +173,10 @@ class InlineKeyboardButton(TelegramObject):
in inline mode when they are currently in a private chat with it. Especially useful
when combined with ``switch_pm*`` actions - in this case the user will be automatically
returned to the chat they switched from, skipping the chat selection screen.
Tip:
This is similar to the new parameter :paramref:`switch_inline_query_chosen_chat`,
but gives no control over which chats can be selected.
switch_inline_query_current_chat (:obj:`str`): Optional. If set, pressing the button will
insert the bot's username and the specified inline query in the current chat's input
field. Can be empty, in which case only the bot's username will be inserted. This
@ -165,7 +188,20 @@ class InlineKeyboardButton(TelegramObject):
pay (:obj:`bool`): Optional. Specify :obj:`True`, to send a Pay button. This type of button
**must** always be the **first** button in the first row and can only be used in
invoice messages.
switch_inline_query_chosen_chat (:obj:`telegram.SwitchInlineQueryChosenChat`): Optional.
If set, pressing the button will prompt the user to select one of their chats of the
specified type, open that chat and insert the bot's username and the specified inline
query in the input field.
.. versionadded:: NEXT.VERSION
Tip:
This is similar to :attr:`switch_inline_query`, but gives more control on
which chats can be selected.
Caution:
The PTB team has discovered that this field works correctly only if your Telegram
client is released after April 20th 2023.
"""
__slots__ = (
@ -178,6 +214,7 @@ class InlineKeyboardButton(TelegramObject):
"text",
"login_url",
"web_app",
"switch_inline_query_chosen_chat",
)
def __init__(
@ -191,6 +228,7 @@ class InlineKeyboardButton(TelegramObject):
pay: bool = None,
login_url: LoginUrl = None,
web_app: WebAppInfo = None,
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat = None,
*,
api_kwargs: JSONDict = None,
):
@ -207,6 +245,9 @@ class InlineKeyboardButton(TelegramObject):
self.callback_game: Optional[CallbackGame] = callback_game
self.pay: Optional[bool] = pay
self.web_app: Optional[WebAppInfo] = web_app
self.switch_inline_query_chosen_chat: Optional[
SwitchInlineQueryChosenChat
] = switch_inline_query_chosen_chat
self._id_attrs = ()
self._set_id_attrs()
@ -236,6 +277,9 @@ class InlineKeyboardButton(TelegramObject):
data["login_url"] = LoginUrl.de_json(data.get("login_url"), bot)
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
data["callback_game"] = CallbackGame.de_json(data.get("callback_game"), bot)
data["switch_inline_query_chosen_chat"] = SwitchInlineQueryChosenChat.de_json(
data.get("switch_inline_query_chosen_chat"), bot
)
return super().de_json(data=data, bot=bot)

View file

@ -23,6 +23,7 @@ from typing import TYPE_CHECKING, Callable, ClassVar, Optional, Sequence, Union
from telegram import constants
from telegram._files.location import Location
from telegram._inline.inlinequeryresultsbutton import InlineQueryResultsButton
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils.defaultvalue import DEFAULT_NONE
@ -146,6 +147,7 @@ class InlineQuery(TelegramObject):
next_offset: str = None,
switch_pm_text: str = None,
switch_pm_parameter: str = None,
button: InlineQueryResultsButton = None,
*,
current_offset: str = None,
auto_pagination: bool = False,
@ -192,6 +194,7 @@ class InlineQuery(TelegramObject):
next_offset=next_offset,
switch_pm_text=switch_pm_text,
switch_pm_parameter=switch_pm_parameter,
button=button,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,

View file

@ -0,0 +1,117 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
# pylint: disable=redefined-builtin
"""This module contains the class that represent a Telegram InlineQueryResultsButton."""
from typing import TYPE_CHECKING, ClassVar, Optional
from telegram import constants
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
from telegram._webappinfo import WebAppInfo
if TYPE_CHECKING:
from telegram import Bot
class InlineQueryResultsButton(TelegramObject):
"""This object represents a button to be shown above inline query results. You **must** use
exactly one of the optional fields.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`text`, :attr:`web_app` and :attr:`start_parameter` are equal.
Args:
text (:obj:`str`): Label text on the button.
web_app (:class:`telegram.WebAppInfo`, optional): Description of the
`Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the
user presses the button. The Web App will be able to switch back to the inline mode
using the method
`switchInlineQuery <https://core.telegram.org/bots/webapps#initializing-web-apps>`_
inside the Web App.
start_parameter (:obj:`str`, optional): Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
Example:
An inline bot that sends YouTube videos can ask the user to connect the bot to
their YouTube account to adapt search results accordingly. To do this, it displays
a 'Connect your YouTube account' button above the results, or even before showing
any. The user presses the button, switches to a private chat with the bot and, in
doing so, passes a start parameter that instructs the bot to return an OAuth link.
Once done, the bot can offer a switch_inline button so that the user can easily
return to the chat where they wanted to use the bot's inline capabilities.
Attributes:
text (:obj:`str`): Label text on the button.
web_app (:class:`telegram.WebAppInfo`): Optional. Description of the
`Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the
user presses the button. The Web App will be able to switch back to the inline mode
using the method ``web_app_switch_inline_query`` inside the Web App.
start_parameter (:obj:`str`): Optional. Deep-linking parameter for the
:guilabel:`/start` message sent to the bot when user presses the switch button.
:tg-const:`telegram.InlineQuery.MIN_SWITCH_PM_TEXT_LENGTH`-
:tg-const:`telegram.InlineQuery.MAX_SWITCH_PM_TEXT_LENGTH` characters,
only ``A-Z``, ``a-z``, ``0-9``, ``_`` and ``-`` are allowed.
"""
__slots__ = ("text", "web_app", "start_parameter")
def __init__(
self,
text: str,
web_app: WebAppInfo = None,
start_parameter: str = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Required
self.text: str = text
# Optional
self.web_app: Optional[WebAppInfo] = web_app
self.start_parameter: Optional[str] = start_parameter
self._id_attrs = (self.text, self.web_app, self.start_parameter)
self._freeze()
@classmethod
def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["InlineQueryResultsButton"]:
"""See :meth:`telegram.TelegramObject.de_json`."""
if not data:
return None
data["web_app"] = WebAppInfo.de_json(data.get("web_app"), bot)
return super().de_json(data=data, bot=bot)
MIN_START_PARAMETER_LENGTH: ClassVar[
int
] = constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`"""
MAX_START_PARAMETER_LENGTH: ClassVar[
int
] = constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH
""":const:`telegram.constants.InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`"""

View file

@ -59,6 +59,7 @@ from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.types import DVInput, FileInput, JSONDict, ODVInput, ReplyMarkup
from telegram._utils.warnings import warn
from telegram._videochat import (
VideoChatEnded,
VideoChatParticipantsInvited,
@ -69,6 +70,7 @@ from telegram._webappdata import WebAppData
from telegram._writeaccessallowed import WriteAccessAllowed
from telegram.constants import MessageAttachmentType, ParseMode
from telegram.helpers import escape_markdown
from telegram.warnings import PTBDeprecationWarning
if TYPE_CHECKING:
from telegram import (
@ -578,8 +580,13 @@ class Message(TelegramObject):
.. versionadded:: 20.1
.. |custom_emoji_formatting_note| replace:: Custom emoji entities will currently be ignored
by this function. Instead, the supplied replacement for the emoji will be used.
.. |custom_emoji_formatting_note| replace:: Custom emoji entities will be ignored by this
function. Instead, the supplied replacement for the emoji will be used.
.. |custom_emoji_md1_deprecation| replace:: Since custom emoji entities are not supported by
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method will raise a
:exc:`ValueError` in future versions instead of falling back to the supplied replacement
for the emoji.
"""
# fmt: on
@ -3317,6 +3324,10 @@ class Message(TelegramObject):
insert = f"<s>{escaped_text}</s>"
elif entity.type == MessageEntity.SPOILER:
insert = f'<span class="tg-spoiler">{escaped_text}</span>'
elif entity.type == MessageEntity.CUSTOM_EMOJI:
insert = (
f'<tg-emoji emoji-id="{entity.custom_emoji_id}">{escaped_text}</tg-emoji>'
)
else:
insert = escaped_text
@ -3355,12 +3366,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML in
the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as HTML.
@ -3374,12 +3385,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as HTML.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as HTML.
@ -3394,12 +3405,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as HTML.
"""
@ -3413,12 +3424,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
HTML. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as HTML.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as HTML.
"""
@ -3522,6 +3533,26 @@ class Message(TelegramObject):
"Spoiler entities are not supported for Markdown version 1"
)
insert = f"||{escaped_text}||"
elif entity.type == MessageEntity.CUSTOM_EMOJI:
if version == 1:
# this ensures compatibility to previous PTB versions
insert = escaped_text
warn(
"Custom emoji entities are not supported for Markdown version 1. "
"Future version of PTB will raise a ValueError instead of falling "
"back to the alternative standard emoji.",
stacklevel=3,
category=PTBDeprecationWarning,
)
else:
# This should never be needed because ids are numeric but the documentation
# specifically mentions it so here we are
custom_emoji_id = escape_markdown(
entity.custom_emoji_id,
version=version,
entity_type=MessageEntity.CUSTOM_EMOJI,
)
insert = f"![{escaped_text}](tg://emoji?id={custom_emoji_id})"
else:
insert = escaped_text
@ -3570,6 +3601,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: NEXT.VERSION
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@ -3588,12 +3622,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown
in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
"""
@ -3614,6 +3648,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: NEXT.VERSION
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
@ -3632,12 +3669,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message text with the entities formatted as Markdown.
This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message text with entities formatted as Markdown.
"""
@ -3658,6 +3695,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: NEXT.VERSION
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@ -3676,12 +3716,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown in the same way the original message was formatted.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
"""
@ -3704,6 +3744,9 @@ class Message(TelegramObject):
* |custom_emoji_formatting_note|
.. deprecated:: NEXT.VERSION
|custom_emoji_md1_deprecation|
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
@ -3722,12 +3765,12 @@ class Message(TelegramObject):
Use this if you want to retrieve the message caption with the caption entities formatted as
Markdown. This also formats :attr:`telegram.MessageEntity.URL` as a hyperlink.
Note:
|custom_emoji_formatting_note|
.. versionchanged:: 13.10
Spoiler entities are now formatted as Markdown V2.
.. versionchanged:: NEXT.VERSION
Custom emoji entities are now supported.
Returns:
:obj:`str`: Message caption with caption entities formatted as Markdown.
"""

View file

@ -0,0 +1,99 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
"""This module contains a class that represents a Telegram SwitchInlineQueryChosenChat."""
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class SwitchInlineQueryChosenChat(TelegramObject):
"""
This object represents an inline button that switches the current user to inline mode in a
chosen chat, with an optional default inline query.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`query`, :attr:`allow_user_chats`, :attr:`allow_bot_chats`,
:attr:`allow_group_chats`, and :attr:`allow_channel_chats` are equal.
.. versionadded:: NEXT.VERSION
Caution:
The PTB team has discovered that you must pass at least one of
:paramref:`allow_user_chats`, :paramref:`allow_bot_chats`, :paramref:`allow_group_chats`,
or :paramref:`allow_channel_chats` to Telegram. Otherwise, an error will be raised.
Args:
query (:obj:`str`, optional): The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with users
can be chosen.
allow_bot_chats (:obj:`bool`, optional): Pass :obj:`True`, if private chats with bots can
be chosen.
allow_group_chats (:obj:`bool`, optional): Pass :obj:`True`, if group and supergroup chats
can be chosen.
allow_channel_chats (:obj:`bool`, optional): Pass :obj:`True`, if channel chats can be
chosen.
Attributes:
query (:obj:`str`): Optional. The default inline query to be inserted in the input field.
If left empty, only the bot's username will be inserted.
allow_user_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with users can be
chosen.
allow_bot_chats (:obj:`bool`): Optional. :obj:`True`, if private chats with bots can be
chosen.
allow_group_chats (:obj:`bool`): Optional. :obj:`True`, if group and supergroup chats can
be chosen.
allow_channel_chats (:obj:`bool`): Optional. :obj:`True`, if channel chats can be chosen.
"""
__slots__ = (
"query",
"allow_user_chats",
"allow_bot_chats",
"allow_group_chats",
"allow_channel_chats",
)
def __init__(
self,
query: str = None,
allow_user_chats: bool = None,
allow_bot_chats: bool = None,
allow_group_chats: bool = None,
allow_channel_chats: bool = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
# Optional
self.query = query
self.allow_user_chats = allow_user_chats
self.allow_bot_chats = allow_bot_chats
self.allow_group_chats = allow_group_chats
self.allow_channel_chats = allow_channel_chats
self._id_attrs = (
self.query,
self.allow_user_chats,
self.allow_bot_chats,
self.allow_group_chats,
self.allow_channel_chats,
)
self._freeze()

View file

@ -17,21 +17,35 @@
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
"""This module contains objects related to the write access allowed service message."""
from typing import Optional
from telegram._telegramobject import TelegramObject
from telegram._utils.types import JSONDict
class WriteAccessAllowed(TelegramObject):
"""
This object represents a service message about a user allowing a bot added to the attachment
menu to write messages. Currently holds no information.
This object represents a service message about a user allowing a bot to write messages after
adding the bot to the attachment menu or launching a Web App from a link.
.. versionadded:: 20.0
Args:
web_app_name (:obj:`str`, optional): Name of the Web App which was launched from a link.
.. versionadded:: NEXT.VERSION
Attributes:
web_app_name (:obj:`str`): Optional. Name of the Web App which was launched from a link.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
__slots__ = ("web_app_name",)
def __init__(self, *, api_kwargs: JSONDict = None):
def __init__(self, web_app_name: str = None, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self.web_app_name: Optional[str] = web_app_name
self._freeze()

View file

@ -37,6 +37,7 @@ __all__ = [
"BotCommandLimit",
"BotCommandScopeType",
"BotDescriptionLimit",
"BotNameLimit",
"CallbackQueryLimit",
"ChatAction",
"ChatID",
@ -57,6 +58,7 @@ __all__ = [
"InlineKeyboardMarkupLimit",
"InlineQueryLimit",
"InlineQueryResultLimit",
"InlineQueryResultsButtonLimit",
"InlineQueryResultType",
"InputMediaType",
"InvoiceLimit",
@ -114,7 +116,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=6)
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=7)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -209,6 +211,21 @@ class BotDescriptionLimit(IntEnum):
"""
class BotNameLimit(IntEnum):
"""This enum contains limitations for the methods :meth:`telegram.Bot.set_my_name`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
MAX_NAME_LENGTH = 64
""":obj:`int`: Maximum length for the parameter :paramref:`~telegram.Bot.set_my_name.name` of
:meth:`telegram.Bot.set_my_name`
"""
class CallbackQueryLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.CallbackQuery`/
:meth:`telegram.Bot.answer_callback_query`. The enum members of this enumeration are instances
@ -735,11 +752,19 @@ class InlineQueryLimit(IntEnum):
MIN_SWITCH_PM_TEXT_LENGTH = 1
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of
:meth:`telegram.Bot.answer_inline_query`."""
:meth:`telegram.Bot.answer_inline_query`.
.. deprecated:: NEXT.VERSION
Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MIN_START_PARAMETER_LENGTH`.
"""
MAX_SWITCH_PM_TEXT_LENGTH = 64
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.answer_inline_query.switch_pm_parameter` parameter of
:meth:`telegram.Bot.answer_inline_query`."""
:meth:`telegram.Bot.answer_inline_query`.
.. deprecated:: NEXT.VERSION
Deprecated in favor of :attr:`InlineQueryResultsButtonLimit.MAX_START_PARAMETER_LENGTH`.
"""
class InlineQueryResultLimit(IntEnum):
@ -763,6 +788,26 @@ class InlineQueryResultLimit(IntEnum):
"""
class InlineQueryResultsButtonLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.InlineQueryResultsButton`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
MIN_START_PARAMETER_LENGTH = InlineQueryLimit.MIN_SWITCH_PM_TEXT_LENGTH
""":obj:`int`: Minimum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of
:meth:`telegram.InlineQueryResultsButton`."""
MAX_START_PARAMETER_LENGTH = InlineQueryLimit.MAX_SWITCH_PM_TEXT_LENGTH
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.InlineQueryResultsButton.start_parameter` parameter of
:meth:`telegram.InlineQueryResultsButton`."""
class InlineQueryResultType(StringEnum):
"""This enum contains the available types of :class:`telegram.InlineQueryResult`. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.

View file

@ -46,6 +46,7 @@ from telegram import (
BotCommand,
BotCommandScope,
BotDescription,
BotName,
BotShortDescription,
CallbackQuery,
Chat,
@ -60,6 +61,7 @@ from telegram import (
ForumTopic,
GameHighScore,
InlineKeyboardMarkup,
InlineQueryResultsButton,
InputMedia,
InputSticker,
Location,
@ -792,6 +794,7 @@ class ExtBot(Bot, Generic[RLARGS]):
next_offset: str = None,
switch_pm_text: str = None,
switch_pm_parameter: str = None,
button: InlineQueryResultsButton = None,
*,
current_offset: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -814,6 +817,7 @@ class ExtBot(Bot, Generic[RLARGS]):
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
button=button,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
@ -3581,6 +3585,48 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_my_name(
self,
name: str = None,
language_code: str = 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,
rate_limit_args: RLARGS = None,
) -> bool:
return await super().set_my_name(
name=name,
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def get_my_name(
self,
language_code: str = 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,
rate_limit_args: RLARGS = None,
) -> BotName:
return await super().get_my_name(
language_code=language_code,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_custom_emoji_sticker_set_thumbnail(
self,
name: str,
@ -3823,3 +3869,5 @@ class ExtBot(Bot, Generic[RLARGS]):
setStickerEmojiList = set_sticker_emoji_list
setStickerKeywords = set_sticker_keywords
setStickerMaskPosition = set_sticker_mask_position
setMyName = set_my_name
getMyName = get_my_name

View file

@ -44,23 +44,27 @@ if TYPE_CHECKING:
def escape_markdown(text: str, version: int = 1, entity_type: str = None) -> str:
"""Helper function to escape telegram markup symbols.
.. versionchanged:: NEXT.VERSION
Custom emoji entity escaping is now supported.
Args:
text (:obj:`str`): The text.
version (:obj:`int` | :obj:`str`): Use to specify the version of telegrams Markdown.
Either ``1`` or ``2``. Defaults to ``1``.
entity_type (:obj:`str`, optional): For the entity types
:tg-const:`telegram.MessageEntity.PRE`, :tg-const:`telegram.MessageEntity.CODE` and
the link part of :tg-const:`telegram.MessageEntity.TEXT_LINK`, only certain characters
need to be escaped in :tg-const:`telegram.constants.ParseMode.MARKDOWN_V2`.
See the official API documentation for details. Only valid in combination with
``version=2``, will be ignored else.
the link part of :tg-const:`telegram.MessageEntity.TEXT_LINK` and
:tg-const:`telegram.MessageEntity.CUSTOM_EMOJI`, only certain characters need to be
escaped in :tg-const:`telegram.constants.ParseMode.MARKDOWN_V2`. See the `official API
documentation <https://core.telegram.org/bots/api#formatting-options>`_ for details.
Only valid in combination with ``version=2``, will be ignored else.
"""
if int(version) == 1:
escape_chars = r"_*`["
elif int(version) == 2:
if entity_type in ["pre", "code"]:
escape_chars = r"\`"
elif entity_type == "text_link":
elif entity_type in ["text_link", "custom_emoji"]:
escape_chars = r"\)"
else:
escape_chars = r"\_*[]()~`>#+-=|{}.!"

View file

@ -19,7 +19,13 @@
import pytest
from telegram import CallbackGame, InlineKeyboardButton, LoginUrl, WebAppInfo
from telegram import (
CallbackGame,
InlineKeyboardButton,
LoginUrl,
SwitchInlineQueryChosenChat,
WebAppInfo,
)
from tests.auxil.slots import mro_slots
@ -35,6 +41,7 @@ def inline_keyboard_button():
pay=TestInlineKeyboardButtonBase.pay,
login_url=TestInlineKeyboardButtonBase.login_url,
web_app=TestInlineKeyboardButtonBase.web_app,
switch_inline_query_chosen_chat=TestInlineKeyboardButtonBase.switch_inline_query_chosen_chat, # noqa: E501
)
@ -48,6 +55,7 @@ class TestInlineKeyboardButtonBase:
pay = True
login_url = LoginUrl("http://google.com")
web_app = WebAppInfo(url="https://example.com")
switch_inline_query_chosen_chat = SwitchInlineQueryChosenChat("a_bot", True, False, True, True)
class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
@ -70,6 +78,10 @@ class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
assert inline_keyboard_button.pay == self.pay
assert inline_keyboard_button.login_url == self.login_url
assert inline_keyboard_button.web_app == self.web_app
assert (
inline_keyboard_button.switch_inline_query_chosen_chat
== self.switch_inline_query_chosen_chat
)
def test_to_dict(self, inline_keyboard_button):
inline_keyboard_button_dict = inline_keyboard_button.to_dict()
@ -95,6 +107,10 @@ class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
inline_keyboard_button_dict["login_url"] == inline_keyboard_button.login_url.to_dict()
)
assert inline_keyboard_button_dict["web_app"] == inline_keyboard_button.web_app.to_dict()
assert (
inline_keyboard_button_dict["switch_inline_query_chosen_chat"]
== inline_keyboard_button.switch_inline_query_chosen_chat.to_dict()
)
def test_de_json(self, bot):
json_dict = {
@ -107,6 +123,7 @@ class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
"web_app": self.web_app.to_dict(),
"login_url": self.login_url.to_dict(),
"pay": self.pay,
"switch_inline_query_chosen_chat": self.switch_inline_query_chosen_chat.to_dict(),
}
inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None)
@ -124,6 +141,10 @@ class TestInlineKeyboardButtonWithoutRequest(TestInlineKeyboardButtonBase):
assert inline_keyboard_button.pay == self.pay
assert inline_keyboard_button.login_url == self.login_url
assert inline_keyboard_button.web_app == self.web_app
assert (
inline_keyboard_button.switch_inline_query_chosen_chat
== self.switch_inline_query_chosen_chat
)
none = InlineKeyboardButton.de_json({}, bot)
assert none is None

View file

@ -34,6 +34,7 @@ from telegram import (
BotCommand,
BotCommandScopeChat,
BotDescription,
BotName,
BotShortDescription,
CallbackQuery,
Chat,
@ -44,6 +45,7 @@ from telegram import (
InlineKeyboardMarkup,
InlineQueryResultArticle,
InlineQueryResultDocument,
InlineQueryResultsButton,
InlineQueryResultVoice,
InputFile,
InputMessageContent,
@ -76,7 +78,7 @@ from telegram.error import BadRequest, InvalidToken, NetworkError
from telegram.ext import ExtBot, InvalidCallbackData
from telegram.helpers import escape_markdown
from telegram.request import BaseRequest, HTTPXRequest, RequestData
from telegram.warnings import PTBUserWarning
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
from tests.auxil.bot_method_checks import check_defaults_handling
from tests.auxil.ci_bots import FALLBACKS
from tests.auxil.envvars import GITHUB_ACTION, TEST_WITH_OPT_DEPS
@ -655,10 +657,11 @@ class TestBotWithoutRequest:
)
# TODO: Needs improvement. We need incoming inline query to test answer.
async def test_answer_inline_query(self, monkeypatch, bot, raw_bot):
@pytest.mark.parametrize("button_type", ["start", "web_app", "backward_compat"])
async def test_answer_inline_query(self, monkeypatch, bot, raw_bot, button_type):
# For now just test that our internals pass the correct data
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters == {
expected = {
"cache_time": 300,
"results": [
{
@ -685,12 +688,22 @@ class TestBotWithoutRequest:
},
],
"next_offset": "42",
"switch_pm_parameter": "start_pm",
"inline_query_id": 1234,
"is_personal": True,
"switch_pm_text": "switch pm",
}
if button_type in ["start", "backward_compat"]:
button_dict = {"text": "button_text", "start_parameter": "start_parameter"}
else:
button_dict = {
"text": "button_text",
"web_app": {"url": "https://python-telegram-bot.org"},
}
expected["button"] = button_dict
return request_data.parameters == expected
results = [
InlineQueryResultArticle("11", "first", InputTextMessageContent("first")),
InlineQueryResultArticle("12", "second", InputMessageContentDWPP("second")),
@ -705,6 +718,17 @@ class TestBotWithoutRequest:
),
]
if button_type == "start":
button = InlineQueryResultsButton(
text="button_text", start_parameter="start_parameter"
)
elif button_type == "web_app":
button = InlineQueryResultsButton(
text="button_text", web_app=WebAppInfo("https://python-telegram-bot.org")
)
else:
button = None
copied_results = copy.copy(results)
ext_bot = bot
for bot in (ext_bot, raw_bot):
@ -717,8 +741,11 @@ class TestBotWithoutRequest:
cache_time=300,
is_personal=True,
next_offset="42",
switch_pm_text="switch pm",
switch_pm_parameter="start_pm",
switch_pm_text="button_text" if button_type == "backward_compat" else None,
switch_pm_parameter="start_parameter"
if button_type == "backward_compat"
else None,
button=button,
)
# 1)
@ -739,6 +766,43 @@ class TestBotWithoutRequest:
monkeypatch.delattr(bot.request, "post")
@pytest.mark.parametrize("bot_class", ["Bot", "ExtBot"])
async def test_answer_inline_query_deprecated_args(
self, monkeypatch, recwarn, bot_class, bot, raw_bot
):
async def mock_post(*args, **kwargs):
return True
bot = raw_bot if bot_class == "Bot" else bot
monkeypatch.setattr(bot.request, "post", mock_post)
with pytest.raises(
TypeError, match="6.7, the parameter `button is mutually exclusive to the deprecated"
):
await bot.answer_inline_query(
inline_query_id="123",
results=[],
button=True,
switch_pm_text="text",
switch_pm_parameter="param",
)
recwarn.clear()
assert await bot.answer_inline_query(
inline_query_id="123",
results=[],
switch_pm_text="text",
switch_pm_parameter="parameter",
)
assert len(recwarn) == 1
assert recwarn[-1].category is PTBDeprecationWarning
assert str(recwarn[-1].message).startswith(
"Since Bot API 6.7, the parameters `switch_pm_text` and `switch_pm_parameter` are "
"deprecated"
)
assert recwarn[-1].filename == __file__, "stacklevel is incorrect!"
async def test_answer_inline_query_no_default_parse_mode(self, monkeypatch, bot):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
return request_data.parameters == {
@ -769,10 +833,8 @@ class TestBotWithoutRequest:
},
],
"next_offset": "42",
"switch_pm_parameter": "start_pm",
"inline_query_id": 1234,
"is_personal": True,
"switch_pm_text": "switch pm",
}
monkeypatch.setattr(bot.request, "post", make_assertion)
@ -797,8 +859,6 @@ class TestBotWithoutRequest:
cache_time=300,
is_personal=True,
next_offset="42",
switch_pm_text="switch pm",
switch_pm_parameter="start_pm",
)
# make sure that the results were not edited in-place
assert results == copied_results
@ -862,10 +922,8 @@ class TestBotWithoutRequest:
},
],
"next_offset": "42",
"switch_pm_parameter": "start_pm",
"inline_query_id": 1234,
"is_personal": True,
"switch_pm_text": "switch pm",
}
monkeypatch.setattr(default_bot.request, "post", make_assertion)
@ -890,8 +948,6 @@ class TestBotWithoutRequest:
cache_time=300,
is_personal=True,
next_offset="42",
switch_pm_text="switch pm",
switch_pm_parameter="start_pm",
)
# make sure that the results were not edited in-place
assert results == copied_results
@ -1680,6 +1736,80 @@ class TestBotWithoutRequest:
assert warning.filename == __file__, "wrong stacklevel!"
assert warning.category is PTBUserWarning
async def test_set_get_my_name(self, bot, monkeypatch):
"""We only test that we pass the correct values to TG since this endpoint is heavily
rate limited which makes automated tests rather infeasible."""
default_name = "default_bot_name"
en_name = "en_bot_name"
de_name = "de_bot_name"
# We predefine the responses that we would TG expect to send us
set_stack = asyncio.Queue()
get_stack = asyncio.Queue()
await set_stack.put({"name": default_name})
await set_stack.put({"name": en_name, "language_code": "en"})
await set_stack.put({"name": de_name, "language_code": "de"})
await get_stack.put({"name": default_name, "language_code": None})
await get_stack.put({"name": en_name, "language_code": "en"})
await get_stack.put({"name": de_name, "language_code": "de"})
await set_stack.put({"name": default_name})
await set_stack.put({"language_code": "en"})
await set_stack.put({"language_code": "de"})
await get_stack.put({"name": default_name, "language_code": None})
await get_stack.put({"name": default_name, "language_code": "en"})
await get_stack.put({"name": default_name, "language_code": "de"})
async def post(url, request_data: RequestData, *args, **kwargs):
# The mock-post now just fetches the predefined responses from the queues
if "setMyName" in url:
expected = await set_stack.get()
assert request_data.json_parameters == expected
set_stack.task_done()
return True
bot_name = await get_stack.get()
if "language_code" in request_data.json_parameters:
assert request_data.json_parameters == {"language_code": bot_name["language_code"]}
else:
assert request_data.json_parameters == {}
get_stack.task_done()
return bot_name
monkeypatch.setattr(bot.request, "post", post)
# Set the names
assert all(
await asyncio.gather(
bot.set_my_name(default_name),
bot.set_my_name(en_name, language_code="en"),
bot.set_my_name(de_name, language_code="de"),
)
)
# Check that they were set correctly
assert await asyncio.gather(
bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de")
) == [
BotName(default_name),
BotName(en_name),
BotName(de_name),
]
# Delete the names
assert all(
await asyncio.gather(
bot.set_my_name(default_name),
bot.set_my_name(None, language_code="en"),
bot.set_my_name(None, language_code="de"),
)
)
# Check that they were deleted correctly
assert await asyncio.gather(
bot.get_my_name(), bot.get_my_name("en"), bot.get_my_name("de")
) == 3 * [BotName(default_name)]
class TestBotWithRequest:
"""

56
tests/test_botname.py Normal file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import BotName
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def bot_name(bot):
return BotName(TestBotNameBase.name)
class TestBotNameBase:
name = "This is a test name"
class TestBotNameWithoutRequest(TestBotNameBase):
def test_slot_behaviour(self, bot_name):
for attr in bot_name.__slots__:
assert getattr(bot_name, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(bot_name)) == len(set(mro_slots(bot_name))), "duplicate slot"
def test_to_dict(self, bot_name):
bot_name_dict = bot_name.to_dict()
assert isinstance(bot_name_dict, dict)
assert bot_name_dict["name"] == self.name
def test_equality(self):
a = BotName(self.name)
b = BotName(self.name)
c = BotName("text.com")
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)

View file

@ -79,7 +79,7 @@ def invite_link(user):
@pytest.fixture(scope="module")
def chat_member_updated(user, chat, old_chat_member, new_chat_member, invite_link, time):
return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link)
return ChatMemberUpdated(chat, user, time, old_chat_member, new_chat_member, invite_link, True)
class TestChatMemberUpdatedBase:
@ -113,6 +113,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link is None
assert chat_member_updated.via_chat_folder_invite_link is None
def test_de_json_all_args(
self, bot, user, time, invite_link, chat, old_chat_member, new_chat_member
@ -124,6 +125,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
"old_chat_member": old_chat_member.to_dict(),
"new_chat_member": new_chat_member.to_dict(),
"invite_link": invite_link.to_dict(),
"via_chat_folder_invite_link": True,
}
chat_member_updated = ChatMemberUpdated.de_json(json_dict, bot)
@ -136,6 +138,7 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
assert chat_member_updated.old_chat_member == old_chat_member
assert chat_member_updated.new_chat_member == new_chat_member
assert chat_member_updated.invite_link == invite_link
assert chat_member_updated.via_chat_folder_invite_link is True
def test_de_json_localization(
self, bot, raw_bot, tz_bot, user, chat, old_chat_member, new_chat_member, time, invite_link
@ -178,6 +181,10 @@ class TestChatMemberUpdatedWithoutRequest(TestChatMemberUpdatedBase):
== chat_member_updated.new_chat_member.to_dict()
)
assert chat_member_updated_dict["invite_link"] == chat_member_updated.invite_link.to_dict()
assert (
chat_member_updated_dict["via_chat_folder_invite_link"]
== chat_member_updated.via_chat_folder_invite_link
)
def test_equality(self, time, old_chat_member, new_chat_member, invite_link):
a = ChatMemberUpdated(

View file

@ -32,8 +32,9 @@ class TestHelpers:
("_italic_", r"\_italic\_"),
("`code`", r"\`code\`"),
("[text_link](https://github.com/)", r"\[text\_link](https://github.com/)"),
("![👍](tg://emoji?id=1)", r"!\[👍](tg://emoji?id=1)"),
],
ids=["bold", "italic", "code", "text_link"],
ids=["bold", "italic", "code", "text_link", "custom_emoji_id"],
)
def test_escape_markdown(self, test_str, expected):
assert expected == helpers.escape_markdown(test_str)
@ -68,13 +69,16 @@ class TestHelpers:
test_str, version=2, entity_type=MessageEntity.CODE
)
def test_escape_markdown_v2_text_link(self):
def test_escape_markdown_v2_links(self):
test_str = "https://url.containing/funny)cha)\\ra\\)cter\\s"
expected_str = "https://url.containing/funny\\)cha\\)\\\\ra\\\\\\)cter\\\\s"
assert expected_str == helpers.escape_markdown(
test_str, version=2, entity_type=MessageEntity.TEXT_LINK
)
assert expected_str == helpers.escape_markdown(
test_str, version=2, entity_type=MessageEntity.CUSTOM_EMOJI
)
def test_markdown_invalid_version(self):
with pytest.raises(ValueError, match="Markdown version must be either"):

View file

@ -0,0 +1,83 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import InlineQueryResultsButton, WebAppInfo
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def inline_query_results_button():
return InlineQueryResultsButton(
text=TestInlineQueryResultsButtonBase.text,
start_parameter=TestInlineQueryResultsButtonBase.start_parameter,
web_app=TestInlineQueryResultsButtonBase.web_app,
)
class TestInlineQueryResultsButtonBase:
text = "text"
start_parameter = "start_parameter"
web_app = WebAppInfo(url="https://python-telegram-bot.org")
class TestInlineQueryResultsButtonWithoutRequest(TestInlineQueryResultsButtonBase):
def test_slot_behaviour(self, inline_query_results_button):
inst = inline_query_results_button
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_to_dict(self, inline_query_results_button):
inline_query_results_button_dict = inline_query_results_button.to_dict()
assert isinstance(inline_query_results_button_dict, dict)
assert inline_query_results_button_dict["text"] == self.text
assert inline_query_results_button_dict["start_parameter"] == self.start_parameter
assert inline_query_results_button_dict["web_app"] == self.web_app.to_dict()
def test_de_json(self, bot):
assert InlineQueryResultsButton.de_json(None, bot) is None
assert InlineQueryResultsButton.de_json({}, bot) is None
json_dict = {
"text": self.text,
"start_parameter": self.start_parameter,
"web_app": self.web_app.to_dict(),
}
inline_query_results_button = InlineQueryResultsButton.de_json(json_dict, bot)
assert inline_query_results_button.text == self.text
assert inline_query_results_button.start_parameter == self.start_parameter
assert inline_query_results_button.web_app == self.web_app
def test_equality(self):
a = InlineQueryResultsButton(self.text, self.start_parameter, self.web_app)
b = InlineQueryResultsButton(self.text, self.start_parameter, self.web_app)
c = InlineQueryResultsButton(self.text, "", self.web_app)
d = InlineQueryResultsButton(self.text, self.start_parameter, None)
assert a == b
assert hash(a) == hash(b)
assert a is not b
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)

View file

@ -59,6 +59,7 @@ from telegram import (
from telegram._utils.datetime import UTC
from telegram.constants import ChatAction, ParseMode
from telegram.ext import Defaults
from telegram.warnings import PTBDeprecationWarning
from tests._passport.test_passport import RAW_PASSPORT_DATA
from tests.auxil.bot_method_checks import (
check_defaults_handling,
@ -322,10 +323,11 @@ class TestMessageBase:
{"length": 9, "offset": 101, "type": "strikethrough"},
{"length": 10, "offset": 129, "type": "pre", "language": "python"},
{"length": 7, "offset": 141, "type": "spoiler"},
{"length": 2, "offset": 150, "type": "custom_emoji", "custom_emoji_id": "1"},
]
test_text_v2 = (
r"Test for <bold, ita_lic, \`code, links, text-mention and `\pre. "
"http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled."
"http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled. 👍."
)
test_message = Message(
message_id=1,
@ -513,7 +515,8 @@ class TestMessageWithoutRequest(TestMessageBase):
r"<pre>`\pre</pre>. http://google.com "
"and <i>bold <b>nested in <s>strk&gt;trgh</s> nested in</b> italic</i>. "
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>.'
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.'
)
text_html = self.test_message_v2.text_html
assert text_html == test_html_string
@ -532,7 +535,8 @@ class TestMessageWithoutRequest(TestMessageBase):
r'<pre>`\pre</pre>. <a href="http://google.com">http://google.com</a> '
"and <i>bold <b>nested in <s>strk&gt;trgh</s> nested in</b> italic</i>. "
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>.'
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.'
)
text_html = self.test_message_v2.text_html_urled
assert text_html == test_html_string
@ -553,7 +557,7 @@ class TestMessageWithoutRequest(TestMessageBase):
"[links](http://github.com/abc\\\\\\)def), "
"[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. "
r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. "
"```python\nPython pre```\\. ||Spoiled||\\."
"```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\."
)
text_markdown = self.test_message_v2.text_markdown_v2
assert text_markdown == test_md_string
@ -603,7 +607,8 @@ class TestMessageWithoutRequest(TestMessageBase):
"[links](http://github.com/abc\\\\\\)def), "
"[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. "
r"[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ "
"nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\."
"nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\. "
"![👍](tg://emoji?id=1)\\."
)
text_markdown = self.test_message_v2.text_markdown_v2_urled
assert text_markdown == test_md_string
@ -634,17 +639,72 @@ class TestMessageWithoutRequest(TestMessageBase):
@pytest.mark.parametrize(
"type_",
argvalues=[
"text_html",
"text_html_urled",
"text_markdown",
"text_markdown_urled",
],
)
def test_text_custom_emoji_md_v1(self, type_, recwarn):
text = "Look a custom emoji: 😎"
expected = "Look a custom emoji: 😎"
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
length=2,
custom_emoji_id="5472409228461217725",
)
message = Message(
1,
from_user=self.from_user,
date=self.date,
chat=self.chat,
text=text,
entities=[emoji_entity],
)
assert expected == getattr(message, type_)
assert len(recwarn) == 1
assert recwarn[0].category is PTBDeprecationWarning
assert str(recwarn[0].message).startswith(
"Custom emoji entities are not supported for Markdown version 1"
)
assert recwarn[0].filename == __file__
@pytest.mark.parametrize(
"type_",
argvalues=[
"text_markdown_v2",
"text_markdown_v2_urled",
],
)
def test_text_custom_emoji(self, type_):
def test_text_custom_emoji_md_v2(self, type_):
text = "Look a custom emoji: 😎"
expected = "Look a custom emoji: 😎"
expected = "Look a custom emoji: ![😎](tg://emoji?id=5472409228461217725)"
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
length=2,
custom_emoji_id="5472409228461217725",
)
message = Message(
1,
from_user=self.from_user,
date=self.date,
chat=self.chat,
text=text,
entities=[emoji_entity],
)
assert expected == message[type_]
@pytest.mark.parametrize(
"type_",
argvalues=[
"text_html",
"text_html_urled",
],
)
def test_text_custom_emoji_html(self, type_):
text = "Look a custom emoji: 😎"
expected = 'Look a custom emoji: <tg-emoji emoji-id="5472409228461217725">😎</tg-emoji>'
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
@ -670,7 +730,8 @@ class TestMessageWithoutRequest(TestMessageBase):
r"<pre>`\pre</pre>. http://google.com "
"and <i>bold <b>nested in <s>strk&gt;trgh</s> nested in</b> italic</i>. "
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>.'
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.'
)
caption_html = self.test_message_v2.caption_html
assert caption_html == test_html_string
@ -689,7 +750,8 @@ class TestMessageWithoutRequest(TestMessageBase):
r'<pre>`\pre</pre>. <a href="http://google.com">http://google.com</a> '
"and <i>bold <b>nested in <s>strk&gt;trgh</s> nested in</b> italic</i>. "
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>.'
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.'
)
caption_html = self.test_message_v2.caption_html_urled
assert caption_html == test_html_string
@ -710,7 +772,7 @@ class TestMessageWithoutRequest(TestMessageBase):
"[links](http://github.com/abc\\\\\\)def), "
"[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. "
r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. "
"```python\nPython pre```\\. ||Spoiled||\\."
"```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\."
)
caption_markdown = self.test_message_v2.caption_markdown_v2
assert caption_markdown == test_md_string
@ -737,7 +799,8 @@ class TestMessageWithoutRequest(TestMessageBase):
"[links](http://github.com/abc\\\\\\)def), "
"[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. "
r"[http://google\.com](http://google.com) and _bold *nested in ~strk\>trgh~ "
"nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\."
"nested in* italic_\\. ```python\nPython pre```\\. ||Spoiled||\\. "
"![👍](tg://emoji?id=1)\\."
)
caption_markdown = self.test_message_v2.caption_markdown_v2_urled
assert caption_markdown == test_md_string
@ -773,17 +836,72 @@ class TestMessageWithoutRequest(TestMessageBase):
@pytest.mark.parametrize(
"type_",
argvalues=[
"caption_html",
"caption_html_urled",
"caption_markdown",
"caption_markdown_urled",
],
)
def test_caption_custom_emoji_md_v1(self, type_, recwarn):
caption = "Look a custom emoji: 😎"
expected = "Look a custom emoji: 😎"
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
length=2,
custom_emoji_id="5472409228461217725",
)
message = Message(
1,
from_user=self.from_user,
date=self.date,
chat=self.chat,
caption=caption,
caption_entities=[emoji_entity],
)
assert expected == getattr(message, type_)
assert len(recwarn) == 1
assert recwarn[0].category is PTBDeprecationWarning
assert str(recwarn[0].message).startswith(
"Custom emoji entities are not supported for Markdown version 1"
)
assert recwarn[0].filename == __file__
@pytest.mark.parametrize(
"type_",
argvalues=[
"caption_markdown_v2",
"caption_markdown_v2_urled",
],
)
def test_caption_custom_emoji(self, type_):
def test_caption_custom_emoji_md_v2(self, type_):
caption = "Look a custom emoji: 😎"
expected = "Look a custom emoji: 😎"
expected = "Look a custom emoji: ![😎](tg://emoji?id=5472409228461217725)"
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
length=2,
custom_emoji_id="5472409228461217725",
)
message = Message(
1,
from_user=self.from_user,
date=self.date,
chat=self.chat,
caption=caption,
caption_entities=[emoji_entity],
)
assert expected == message[type_]
@pytest.mark.parametrize(
"type_",
argvalues=[
"caption_html",
"caption_html_urled",
],
)
def test_caption_custom_emoji_html(self, type_):
caption = "Look a custom emoji: 😎"
expected = 'Look a custom emoji: <tg-emoji emoji-id="5472409228461217725">😎</tg-emoji>'
emoji_entity = MessageEntity(
type=MessageEntity.CUSTOM_EMOJI,
offset=21,
@ -955,7 +1073,7 @@ class TestMessageWithoutRequest(TestMessageBase):
"[links](http://github.com/abc\\\\\\)def), "
"[text\\-mention](tg://user?id=123456789) and ```\\`\\\\pre```\\. "
r"http://google\.com and _bold *nested in ~strk\>trgh~ nested in* italic_\. "
"```python\nPython pre```\\. ||Spoiled||\\."
"```python\nPython pre```\\. ||Spoiled||\\. ![👍](tg://emoji?id=1)\\."
)
async def make_assertion(*_, **kwargs):
@ -995,7 +1113,8 @@ class TestMessageWithoutRequest(TestMessageBase):
r"<pre>`\pre</pre>. http://google.com "
"and <i>bold <b>nested in <s>strk&gt;trgh</s> nested in</b> italic</i>. "
'<pre><code class="python">Python pre</code></pre>. '
'<span class="tg-spoiler">Spoiled</span>.'
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.'
)
async def make_assertion(*_, **kwargs):

View file

@ -154,6 +154,7 @@ BACKWARDS_COMPAT_KWARGS = {
"thumb_url",
},
"InlineQueryResult(Game|Gif|Mpeg4Gif)": {"thumb_mime_type"},
"answer_inline_query": {"switch_pm_text", "switch_pm_parameter"},
}

View file

@ -0,0 +1,87 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2023
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser Public License for more details.
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import pytest
from telegram import SwitchInlineQueryChosenChat
from tests.auxil.slots import mro_slots
@pytest.fixture(scope="module")
def switch_inline_query_chosen_chat():
return SwitchInlineQueryChosenChat(
query=TestSwitchInlineQueryChosenChatBase.query,
allow_user_chats=TestSwitchInlineQueryChosenChatBase.allow_user_chats,
allow_bot_chats=TestSwitchInlineQueryChosenChatBase.allow_bot_chats,
allow_channel_chats=TestSwitchInlineQueryChosenChatBase.allow_channel_chats,
allow_group_chats=TestSwitchInlineQueryChosenChatBase.allow_group_chats,
)
class TestSwitchInlineQueryChosenChatBase:
query = "query"
allow_user_chats = True
allow_bot_chats = True
allow_channel_chats = False
allow_group_chats = True
class TestSwitchInlineQueryChosenChat(TestSwitchInlineQueryChosenChatBase):
def test_slot_behaviour(self, switch_inline_query_chosen_chat):
inst = switch_inline_query_chosen_chat
for attr in inst.__slots__:
assert getattr(inst, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(inst)) == len(set(mro_slots(inst))), "duplicate slot"
def test_expected_values(self, switch_inline_query_chosen_chat):
assert switch_inline_query_chosen_chat.query == self.query
assert switch_inline_query_chosen_chat.allow_user_chats == self.allow_user_chats
assert switch_inline_query_chosen_chat.allow_bot_chats == self.allow_bot_chats
assert switch_inline_query_chosen_chat.allow_channel_chats == self.allow_channel_chats
assert switch_inline_query_chosen_chat.allow_group_chats == self.allow_group_chats
def test_to_dict(self, switch_inline_query_chosen_chat):
siqcc = switch_inline_query_chosen_chat.to_dict()
assert isinstance(siqcc, dict)
assert siqcc["query"] == switch_inline_query_chosen_chat.query
assert siqcc["allow_user_chats"] == switch_inline_query_chosen_chat.allow_user_chats
assert siqcc["allow_bot_chats"] == switch_inline_query_chosen_chat.allow_bot_chats
assert siqcc["allow_channel_chats"] == switch_inline_query_chosen_chat.allow_channel_chats
assert siqcc["allow_group_chats"] == switch_inline_query_chosen_chat.allow_group_chats
def test_equality(self):
siqcc = SwitchInlineQueryChosenChat
a = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats)
b = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats)
c = siqcc(self.query, self.allow_user_chats)
d = siqcc("", self.allow_user_chats, self.allow_bot_chats)
e = siqcc(self.query, self.allow_user_chats, self.allow_bot_chats, self.allow_group_chats)
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
assert a != e
assert hash(a) != hash(e)