Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Dmitry Kolomatskiy <58207913+lemontree210@users.noreply.github.com>
Co-authored-by: Hinrich Mahler <22366557+Bibo-Joshi@users.noreply.github.com>
This commit is contained in:
Harshil 2023-01-01 20:00:49 +04:00 committed by GitHub
parent f408b1a2dd
commit 606773d8f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 1379 additions and 59 deletions

File diff suppressed because one or more lines are too long

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.3-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.4-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.2** are supported.
All types and methods of the Telegram Bot API **6.4** 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.3-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-6.4-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.2** are supported.
All types and methods of the Telegram Bot API **6.4** are supported.
Installing
==========

View file

@ -267,16 +267,26 @@
* - :meth:`~telegram.Bot.close_forum_topic`
- Used for closing a forum topic
* - :meth:`~telegram.Bot.close_general_forum_topic`
- Used for closing the general forum topic
* - :meth:`~telegram.Bot.create_forum_topic`
- Used to create a topic
* - :meth:`~telegram.Bot.delete_forum_topic`
- Used for deleting a forum topic
* - :meth:`~telegram.Bot.edit_forum_topic`
- Used to edit a topic
* - :meth:`~telegram.Bot.reopen_forum_topic`
- Used to reopen a topic
* - :meth:`~telegram.Bot.edit_general_forum_topic`
- Used to edit the general topic
* - :meth:`~telegram.Bot.get_forum_topic_icon_stickers`
- Used to get custom emojis to use as topic icons
* - :meth:`~telegram.Bot.hide_general_forum_topic`
- Used to hide the general topic
* - :meth:`~telegram.Bot.unhide_general_forum_topic`
- Used to unhide the general topic
* - :meth:`~telegram.Bot.reopen_forum_topic`
- Used to reopen a topic
* - :meth:`~telegram.Bot.reopen_general_forum_topic`
- Used to reopen the general topic
* - :meth:`~telegram.Bot.unpin_all_forum_topic_messages`
- Used to unpin all messages in a forum topic

View file

@ -39,7 +39,10 @@ Available Types
telegram.forumtopic
telegram.forumtopicclosed
telegram.forumtopiccreated
telegram.forumtopicedited
telegram.forumtopicreopened
telegram.generalforumtopichidden
telegram.generalforumtopicunhidden
telegram.inlinekeyboardbutton
telegram.inlinekeyboardmarkup
telegram.inputfile
@ -84,4 +87,5 @@ Available Types
telegram.webappdata
telegram.webappinfo
telegram.webhookinfo
telegram.writeaccessallowed

View file

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

View file

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

View file

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

View file

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

View file

@ -70,9 +70,12 @@ __all__ = ( # Keep this alphabetically ordered
"ForumTopic",
"ForumTopicClosed",
"ForumTopicCreated",
"ForumTopicEdited",
"ForumTopicReopened",
"Game",
"GameHighScore",
"GeneralForumTopicHidden",
"GeneralForumTopicUnhidden",
"helpers",
"IdDocumentData",
"InlineKeyboardButton",
@ -176,6 +179,7 @@ __all__ = ( # Keep this alphabetically ordered
"WebAppData",
"WebAppInfo",
"WebhookInfo",
"WriteAccessAllowed",
)
@ -234,7 +238,15 @@ from ._files.video import Video
from ._files.videonote import VideoNote
from ._files.voice import Voice
from ._forcereply import ForceReply
from ._forumtopic import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened
from ._forumtopic import (
ForumTopic,
ForumTopicClosed,
ForumTopicCreated,
ForumTopicEdited,
ForumTopicReopened,
GeneralForumTopicHidden,
GeneralForumTopicUnhidden,
)
from ._games.callbackgame import CallbackGame
from ._games.game import Game
from ._games.gamehighscore import GameHighScore
@ -326,6 +338,7 @@ from ._videochat import (
from ._webappdata import WebAppData
from ._webappinfo import WebAppInfo
from ._webhookinfo import WebhookInfo
from ._writeaccessallowed import WriteAccessAllowed
#: :obj:`str`: The version of the `python-telegram-bot` library as string.
#: To get detailed information about the version number, please use :data:`__version_info__`

View file

@ -919,6 +919,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -973,6 +974,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the photo needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the photo, when uploading a
@ -991,6 +996,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
data: JSONDict = {
"chat_id": chat_id,
"photo": self._parse_file_input(photo, PhotoSize, filename=filename),
"has_spoiler": has_spoiler,
}
return await self._send_message(
@ -1363,6 +1369,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1438,6 +1445,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
.. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the video needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the video, when uploading a
@ -1461,6 +1472,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"height": height,
"supports_streaming": supports_streaming,
"thumb": self._parse_file_input(thumb, attach=True) if thumb else None,
"has_spoiler": has_spoiler,
}
return await self._send_message(
@ -1617,6 +1629,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1687,6 +1700,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True` if the animation needs to be
covered with a spoiler animation.
.. versionadded:: 20.0
Keyword Args:
filename (:obj:`str`, optional): Custom file name for the animation, when uploading a
@ -1709,6 +1726,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"width": width,
"height": height,
"thumb": self._parse_file_input(thumb, attach=True) if thumb else None,
"has_spoiler": has_spoiler,
}
return await self._send_message(
@ -2553,6 +2571,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
self,
chat_id: Union[str, int],
action: str,
message_thread_id: int = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2574,6 +2593,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
action(:obj:`str`): Type of action to broadcast. Choose one, depending on what the user
is about to receive. For convenience look at the constants in
:class:`telegram.constants.ChatAction`.
message_thread_id (:obj:`int`, optional): |message_thread_id_arg|
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@ -2582,7 +2604,11 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id, "action": action}
data: JSONDict = {
"chat_id": chat_id,
"action": action,
"message_thread_id": message_thread_id,
}
result = await self._post(
"sendChatAction",
data,
@ -3913,7 +3939,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> ChatMember:
"""Use this method to get information about a member of a chat.
"""Use this method to get information about a member of a chat. The method is guaranteed
to work only if the bot is an administrator in the chat.
.. seealso:: :meth:`telegram.Chat.get_member`
@ -6970,8 +6997,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
self,
chat_id: Union[str, int],
message_thread_id: int,
name: str,
icon_custom_emoji_id: str,
name: str = None,
icon_custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -6993,12 +7020,14 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
message_thread_id (:obj:`int`): |message_thread_id|
name (:obj:`str`): New topic name,
name (:obj:`str`, optional): New topic name,
:tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`-
:tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters.
icon_custom_emoji_id (:obj:`str`): New unique identifier of the custom emoji shown as
the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` to get all
allowed custom emoji identifiers.
:tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. If
not specified or empty, the current name of the topic will be kept.
icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji
shown as the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers`
to get all allowed custom emoji identifiers.Pass an empty string to remove the
icon. If not specified, the current icon will be kept.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@ -7214,6 +7243,222 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
api_kwargs=api_kwargs,
)
@_log
async def edit_general_forum_topic(
self,
chat_id: Union[str, int],
name: str,
*,
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 edit the name of the 'General' topic in a forum supergroup chat. The bot
must be an administrator in the chat for this to work and must have
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
.. seealso:: :meth:`telegram.Chat.edit_general_forum_topic`
.. versionadded:: 20.0
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
name (:obj:`str`): New topic name,
:tg-const:`telegram.constants.ForumTopicLimit.MIN_NAME_LENGTH`-
:tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id, "name": name}
return await self._post(
"editGeneralForumTopic",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def close_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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 close an open 'General' topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
.. seealso:: :meth:`telegram.Chat.close_general_forum_topic`
.. versionadded:: 20.0
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id}
return await self._post(
"closeGeneralForumTopic",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def reopen_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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 reopen a closed 'General' topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
The topic will be automatically unhidden if it was hidden.
.. seealso:: :meth:`telegram.Chat.reopen_general_forum_topic`
.. versionadded:: 20.0
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id}
return await self._post(
"reopenGeneralForumTopic",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def hide_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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 hide the 'General' topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
The topic will be automatically closed if it was open.
.. seealso:: :meth:`telegram.Chat.hide_general_forum_topic`
.. versionadded:: 20.0
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id}
return await self._post(
"hideGeneralForumTopic",
data,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
@_log
async def unhide_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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 unhide the 'General' topic in a forum supergroup chat. The bot must
be an administrator in the chat for this to work and must have
:attr:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
.. seealso:: :meth:`telegram.Chat.unhide_general_forum_topic`
.. versionadded:: 20.0
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {"chat_id": chat_id}
return await self._post(
"unhideGeneralForumTopic",
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, 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}
@ -7422,3 +7667,13 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`delete_forum_topic`"""
unpinAllForumTopicMessages = unpin_all_forum_topic_messages
"""Alias for :meth:`unpin_all_forum_topic_messages`"""
editGeneralForumTopic = edit_general_forum_topic
"""Alias for :meth:`edit_general_forum_topic`"""
closeGeneralForumTopic = close_general_forum_topic
"""Alias for :meth:`close_general_forum_topic`"""
reopenGeneralForumTopic = reopen_general_forum_topic
"""Alias for :meth:`reopen_general_forum_topic`"""
hideGeneralForumTopic = hide_general_forum_topic
"""Alias for :meth:`hide_general_forum_topic`"""
unhideGeneralForumTopic = unhide_general_forum_topic
"""Alias for :meth:`unhide_general_forum_topic`"""

View file

@ -164,6 +164,16 @@ class Chat(TelegramObject):
status of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
has_aggressive_anti_spam_enabled (:obj:`bool`, optional): :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
has_hidden_members (:obj:`bool`, optional): :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
Attributes:
@ -247,6 +257,16 @@ class Chat(TelegramObject):
status of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
has_aggressive_anti_spam_enabled (:obj:`bool`): Optional. :obj:`True`, if aggressive
anti-spam checks are enabled in the supergroup. The field is only available to chat
administrators. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
has_hidden_members (:obj:`bool`): Optional. :obj:`True`, if non-administrators can only
get the list of bots and administrators in the chat. Returned only in
:meth:`telegram.Bot.get_chat`.
.. versionadded:: 20.0
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
@ -279,6 +299,8 @@ class Chat(TelegramObject):
"is_forum",
"active_usernames",
"emoji_status_custom_emoji_id",
"has_hidden_members",
"has_aggressive_anti_spam_enabled",
)
SENDER: ClassVar[str] = constants.ChatType.SENDER
@ -323,6 +345,8 @@ class Chat(TelegramObject):
is_forum: bool = None,
active_usernames: Sequence[str] = None,
emoji_status_custom_emoji_id: str = None,
has_aggressive_anti_spam_enabled: bool = None,
has_hidden_members: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -357,6 +381,8 @@ class Chat(TelegramObject):
self.is_forum = is_forum
self.active_usernames = parse_sequence_arg(active_usernames)
self.emoji_status_custom_emoji_id = emoji_status_custom_emoji_id
self.has_aggressive_anti_spam_enabled = has_aggressive_anti_spam_enabled
self.has_hidden_members = has_hidden_members
self._id_attrs = (self.id,)
@ -1330,6 +1356,7 @@ class Chat(TelegramObject):
async def send_chat_action(
self,
action: str,
message_thread_id: int = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1350,6 +1377,7 @@ class Chat(TelegramObject):
return await self.get_bot().send_chat_action(
chat_id=self.id,
action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -1372,6 +1400,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1408,6 +1437,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
)
async def send_contact(
@ -1818,6 +1848,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1858,6 +1889,7 @@ class Chat(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def send_sticker(
@ -1977,6 +2009,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2018,6 +2051,7 @@ class Chat(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def send_video_note(
@ -2663,8 +2697,8 @@ class Chat(TelegramObject):
async def edit_forum_topic(
self,
message_thread_id: int,
name: str,
icon_custom_emoji_id: str,
name: str = None,
icon_custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2825,6 +2859,164 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs,
)
async def edit_general_forum_topic(
self,
name: str,
*,
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:
"""Shortcut for::
await bot.edit_general_forum_topic(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.edit_general_forum_topic`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().edit_general_forum_topic(
chat_id=self.id,
name=name,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def close_general_forum_topic(
self,
*,
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:
"""Shortcut for::
await bot.close_general_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.close_general_forum_topic`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().close_general_forum_topic(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def reopen_general_forum_topic(
self,
*,
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:
"""Shortcut for::
await bot.reopen_general_forum_topic(
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.reopen_general_forum_topic`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().reopen_general_forum_topic(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def hide_general_forum_topic(
self,
*,
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:
"""Shortcut for::
await bot.hide_general_forum_topic(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.hide_general_forum_topic`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().hide_general_forum_topic(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def unhide_general_forum_topic(
self,
*,
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:
"""Shortcut for::
await bot.unhide_general_forum_topic (
chat_id=update.effective_chat.id, *args, **kwargs
)
For the documentation of the arguments, please see
:meth:`telegram.Bot.unhide_general_forum_topic`.
.. versionadded:: 20.0
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().unhide_general_forum_topic(
chat_id=self.id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def get_menu_button(
self,
*,

View file

@ -150,6 +150,10 @@ class InputMediaAnimation(InputMedia):
width (:obj:`int`, optional): Animation width.
height (:obj:`int`, optional): Animation height.
duration (:obj:`int`, optional): Animation duration in seconds.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the animation needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.ANIMATION`.
@ -168,10 +172,13 @@ class InputMediaAnimation(InputMedia):
width (:obj:`int`): Optional. Animation width.
height (:obj:`int`): Optional. Animation height.
duration (:obj:`int`): Optional. Animation duration in seconds.
has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the animation is covered with a
spoiler animation.
.. versionadded:: 20.0
"""
__slots__ = ("duration", "height", "thumb", "width")
__slots__ = ("duration", "height", "thumb", "width", "has_spoiler")
def __init__(
self,
@ -184,6 +191,7 @@ class InputMediaAnimation(InputMedia):
duration: int = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -210,6 +218,7 @@ class InputMediaAnimation(InputMedia):
self.width = width
self.height = height
self.duration = duration
self.has_spoiler = has_spoiler
class InputMediaPhoto(InputMedia):
@ -237,6 +246,10 @@ class InputMediaPhoto(InputMedia):
.. versionchanged:: 20.0
|sequenceclassargs|
has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the photo needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.PHOTO`.
@ -251,10 +264,13 @@ class InputMediaPhoto(InputMedia):
* |tupleclassattrs|
* |alwaystuple|
has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the photo is covered with a
spoiler animation.
.. versionadded:: 20.0
"""
__slots__ = ()
__slots__ = ("has_spoiler",)
def __init__(
self,
@ -263,6 +279,7 @@ class InputMediaPhoto(InputMedia):
parse_mode: ODVInput[str] = DEFAULT_NONE,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -278,7 +295,8 @@ class InputMediaPhoto(InputMedia):
api_kwargs=api_kwargs,
)
self._freeze()
with self._unfrozen():
self.has_spoiler = has_spoiler
class InputMediaVideo(InputMedia):
@ -325,6 +343,10 @@ class InputMediaVideo(InputMedia):
.. versionchanged:: 13.2
Accept :obj:`bytes` as input.
has_spoiler (:obj:`bool`, optional): Pass :obj:`True`, if the video needs to be covered
with a spoiler animation.
.. versionadded:: 20.0
Attributes:
type (:obj:`str`): :tg-const:`telegram.constants.InputMediaType.VIDEO`.
@ -345,10 +367,13 @@ class InputMediaVideo(InputMedia):
supports_streaming (:obj:`bool`): Optional. :obj:`True`, if the uploaded video is
suitable for streaming.
thumb (:class:`telegram.InputFile`): Optional. |thumbdocstringbase|
has_spoiler (:obj:`bool`): Optional. :obj:`True`, if the video is covered with a
spoiler animation.
.. versionadded:: 20.0
"""
__slots__ = ("duration", "height", "thumb", "supports_streaming", "width")
__slots__ = ("duration", "height", "thumb", "supports_streaming", "width", "has_spoiler")
def __init__(
self,
@ -362,6 +387,7 @@ class InputMediaVideo(InputMedia):
thumb: FileInput = None,
caption_entities: Sequence[MessageEntity] = None,
filename: str = None,
has_spoiler: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -390,6 +416,7 @@ class InputMediaVideo(InputMedia):
self.duration = duration
self.thumb = self._parse_thumb_input(thumb)
self.supports_streaming = supports_streaming
self.has_spoiler = has_spoiler
class InputMediaAudio(InputMedia):

View file

@ -142,3 +142,73 @@ class ForumTopicReopened(TelegramObject):
super().__init__(api_kwargs=api_kwargs)
self._freeze()
class ForumTopicEdited(TelegramObject):
"""
This object represents a service message about an edited forum topic.
Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`name` and :attr:`icon_custom_emoji_id` are equal.
.. versionadded:: 20.0
Args:
name (:obj:`str`, optional): New name of the topic, if it was edited.
icon_custom_emoji_id (:obj:`str`, optional): New identifier of the custom emoji shown as
the topic icon, if it was edited; an empty string if the icon was removed.
Attributes:
name (:obj:`str`): Optional. New name of the topic, if it was edited.
icon_custom_emoji_id (:obj:`str`): Optional. New identifier of the custom emoji shown as
the topic icon, if it was edited; an empty string if the icon was removed.
"""
__slots__ = ("name", "icon_custom_emoji_id")
def __init__(
self,
name: str = None,
icon_custom_emoji_id: str = None,
*,
api_kwargs: JSONDict = None,
):
super().__init__(api_kwargs=api_kwargs)
self.name = name
self.icon_custom_emoji_id = icon_custom_emoji_id
self._id_attrs = (self.name, self.icon_custom_emoji_id)
self._freeze()
class GeneralForumTopicHidden(TelegramObject):
"""
This object represents a service message about General forum topic hidden in the chat.
Currently holds no information.
.. versionadded:: 20.0
"""
__slots__ = ()
def __init__(self, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self._freeze()
class GeneralForumTopicUnhidden(TelegramObject):
"""
This object represents a service message about General forum topic unhidden in the chat.
Currently holds no information.
.. versionadded:: 20.0
"""
__slots__ = ()
def __init__(self, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self._freeze()

View file

@ -36,7 +36,14 @@ from telegram._files.venue import Venue
from telegram._files.video import Video
from telegram._files.videonote import VideoNote
from telegram._files.voice import Voice
from telegram._forumtopic import ForumTopicClosed, ForumTopicCreated, ForumTopicReopened
from telegram._forumtopic import (
ForumTopicClosed,
ForumTopicCreated,
ForumTopicEdited,
ForumTopicReopened,
GeneralForumTopicHidden,
GeneralForumTopicUnhidden,
)
from telegram._games.game import Game
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
@ -59,6 +66,7 @@ from telegram._videochat import (
VideoChatStarted,
)
from telegram._webappdata import WebAppData
from telegram._writeaccessallowed import WriteAccessAllowed
from telegram.constants import MessageAttachmentType, ParseMode
from telegram.helpers import escape_markdown
@ -277,15 +285,35 @@ class Message(TelegramObject):
.. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message:
forum topic created
forum topic created.
.. versionadded:: 20.0
forum_topic_closed (:class:`telegram.ForumTopicClosed`, optional): Service message:
forum topic closed
forum topic closed.
.. versionadded:: 20.0
forum_topic_reopened (:class:`telegram.ForumTopicReopened`, optional): Service message:
forum topic reopened
forum topic reopened.
.. versionadded:: 20.0
forum_topic_edited (:class:`telegram.ForumTopicEdited`, optional): Service message:
forum topic edited.
.. versionadded:: 20.0
general_forum_topic_hidden (:class:`telegram.GeneralForumTopicHidden`, optional):
Service message: General forum topic hidden.
.. versionadded:: 20.0
general_forum_topic_unhidden (:class:`telegram.GeneralForumTopicUnhidden`, optional):
Service message: General forum topic unhidden.
.. versionadded:: 20.0
write_access_allowed (:class:`telegram.WriteAccessAllowed`, optional): Service message:
the user allowed the bot added to the attachment menu to write messages.
.. versionadded:: 20.0
has_media_spoiler (:obj:`bool`, optional): :obj:`True`, if the message media is covered
by a spoiler animation.
.. versionadded:: 20.0
@ -484,15 +512,35 @@ class Message(TelegramObject):
.. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message:
forum topic created
forum topic created.
.. versionadded:: 20.0
forum_topic_closed (:class:`telegram.ForumTopicClosed`): Optional. Service message:
forum topic closed
forum topic closed.
.. versionadded:: 20.0
forum_topic_reopened (:class:`telegram.ForumTopicReopened`): Optional. Service message:
forum topic reopened
forum topic reopened.
.. versionadded:: 20.0
forum_topic_edited (:class:`telegram.ForumTopicEdited`): Optional. Service message:
forum topic edited.
.. versionadded:: 20.0
general_forum_topic_hidden (:class:`telegram.GeneralForumTopicHidden`): Optional.
Service message: General forum topic hidden.
.. versionadded:: 20.0
general_forum_topic_unhidden (:class:`telegram.GeneralForumTopicUnhidden`): Optional.
Service message: General forum topic unhidden.
.. versionadded:: 20.0
write_access_allowed (:class:`telegram.WriteAccessAllowed`): Optional. Service message:
the user allowed the bot added to the attachment menu to write messages.
.. versionadded:: 20.0
has_media_spoiler (:obj:`bool`): Optional. :obj:`True`, if the message media is covered
by a spoiler animation.
.. versionadded:: 20.0
@ -567,6 +615,11 @@ class Message(TelegramObject):
"forum_topic_created",
"forum_topic_closed",
"forum_topic_reopened",
"forum_topic_edited",
"general_forum_topic_hidden",
"general_forum_topic_unhidden",
"write_access_allowed",
"has_media_spoiler",
)
def __init__(
@ -635,6 +688,11 @@ class Message(TelegramObject):
forum_topic_created: ForumTopicCreated = None,
forum_topic_closed: ForumTopicClosed = None,
forum_topic_reopened: ForumTopicReopened = None,
forum_topic_edited: ForumTopicEdited = None,
general_forum_topic_hidden: GeneralForumTopicHidden = None,
general_forum_topic_unhidden: GeneralForumTopicUnhidden = None,
write_access_allowed: WriteAccessAllowed = None,
has_media_spoiler: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -706,6 +764,11 @@ class Message(TelegramObject):
self.forum_topic_created = forum_topic_created
self.forum_topic_closed = forum_topic_closed
self.forum_topic_reopened = forum_topic_reopened
self.forum_topic_edited = forum_topic_edited
self.general_forum_topic_hidden = general_forum_topic_hidden
self.general_forum_topic_unhidden = general_forum_topic_unhidden
self.write_access_allowed = write_access_allowed
self.has_media_spoiler = has_media_spoiler
self._effective_attachment = DEFAULT_NONE
@ -805,6 +868,16 @@ class Message(TelegramObject):
data["forum_topic_reopened"] = ForumTopicReopened.de_json(
data.get("forum_topic_reopened"), bot
)
data["forum_topic_edited"] = ForumTopicEdited.de_json(data.get("forum_topic_edited"), bot)
data["general_forum_topic_hidden"] = GeneralForumTopicHidden.de_json(
data.get("general_forum_topic_hidden"), bot
)
data["general_forum_topic_unhidden"] = GeneralForumTopicUnhidden.de_json(
data.get("general_forum_topic_unhidden"), bot
)
data["write_access_allowed"] = WriteAccessAllowed.de_json(
data.get("write_access_allowed"), bot
)
return super().de_json(data=data, bot=bot)
@ -1203,6 +1276,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
quote: bool = None,
@ -1246,6 +1320,7 @@ class Message(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
)
async def reply_audio(
@ -1390,6 +1465,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
quote: bool = None,
@ -1438,6 +1514,7 @@ class Message(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def reply_sticker(
@ -1506,6 +1583,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
quote: bool = None,
@ -1554,6 +1632,7 @@ class Message(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def reply_video_note(
@ -1980,6 +2059,7 @@ class Message(TelegramObject):
async def reply_chat_action(
self,
action: str,
message_thread_id: int = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2001,6 +2081,7 @@ class Message(TelegramObject):
"""
return await self.get_bot().send_chat_action(
chat_id=self.chat_id,
message_thread_id=message_thread_id,
action=action,
read_timeout=read_timeout,
write_timeout=write_timeout,
@ -2815,8 +2896,8 @@ class Message(TelegramObject):
async def edit_forum_topic(
self,
name: str,
icon_custom_emoji_id: str,
name: str = None,
icon_custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,

View file

@ -68,6 +68,11 @@ class ReplyKeyboardMarkup(TelegramObject):
characters.
.. versionadded:: 13.7
is_persistent (:obj:`bool`, optional): Requests clients to always show the keyboard when
the regular keyboard is hidden. Defaults to :obj:`False`, in which case the custom
keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
Attributes:
keyboard (Tuple[Tuple[:class:`telegram.KeyboardButton`]]): Array of button rows,
@ -97,6 +102,11 @@ class ReplyKeyboardMarkup(TelegramObject):
characters.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard when
the regular keyboard is hidden. If :obj:`False`, the custom keyboard can be hidden and
opened with a keyboard icon.
.. versionadded:: 20.0
"""
@ -106,6 +116,7 @@ class ReplyKeyboardMarkup(TelegramObject):
"resize_keyboard",
"one_time_keyboard",
"input_field_placeholder",
"is_persistent",
)
def __init__(
@ -115,6 +126,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = None,
selective: bool = None,
input_field_placeholder: str = None,
is_persistent: bool = None,
*,
api_kwargs: JSONDict = None,
):
@ -136,6 +148,7 @@ class ReplyKeyboardMarkup(TelegramObject):
self.one_time_keyboard = one_time_keyboard
self.selective = selective
self.input_field_placeholder = input_field_placeholder
self.is_persistent = is_persistent
self._id_attrs = (self.keyboard,)
@ -149,6 +162,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
@ -182,6 +196,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
return cls(
[[button]],
@ -189,6 +208,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)
@ -200,6 +220,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
@ -236,6 +257,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
return cls(
@ -244,6 +270,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)
@ -255,6 +282,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False,
selective: bool = False,
input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object,
) -> "ReplyKeyboardMarkup":
"""Shortcut for::
@ -291,6 +319,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active.
.. versionadded:: 13.7
is_persistent (:obj:`bool`): Optional. Requests clients to always show the keyboard
when the regular keyboard is hidden. Defaults to :obj:`False`, in which case the
custom keyboard can be hidden and opened with a keyboard icon.
.. versionadded:: 20.0
"""
button_grid = [[button] for button in button_column]
@ -300,6 +333,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard,
selective=selective,
input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type]
)

View file

@ -427,6 +427,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -463,6 +464,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
)
async def send_media_group(
@ -575,6 +577,7 @@ class User(TelegramObject):
async def send_chat_action(
self,
action: str,
message_thread_id: int = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -595,6 +598,7 @@ class User(TelegramObject):
return await self.get_bot().send_chat_action(
chat_id=self.id,
action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -955,6 +959,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -995,6 +1000,7 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def send_sticker(
@ -1056,6 +1062,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1097,6 +1104,7 @@ class User(TelegramObject):
filename=filename,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
)
async def send_venue(

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# 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 objects related to the write access allowed service message."""
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.
.. versionadded:: 20.0
"""
__slots__ = ()
def __init__(self, *, api_kwargs: JSONDict = None):
super().__init__(api_kwargs=api_kwargs)
self._freeze()

View file

@ -111,7 +111,7 @@ class _BotAPIVersion(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=3)
BOT_API_VERSION_INFO = _BotAPIVersion(major=6, minor=4)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@ -1568,14 +1568,22 @@ class ForumTopicLimit(IntEnum):
__slots__ = ()
MIN_NAME_LENGTH = 1
""":obj:`int`: Minimum length of a :obj:`str` passed as the
:paramref:`~telegram.Bot.create_forum_topic.name` parameter of
:meth:`telegram.Bot.create_forum_topic` and :paramref:`~telegram.Bot.edit_forum_topic.name`
parameter of :meth:`telegram.Bot.edit_forum_topic`.
""":obj:`int`: Minimum length of a :obj:`str` passed as:
* :paramref:`~telegram.Bot.create_forum_topic.name` parameter of
:meth:`telegram.Bot.create_forum_topic`
* :paramref:`~telegram.Bot.edit_forum_topic.name` parameter of
:meth:`telegram.Bot.edit_forum_topic`
* :paramref:`~telegram.Bot.edit_general_forum_topic.name` parameter of
:meth:`telegram.Bot.edit_general_forum_topic`
"""
MAX_NAME_LENGTH = 128
""":obj:`int`: Maximum length of a :obj:`str` passed as the
:paramref:`~telegram.Bot.create_forum_topic.name` parameter of
:meth:`telegram.Bot.create_forum_topic` and :paramref:`~telegram.Bot.edit_forum_topic.name`
parameter of :meth:`telegram.Bot.edit_forum_topic`.
""":obj:`int`: Maximum length of a :obj:`str` passed as:
* :paramref:`~telegram.Bot.create_forum_topic.name` parameter of
:meth:`telegram.Bot.create_forum_topic`
* :paramref:`~telegram.Bot.edit_forum_topic.name` parameter of
:meth:`telegram.Bot.edit_forum_topic`
* :paramref:`~telegram.Bot.edit_general_forum_topic.name` parameter of
:meth:`telegram.Bot.edit_general_forum_topic`
"""

View file

@ -1264,8 +1264,8 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[str, int],
message_thread_id: int,
name: str,
icon_custom_emoji_id: str,
name: str = None,
icon_custom_emoji_id: str = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1286,6 +1286,28 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def edit_general_forum_topic(
self,
chat_id: Union[str, int],
name: str,
*,
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().edit_general_forum_topic(
chat_id=chat_id,
name=name,
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 edit_message_caption(
self,
chat_id: Union[str, int] = None,
@ -1862,6 +1884,26 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def close_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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().close_general_forum_topic(
chat_id=chat_id,
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 create_forum_topic(
self,
chat_id: Union[str, int],
@ -1888,6 +1930,66 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def reopen_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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().reopen_general_forum_topic(
chat_id=chat_id,
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 hide_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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().hide_general_forum_topic(
chat_id=chat_id,
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 unhide_general_forum_topic(
self,
chat_id: Union[str, int],
*,
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().unhide_general_forum_topic(
chat_id=chat_id,
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 pin_chat_message(
self,
chat_id: Union[str, int],
@ -2045,6 +2147,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2070,6 +2173,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@ -2132,6 +2236,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self,
chat_id: Union[str, int],
action: str,
message_thread_id: int = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2143,6 +2248,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().send_chat_action(
chat_id=chat_id,
action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@ -2519,6 +2625,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2540,6 +2647,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@ -2706,6 +2814,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None,
has_spoiler: bool = None,
*,
filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2732,6 +2841,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities,
protect_content=protect_content,
message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename,
read_timeout=read_timeout,
write_timeout=write_timeout,
@ -3414,3 +3524,8 @@ class ExtBot(Bot, Generic[RLARGS]):
reopenForumTopic = reopen_forum_topic
deleteForumTopic = delete_forum_topic
unpinAllForumTopicMessages = unpin_all_forum_topic_messages
editGeneralForumTopic = edit_general_forum_topic
closeGeneralForumTopic = close_general_forum_topic
reopenGeneralForumTopic = reopen_general_forum_topic
hideGeneralForumTopic = hide_general_forum_topic
unhideGeneralForumTopic = unhide_general_forum_topic

View file

@ -58,6 +58,7 @@ __all__ = (
"FORWARDED",
"ForwardedFrom",
"GAME",
"HAS_MEDIA_SPOILER",
"HAS_PROTECTED_CONTENT",
"INVOICE",
"IS_AUTOMATIC_FORWARD",
@ -1383,6 +1384,20 @@ GAME = _Game(name="filters.GAME")
"""Messages that contain :attr:`telegram.Message.game`."""
class _HasMediaSpoiler(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.has_media_spoiler)
HAS_MEDIA_SPOILER = _HasMediaSpoiler(name="filters.HAS_MEDIA_SPOILER")
"""Messages that contain :attr:`telegram.Message.has_media_spoiler`.
.. versionadded:: 20.0
"""
class _HasProtectedContent(MessageFilter):
__slots__ = ()
@ -1721,6 +1736,10 @@ class StatusUpdate:
or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update)
or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update)
or StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update)
or StatusUpdate.FORUM_TOPIC_EDITED.check_update(update)
or StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN.check_update(update)
or StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN.check_update(update)
or StatusUpdate.WRITE_ACCESS_ALLOWED.check_update(update)
)
ALL = _All(name="filters.StatusUpdate.ALL")
@ -1783,6 +1802,18 @@ class StatusUpdate:
.. versionadded:: 20.0
"""
class _ForumTopicEdited(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.forum_topic_edited)
FORUM_TOPIC_EDITED = _ForumTopicEdited(name="filters.StatusUpdate.FORUM_TOPIC_EDITED")
"""Messages that contain :attr:`telegram.Message.forum_topic_edited`.
.. versionadded:: 20.0
"""
class _ForumTopicReopened(MessageFilter):
__slots__ = ()
@ -1795,6 +1826,34 @@ class StatusUpdate:
.. versionadded:: 20.0
"""
class _GeneralForumTopicHidden(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.general_forum_topic_hidden)
GENERAL_FORUM_TOPIC_HIDDEN = _GeneralForumTopicHidden(
name="filters.StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN"
)
"""Messages that contain :attr:`telegram.Message.general_forum_topic_hidden`.
.. versionadded:: 20.0
"""
class _GeneralForumTopicUnhidden(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.general_forum_topic_unhidden)
GENERAL_FORUM_TOPIC_UNHIDDEN = _GeneralForumTopicUnhidden(
name="filters.StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN"
)
"""Messages that contain :attr:`telegram.Message.general_forum_topic_unhidden`.
.. versionadded:: 20.0
"""
class _LeftChatMember(MessageFilter):
__slots__ = ()
@ -1945,6 +2004,18 @@ class StatusUpdate:
.. versionadded:: 20.0
"""
class _WriteAccessAllowed(MessageFilter):
__slots__ = ()
def filter(self, message: Message) -> bool:
return bool(message.write_access_allowed)
WRITE_ACCESS_ALLOWED = _WriteAccessAllowed(name="filters.StatusUpdate.WRITE_ACCESS_ALLOWED")
"""Messages that contain :attr:`telegram.Message.write_access_allowed`.
.. versionadded:: 20.0
"""
class Sticker:
"""Filters messages which contain a sticker.

View file

@ -27,15 +27,15 @@ import random
# purposes than testing.
FALLBACKS = (
"W3sidG9rZW4iOiAiNTc5Njk0NzE0OkFBRnBLOHc2emtrVXJENHhTZVl3RjNNTzhlLTRHcm1jeTdjIiwgInBheW1lbnRfc"
"HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2lkIjogIjY3NTY2Nj"
"IyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE"
"1OTQwNCIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0"
"cyBmYWxsYmFjayAxIiwgImJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzFfYm90In0sIHsidG9rZW4iOiAiNTU4M"
"Tk0MDY2OkFBRndEUElGbHpHVWxDYVdIdFRPRVg0UkZyWDh1OURNcWZvIiwgInBheW1lbnRfcHJvdmlkZXJfdG9rZW4iOi"
"AiMjg0Njg1MDYzOlRFU1Q6WWpFd09EUXdNVEZtTkRjeSIsICJjaGF0X2lkIjogIjY3NTY2NjIyNCIsICJzdXBlcl9ncm9"
"1cF9pZCI6ICItMTAwMTIyMTIxNjgzMCIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTYxOTE1OTQwNCIsICJjaGFubmVs"
"X2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIiwgImJvdF9uYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgI"
"mJvdF91c2VybmFtZSI6ICJAcHRiX2ZhbGxiYWNrXzJfYm90In1d "
"HJvdmlkZXJfdG9rZW4iOiAiMjg0Njg1MDYzOlRFU1Q6TmpRME5qWmxOekk1WWpKaSIsICJjaGF0X2 lkIjogIjY3NTY2N"
"jIyNCIsICJzdXBlcl9ncm91cF9pZCI6ICItMTAwMTMxMDkxMTEzNSIsICJmb3J1bV9ncm91cF9pZCI6ICItMTAwMTgzOD"
"AwNDU3NyIsICJjaGFubmVsX2lkIjogIkBweXRob250ZWxlZ3JhbWJvdHRlc3RzIi wgIm5hbWUiOiAiUFRCIHRlc3RzIG"
"ZhbGxiYWNrIDEiLCAidXNlcm5hbWUiOiAiQHB0Yl9mYWxsYmFja18xX2JvdCJ9LCB7InRva2VuIjogIjU1ODE5NDA2Njp"
"BQUZ3RFBJRmx6R1VsQ2FXSHRUT0VYNFJGclg4dTlETXFmbyIsIC JwYXltZW50X3Byb3ZpZGVyX3Rva2VuIjogIjI4NDY"
"4NTA2MzpURVNUOllqRXdPRFF3TVRGbU5EY3kiLCAiY2hhdF9pZCI6ICI2NzU2NjYyMjQiLCAic3VwZXJfZ3JvdXBfaWQi"
"OiAiLTEwMDEyMjEyMTY4MzAiLCAiZm9ydW1fZ3 JvdXBfaWQiOiAiLTEwMDE4NTc4NDgzMTQiLCAiY2hhbm5lbF9pZCI6"
"ICJAcHl0aG9udGVsZWdyYW1ib3R0ZXN0cyIsICJuYW1lIjogIlBUQiB0ZXN0cyBmYWxsYmFjayAyIiwgInVzZXJuYW1lI"
"jogIkBwdGJfZmFsbGJhY2tfMl9ib3QifV0="
)
GITHUB_ACTION = os.getenv("GITHUB_ACTION", None)

View file

@ -93,6 +93,7 @@ class TestAnimation:
disable_notification=False,
protect_content=True,
thumb=thumb_file,
has_spoiler=True,
)
assert isinstance(message.animation, Animation)
@ -106,6 +107,10 @@ class TestAnimation:
assert message.animation.thumb.width == self.width
assert message.animation.thumb.height == self.height
assert message.has_protected_content
try:
assert message.has_media_spoiler
except AssertionError:
pytest.xfail("This is a bug on Telegram's end")
@pytest.mark.flaky(3, 1)
async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch):

View file

@ -967,6 +967,18 @@ class TestBot:
with pytest.raises(BadRequest, match="Wrong parameter action"):
await bot.send_chat_action(chat_id, "unknown action")
async def test_send_chat_action_all_args(self, bot, chat_id, provider_token, monkeypatch):
async def make_assertion(*args, **_):
kwargs = args[1]
return (
kwargs["chat_id"] == chat_id
and kwargs["action"] == "action"
and kwargs["message_thread_id"] == 1
)
monkeypatch.setattr(bot, "_post", make_assertion)
assert await bot.send_chat_action(chat_id, "action", 1)
@pytest.mark.asyncio
async def test_answer_web_app_query(self, bot, raw_bot, monkeypatch):
params = False
@ -2525,7 +2537,7 @@ class TestBot:
# set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers
# are tested in the test_sticker module.
# get_forum_topic_icon_stickers, edit_forum_topic, etc...
# get_forum_topic_icon_stickers, edit_forum_topic, general_forum etc...
# are tested in the test_forum module.
async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id):

View file

@ -51,6 +51,8 @@ def chat(bot):
is_forum=True,
active_usernames=TestChat.active_usernames,
emoji_status_custom_emoji_id=TestChat.emoji_status_custom_emoji_id,
has_aggressive_anti_spam_enabled=TestChat.has_aggressive_anti_spam_enabled,
has_hidden_members=TestChat.has_hidden_members,
)
chat.set_bot(bot)
chat._unfreeze()
@ -82,6 +84,8 @@ class TestChat:
is_forum = True
active_usernames = ["These", "Are", "Usernames!"]
emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
has_aggressive_anti_spam_enabled = True
has_hidden_members = True
def test_slot_behaviour(self, chat, mro_slots):
for attr in chat.__slots__:
@ -112,6 +116,8 @@ class TestChat:
"is_forum": self.is_forum,
"active_usernames": self.active_usernames,
"emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id,
"has_aggressive_anti_spam_enabled": self.has_aggressive_anti_spam_enabled,
"has_hidden_members": self.has_hidden_members,
}
chat = Chat.de_json(json_dict, bot)
@ -141,6 +147,8 @@ class TestChat:
assert chat.is_forum == self.is_forum
assert chat.active_usernames == tuple(self.active_usernames)
assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id
assert chat.has_aggressive_anti_spam_enabled == self.has_aggressive_anti_spam_enabled
assert chat.has_hidden_members == self.has_hidden_members
def test_to_dict(self, chat):
chat_dict = chat.to_dict()
@ -166,6 +174,10 @@ class TestChat:
assert chat_dict["is_forum"] == chat.is_forum
assert chat_dict["active_usernames"] == list(chat.active_usernames)
assert chat_dict["emoji_status_custom_emoji_id"] == chat.emoji_status_custom_emoji_id
assert (
chat_dict["has_aggressive_anti_spam_enabled"] == chat.has_aggressive_anti_spam_enabled
)
assert chat_dict["has_hidden_members"] == chat.has_hidden_members
def test_always_tuples_attributes(self):
chat = Chat(
@ -1032,6 +1044,111 @@ class TestChat:
monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion)
assert await chat.unpin_all_forum_topic_messages(message_thread_id=42)
async def test_edit_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id and kwargs["name"] == "WhatAName"
assert check_shortcut_signature(
Chat.edit_general_forum_topic,
Bot.edit_general_forum_topic,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.edit_general_forum_topic,
chat.get_bot(),
"edit_general_forum_topic",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(chat.edit_general_forum_topic, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "edit_general_forum_topic", make_assertion)
assert await chat.edit_general_forum_topic(name="WhatAName")
async def test_close_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(
Chat.close_general_forum_topic,
Bot.close_general_forum_topic,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.close_general_forum_topic,
chat.get_bot(),
"close_general_forum_topic",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(chat.close_general_forum_topic, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "close_general_forum_topic", make_assertion)
assert await chat.close_general_forum_topic()
async def test_reopen_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(
Chat.reopen_general_forum_topic,
Bot.reopen_general_forum_topic,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.reopen_general_forum_topic,
chat.get_bot(),
"reopen_general_forum_topic",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(chat.reopen_general_forum_topic, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "reopen_general_forum_topic", make_assertion)
assert await chat.reopen_general_forum_topic()
async def test_hide_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(
Chat.hide_general_forum_topic,
Bot.hide_general_forum_topic,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.hide_general_forum_topic,
chat.get_bot(),
"hide_general_forum_topic",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(chat.hide_general_forum_topic, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "hide_general_forum_topic", make_assertion)
assert await chat.hide_general_forum_topic()
async def test_unhide_general_forum_topic(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return kwargs["chat_id"] == chat.id
assert check_shortcut_signature(
Chat.unhide_general_forum_topic,
Bot.unhide_general_forum_topic,
["chat_id"],
[],
)
assert await check_shortcut_call(
chat.unhide_general_forum_topic,
chat.get_bot(),
"unhide_general_forum_topic",
shortcut_kwargs=["chat_id"],
)
assert await check_defaults_handling(chat.unhide_general_forum_topic, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "unhide_general_forum_topic", make_assertion)
assert await chat.unhide_general_forum_topic()
def test_mention_html(self):
with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):
chat = Chat(id=1, type="foo")

View file

@ -1026,6 +1026,26 @@ class TestFilters:
assert filters.StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update)
update.message.forum_topic_reopened = None
update.message.forum_topic_edited = "topic_edited"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.FORUM_TOPIC_EDITED.check_update(update)
update.message.forum_topic_edited = None
update.message.general_forum_topic_hidden = "topic_hidden"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.GENERAL_FORUM_TOPIC_HIDDEN.check_update(update)
update.message.general_forum_topic_hidden = None
update.message.general_forum_topic_unhidden = "topic_unhidden"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.GENERAL_FORUM_TOPIC_UNHIDDEN.check_update(update)
update.message.general_forum_topic_unhidden = None
update.message.write_access_allowed = "allowed"
assert filters.StatusUpdate.ALL.check_update(update)
assert filters.StatusUpdate.WRITE_ACCESS_ALLOWED.check_update(update)
update.message.write_access_allowed = None
def test_filters_forwarded(self, update):
assert not filters.FORWARDED.check_update(update)
update.message.forward_date = datetime.datetime.utcnow()
@ -1815,6 +1835,11 @@ class TestFilters:
update.message.is_topic_message = True
assert filters.IS_TOPIC_MESSAGE.check_update(update)
def test_filters_has_media_spoiler(self, update):
assert not filters.HAS_MEDIA_SPOILER.check_update(update)
update.message.has_media_spoiler = True
assert filters.HAS_MEDIA_SPOILER.check_update(update)
def test_filters_has_protected_content(self, update):
assert not filters.HAS_PROTECTED_CONTENT.check_update(update)
update.message.has_protected_content = True

View file

@ -16,9 +16,20 @@
#
# 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 datetime
import pytest
from telegram import ForumTopic, ForumTopicClosed, ForumTopicCreated, ForumTopicReopened, Sticker
from telegram import (
ForumTopic,
ForumTopicClosed,
ForumTopicCreated,
ForumTopicEdited,
ForumTopicReopened,
GeneralForumTopicHidden,
GeneralForumTopicUnhidden,
Sticker,
)
TEST_MSG_TEXT = "Topics are forever"
TEST_TOPIC_ICON_COLOR = 0x6FB9F0
@ -139,7 +150,9 @@ class TestForumTopic:
assert a != e
assert hash(a) != hash(e)
@pytest.mark.flaky(3, 1)
@pytest.mark.flaky(3, 1)
class TestForumMethods:
async def test_create_forum_topic(self, real_topic):
result = real_topic
assert isinstance(result, ForumTopic)
@ -161,7 +174,6 @@ class TestForumTopic:
)
assert result is True, "Failed to delete forum topic"
@pytest.mark.flaky(3, 1)
async def test_get_forum_topic_icon_stickers(self, bot):
emoji_sticker_list = await bot.get_forum_topic_icon_stickers()
first_sticker = emoji_sticker_list[0]
@ -194,7 +206,6 @@ class TestForumTopic:
assert result is True, "Failed to edit forum topic"
# no way of checking the edited name, just the boolean result
@pytest.mark.flaky(3, 1)
async def test_send_message_to_topic(self, bot, forum_group_id, real_topic):
message_thread_id = real_topic.message_thread_id
@ -223,7 +234,6 @@ class TestForumTopic:
)
assert result is True, "Failed to reopen forum topic"
@pytest.mark.xfail(reason="Can fail due to race conditions in GH actions CI")
async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic):
message_thread_id = real_topic.message_thread_id
@ -244,6 +254,43 @@ class TestForumTopic:
)
assert result is True, "Failed to unpin all the messages in forum topic"
async def test_edit_general_forum_topic(self, bot, forum_group_id):
result = await bot.edit_general_forum_topic(
chat_id=forum_group_id,
name=f"GENERAL_{datetime.datetime.now().timestamp()}",
)
assert result is True, "Failed to edit general forum topic"
# no way of checking the edited name, just the boolean result
async def test_close_and_reopen_general_forum_topic(self, bot, forum_group_id):
result = await bot.close_general_forum_topic(
chat_id=forum_group_id,
)
assert result is True, "Failed to close general forum topic"
result = await bot.reopen_general_forum_topic(
chat_id=forum_group_id,
)
assert result is True, "Failed to reopen general forum topic"
async def test_hide_and_unhide_general_forum_topic(self, bot, forum_group_id):
result = await bot.hide_general_forum_topic(
chat_id=forum_group_id,
)
assert result is True, "Failed to hide general forum topic"
result = await bot.unhide_general_forum_topic(
chat_id=forum_group_id,
)
assert result is True, "Failed to unhide general forum topic"
# hiding the general topic also closes it, so we reopen it
result = await bot.reopen_general_forum_topic(
chat_id=forum_group_id,
)
assert result is True, "Failed to reopen general forum topic"
@pytest.fixture
def topic_created():
@ -333,3 +380,95 @@ class TestForumTopicReopened:
action = ForumTopicReopened()
action_dict = action.to_dict()
assert action_dict == {}
@pytest.fixture(scope="module")
def topic_edited(emoji_id):
return ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id=emoji_id)
class TestForumTopicEdited:
def test_slot_behaviour(self, topic_edited, mro_slots):
for attr in topic_edited.__slots__:
assert getattr(topic_edited, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(topic_edited)) == len(set(mro_slots(topic_edited))), "duplicate slot"
def test_expected_values(self, topic_edited, emoji_id):
assert topic_edited.name == TEST_TOPIC_NAME
assert topic_edited.icon_custom_emoji_id == emoji_id
def test_de_json(self, bot, emoji_id):
assert ForumTopicEdited.de_json(None, bot=bot) is None
json_dict = {"name": TEST_TOPIC_NAME, "icon_custom_emoji_id": emoji_id}
action = ForumTopicEdited.de_json(json_dict, bot)
assert action.api_kwargs == {}
assert action.name == TEST_TOPIC_NAME
assert action.icon_custom_emoji_id == emoji_id
# special test since it is mentioned in the docs that icon_custom_emoji_id can be an
# empty string
json_dict = {"icon_custom_emoji_id": ""}
action = ForumTopicEdited.de_json(json_dict, bot)
assert action.icon_custom_emoji_id == ""
def test_to_dict(self, topic_edited, emoji_id):
action_dict = topic_edited.to_dict()
assert isinstance(action_dict, dict)
assert action_dict["name"] == TEST_TOPIC_NAME
assert action_dict["icon_custom_emoji_id"] == emoji_id
def test_equality(self, emoji_id):
a = ForumTopicEdited(name=TEST_TOPIC_NAME, icon_custom_emoji_id="")
b = ForumTopicEdited(
name=TEST_TOPIC_NAME,
icon_custom_emoji_id="",
)
c = ForumTopicEdited(name=f"{TEST_TOPIC_NAME}!", icon_custom_emoji_id=emoji_id)
d = ForumTopicEdited(icon_custom_emoji_id="")
assert a == b
assert hash(a) == hash(b)
assert a != c
assert hash(a) != hash(c)
assert a != d
assert hash(a) != hash(d)
class TestGeneralForumTopicHidden:
def test_slot_behaviour(self, mro_slots):
action = GeneralForumTopicHidden()
for attr in action.__slots__:
assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
def test_de_json(self):
action = GeneralForumTopicHidden.de_json({}, None)
assert action.api_kwargs == {}
assert isinstance(action, GeneralForumTopicHidden)
def test_to_dict(self):
action = GeneralForumTopicHidden()
action_dict = action.to_dict()
assert action_dict == {}
class TestGeneralForumTopicUnhidden:
def test_slot_behaviour(self, mro_slots):
action = GeneralForumTopicUnhidden()
for attr in action.__slots__:
assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
def test_de_json(self):
action = GeneralForumTopicUnhidden.de_json({}, None)
assert action.api_kwargs == {}
assert isinstance(action, GeneralForumTopicUnhidden)
def test_to_dict(self):
action = GeneralForumTopicUnhidden()
action_dict = action.to_dict()
assert action_dict == {}

View file

@ -68,6 +68,7 @@ def input_media_video(class_thumb_file):
caption_entities=TestInputMediaVideo.caption_entities,
thumb=class_thumb_file,
supports_streaming=TestInputMediaVideo.supports_streaming,
has_spoiler=TestInputMediaVideo.has_spoiler,
)
@ -78,6 +79,7 @@ def input_media_photo(class_thumb_file):
caption=TestInputMediaPhoto.caption,
parse_mode=TestInputMediaPhoto.parse_mode,
caption_entities=TestInputMediaPhoto.caption_entities,
has_spoiler=TestInputMediaPhoto.has_spoiler,
)
@ -92,6 +94,7 @@ def input_media_animation(class_thumb_file):
height=TestInputMediaAnimation.height,
thumb=class_thumb_file,
duration=TestInputMediaAnimation.duration,
has_spoiler=TestInputMediaAnimation.has_spoiler,
)
@ -142,6 +145,7 @@ class TestInputMediaVideo:
parse_mode = "HTML"
supports_streaming = True
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_video, mro_slots):
inst = input_media_video
@ -160,6 +164,7 @@ class TestInputMediaVideo:
assert input_media_video.caption_entities == tuple(self.caption_entities)
assert input_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_media_video.thumb, InputFile)
assert input_media_video.has_spoiler == self.has_spoiler
def test_caption_entities_always_tuple(self):
input_media_video = InputMediaVideo(self.media)
@ -178,6 +183,7 @@ class TestInputMediaVideo:
ce.to_dict() for ce in input_media_video.caption_entities
]
assert input_media_video_dict["supports_streaming"] == input_media_video.supports_streaming
assert input_media_video_dict["has_spoiler"] == input_media_video.has_spoiler
def test_with_video(self, video): # noqa: F811
# fixture found in test_video
@ -210,6 +216,7 @@ class TestInputMediaPhoto:
caption = "My Caption"
parse_mode = "Markdown"
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_photo, mro_slots):
inst = input_media_photo
@ -223,6 +230,7 @@ class TestInputMediaPhoto:
assert input_media_photo.caption == self.caption
assert input_media_photo.parse_mode == self.parse_mode
assert input_media_photo.caption_entities == tuple(self.caption_entities)
assert input_media_photo.has_spoiler == self.has_spoiler
def test_caption_entities_always_tuple(self):
input_media_photo = InputMediaPhoto(self.media)
@ -237,6 +245,7 @@ class TestInputMediaPhoto:
assert input_media_photo_dict["caption_entities"] == [
ce.to_dict() for ce in input_media_photo.caption_entities
]
assert input_media_photo_dict["has_spoiler"] == input_media_photo.has_spoiler
def test_with_photo(self, photo): # noqa: F811
# fixture found in test_photo
@ -266,6 +275,7 @@ class TestInputMediaAnimation:
width = 30
height = 30
duration = 1
has_spoiler = True
def test_slot_behaviour(self, input_media_animation, mro_slots):
inst = input_media_animation
@ -280,6 +290,7 @@ class TestInputMediaAnimation:
assert input_media_animation.parse_mode == self.parse_mode
assert input_media_animation.caption_entities == tuple(self.caption_entities)
assert isinstance(input_media_animation.thumb, InputFile)
assert input_media_animation.has_spoiler == self.has_spoiler
def test_caption_entities_always_tuple(self):
input_media_animation = InputMediaAnimation(self.media)
@ -297,6 +308,7 @@ class TestInputMediaAnimation:
assert input_media_animation_dict["width"] == input_media_animation.width
assert input_media_animation_dict["height"] == input_media_animation.height
assert input_media_animation_dict["duration"] == input_media_animation.duration
assert input_media_animation_dict["has_spoiler"] == input_media_animation.has_spoiler
def test_with_animation(self, animation): # noqa: F811
# fixture found in test_animation
@ -621,6 +633,22 @@ class TestSendMediaGroup:
)
assert all(mes.has_protected_content for mes in messages)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_with_spoiler(
self, bot, chat_id, photo_file, video_file # noqa: F811
):
# Media groups can't contain Animations, so that is tested in test_animation.py
media = [
InputMediaPhoto(photo_file, has_spoiler=True),
InputMediaVideo(video_file, has_spoiler=True),
]
messages = await bot.send_media_group(chat_id, media)
assert isinstance(messages, tuple)
assert len(messages) == 2
assert all(isinstance(mes, Message) for mes in messages)
assert all(mes.media_group_id == messages[0].media_group_id for mes in messages)
assert all(mes.has_media_spoiler for mes in messages)
@pytest.mark.flaky(3, 1)
async def test_send_media_group_custom_filename(
self,

View file

@ -107,6 +107,7 @@ class TestPhoto:
disable_notification=False,
protect_content=True,
parse_mode="Markdown",
has_spoiler=True,
)
assert isinstance(message.photo[-2], PhotoSize)
@ -123,6 +124,7 @@ class TestPhoto:
assert message.caption == TestPhoto.caption.replace("*", "")
assert message.has_protected_content
assert message.has_media_spoiler
@pytest.mark.flaky(3, 1)
async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch):

View file

@ -29,6 +29,7 @@ def reply_keyboard_markup():
resize_keyboard=TestReplyKeyboardMarkup.resize_keyboard,
one_time_keyboard=TestReplyKeyboardMarkup.one_time_keyboard,
selective=TestReplyKeyboardMarkup.selective,
is_persistent=TestReplyKeyboardMarkup.is_persistent,
)
@ -37,6 +38,7 @@ class TestReplyKeyboardMarkup:
resize_keyboard = True
one_time_keyboard = True
selective = True
is_persistent = True
def test_slot_behaviour(self, reply_keyboard_markup, mro_slots):
inst = reply_keyboard_markup
@ -103,6 +105,7 @@ class TestReplyKeyboardMarkup:
assert reply_keyboard_markup.resize_keyboard == self.resize_keyboard
assert reply_keyboard_markup.one_time_keyboard == self.one_time_keyboard
assert reply_keyboard_markup.selective == self.selective
assert reply_keyboard_markup.is_persistent == self.is_persistent
def test_wrong_keyboard_inputs(self):
with pytest.raises(ValueError):
@ -134,6 +137,7 @@ class TestReplyKeyboardMarkup:
== reply_keyboard_markup.one_time_keyboard
)
assert reply_keyboard_markup_dict["selective"] == reply_keyboard_markup.selective
assert reply_keyboard_markup_dict["is_persistent"] == reply_keyboard_markup.is_persistent
def test_equality(self):
a = ReplyKeyboardMarkup.from_column(["button1", "button2", "button3"])

View file

@ -105,6 +105,7 @@ class TestVideo:
height=video.height,
parse_mode="Markdown",
thumb=thumb_file,
has_spoiler=True,
)
assert isinstance(message.video, Video)
@ -125,6 +126,7 @@ class TestVideo:
assert message.video.file_name == self.file_name
assert message.has_protected_content
assert message.has_media_spoiler
@pytest.mark.flaky(3, 1)
async def test_send_video_custom_filename(self, bot, chat_id, video_file, monkeypatch):

View file

@ -0,0 +1,37 @@
#!/usr/bin/env python
#
# A library that provides a Python interface to the Telegram Bot API
# Copyright (C) 2015-2022
# 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/].
from telegram import WriteAccessAllowed
class TestWriteAccessAllowed:
def test_slot_behaviour(self, mro_slots):
action = WriteAccessAllowed()
for attr in action.__slots__:
assert getattr(action, attr, "err") != "err", f"got extra slot '{attr}'"
assert len(mro_slots(action)) == len(set(mro_slots(action))), "duplicate slot"
def test_de_json(self):
action = WriteAccessAllowed.de_json({}, None)
assert action.api_kwargs == {}
assert isinstance(action, WriteAccessAllowed)
def test_to_dict(self):
action = WriteAccessAllowed()
action_dict = action.to_dict()
assert action_dict == {}