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/ :target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions :alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-6.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 :target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API versions :alt: Supported Bot API versions
@ -93,7 +93,7 @@ Installing both ``python-telegram-bot`` and ``python-telegram-bot-raw`` in conju
Telegram API support Telegram API support
==================== ====================
All types and methods of the Telegram Bot API **6.2** are supported. All types and methods of the Telegram Bot API **6.4** are supported.
Installing Installing
========== ==========

View file

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

View file

@ -267,16 +267,26 @@
* - :meth:`~telegram.Bot.close_forum_topic` * - :meth:`~telegram.Bot.close_forum_topic`
- Used for closing a 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` * - :meth:`~telegram.Bot.create_forum_topic`
- Used to create a topic - Used to create a topic
* - :meth:`~telegram.Bot.delete_forum_topic` * - :meth:`~telegram.Bot.delete_forum_topic`
- Used for deleting a forum topic - Used for deleting a forum topic
* - :meth:`~telegram.Bot.edit_forum_topic` * - :meth:`~telegram.Bot.edit_forum_topic`
- Used to edit a topic - Used to edit a topic
* - :meth:`~telegram.Bot.reopen_forum_topic` * - :meth:`~telegram.Bot.edit_general_forum_topic`
- Used to reopen a topic - Used to edit the general topic
* - :meth:`~telegram.Bot.get_forum_topic_icon_stickers` * - :meth:`~telegram.Bot.get_forum_topic_icon_stickers`
- Used to get custom emojis to use as topic icons - 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` * - :meth:`~telegram.Bot.unpin_all_forum_topic_messages`
- Used to unpin all messages in a forum topic - Used to unpin all messages in a forum topic

View file

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

@ -50,4 +50,4 @@
.. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list. .. |sequenceargs| replace:: Accepts any :class:`collections.abc.Sequence` as input instead of just a list.
.. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``. .. |captionentitiesattr| replace:: Tuple of special entities that appear in the caption, which can be specified instead of ``parse_mode``.

View file

@ -70,9 +70,12 @@ __all__ = ( # Keep this alphabetically ordered
"ForumTopic", "ForumTopic",
"ForumTopicClosed", "ForumTopicClosed",
"ForumTopicCreated", "ForumTopicCreated",
"ForumTopicEdited",
"ForumTopicReopened", "ForumTopicReopened",
"Game", "Game",
"GameHighScore", "GameHighScore",
"GeneralForumTopicHidden",
"GeneralForumTopicUnhidden",
"helpers", "helpers",
"IdDocumentData", "IdDocumentData",
"InlineKeyboardButton", "InlineKeyboardButton",
@ -176,6 +179,7 @@ __all__ = ( # Keep this alphabetically ordered
"WebAppData", "WebAppData",
"WebAppInfo", "WebAppInfo",
"WebhookInfo", "WebhookInfo",
"WriteAccessAllowed",
) )
@ -234,7 +238,15 @@ from ._files.video import Video
from ._files.videonote import VideoNote from ._files.videonote import VideoNote
from ._files.voice import Voice from ._files.voice import Voice
from ._forcereply import ForceReply 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.callbackgame import CallbackGame
from ._games.game import Game from ._games.game import Game
from ._games.gamehighscore import GameHighScore from ._games.gamehighscore import GameHighScore
@ -326,6 +338,7 @@ from ._videochat import (
from ._webappdata import WebAppData from ._webappdata import WebAppData
from ._webappinfo import WebAppInfo from ._webappinfo import WebAppInfo
from ._webhookinfo import WebhookInfo from ._webhookinfo import WebhookInfo
from ._writeaccessallowed import WriteAccessAllowed
#: :obj:`str`: The version of the `python-telegram-bot` library as string. #: :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__` #: 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, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -973,6 +974,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply 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. 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: Keyword Args:
filename (:obj:`str`, optional): Custom file name for the photo, when uploading a filename (:obj:`str`, optional): Custom file name for the photo, when uploading a
@ -991,6 +996,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
data: JSONDict = { data: JSONDict = {
"chat_id": chat_id, "chat_id": chat_id,
"photo": self._parse_file_input(photo, PhotoSize, filename=filename), "photo": self._parse_file_input(photo, PhotoSize, filename=filename),
"has_spoiler": has_spoiler,
} }
return await self._send_message( return await self._send_message(
@ -1363,6 +1369,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1438,6 +1445,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
.. versionchanged:: 20.0 .. versionchanged:: 20.0
File paths as input is also accepted for bots *not* running in File paths as input is also accepted for bots *not* running in
:paramref:`~telegram.Bot.local_mode`. :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: Keyword Args:
filename (:obj:`str`, optional): Custom file name for the video, when uploading a filename (:obj:`str`, optional): Custom file name for the video, when uploading a
@ -1461,6 +1472,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"height": height, "height": height,
"supports_streaming": supports_streaming, "supports_streaming": supports_streaming,
"thumb": self._parse_file_input(thumb, attach=True) if thumb else None, "thumb": self._parse_file_input(thumb, attach=True) if thumb else None,
"has_spoiler": has_spoiler,
} }
return await self._send_message( return await self._send_message(
@ -1617,6 +1629,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1687,6 +1700,10 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional): :class:`ReplyKeyboardRemove` | :class:`ForceReply`, optional):
Additional interface options. An object for an inline keyboard, custom reply 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. 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: Keyword Args:
filename (:obj:`str`, optional): Custom file name for the animation, when uploading a filename (:obj:`str`, optional): Custom file name for the animation, when uploading a
@ -1709,6 +1726,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
"width": width, "width": width,
"height": height, "height": height,
"thumb": self._parse_file_input(thumb, attach=True) if thumb else None, "thumb": self._parse_file_input(thumb, attach=True) if thumb else None,
"has_spoiler": has_spoiler,
} }
return await self._send_message( return await self._send_message(
@ -2553,6 +2571,7 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
self, self,
chat_id: Union[str, int], chat_id: Union[str, int],
action: str, action: str,
message_thread_id: int = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2574,6 +2593,9 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
action(:obj:`str`): Type of action to broadcast. Choose one, depending on what the user 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 is about to receive. For convenience look at the constants in
:class:`telegram.constants.ChatAction`. :class:`telegram.constants.ChatAction`.
message_thread_id (:obj:`int`, optional): |message_thread_id_arg|
.. versionadded:: 20.0
Returns: Returns:
:obj:`bool`: On success, :obj:`True` is returned. :obj:`bool`: On success, :obj:`True` is returned.
@ -2582,7 +2604,11 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
:class:`telegram.error.TelegramError` :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( result = await self._post(
"sendChatAction", "sendChatAction",
data, data,
@ -3913,7 +3939,8 @@ class Bot(TelegramObject, AbstractAsyncContextManager):
pool_timeout: ODVInput[float] = DEFAULT_NONE, pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
) -> ChatMember: ) -> 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` .. seealso:: :meth:`telegram.Chat.get_member`
@ -6970,8 +6997,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
self, self,
chat_id: Union[str, int], chat_id: Union[str, int],
message_thread_id: int, message_thread_id: int,
name: str, name: str = None,
icon_custom_emoji_id: str, icon_custom_emoji_id: str = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -6993,12 +7020,14 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group| chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
message_thread_id (:obj:`int`): |message_thread_id| 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.MIN_NAME_LENGTH`-
:tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. :tg-const:`telegram.constants.ForumTopicLimit.MAX_NAME_LENGTH` characters. If
icon_custom_emoji_id (:obj:`str`): New unique identifier of the custom emoji shown as not specified or empty, the current name of the topic will be kept.
the topic icon. Use :meth:`~telegram.Bot.get_forum_topic_icon_stickers` to get all icon_custom_emoji_id (:obj:`str`, optional): New unique identifier of the custom emoji
allowed custom emoji identifiers. 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: Returns:
:obj:`bool`: On success, :obj:`True` is returned. :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, 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 def to_dict(self, recursive: bool = True) -> JSONDict: # skipcq: PYL-W0613
"""See :meth:`telegram.TelegramObject.to_dict`.""" """See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name} data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@ -7422,3 +7667,13 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
"""Alias for :meth:`delete_forum_topic`""" """Alias for :meth:`delete_forum_topic`"""
unpinAllForumTopicMessages = unpin_all_forum_topic_messages unpinAllForumTopicMessages = unpin_all_forum_topic_messages
"""Alias for :meth:`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 status of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`. :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 .. versionadded:: 20.0
Attributes: Attributes:
@ -247,6 +257,16 @@ class Chat(TelegramObject):
status of the other party in a private chat. Returned only in status of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`. :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 .. versionadded:: 20.0
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups .. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
@ -279,6 +299,8 @@ class Chat(TelegramObject):
"is_forum", "is_forum",
"active_usernames", "active_usernames",
"emoji_status_custom_emoji_id", "emoji_status_custom_emoji_id",
"has_hidden_members",
"has_aggressive_anti_spam_enabled",
) )
SENDER: ClassVar[str] = constants.ChatType.SENDER SENDER: ClassVar[str] = constants.ChatType.SENDER
@ -323,6 +345,8 @@ class Chat(TelegramObject):
is_forum: bool = None, is_forum: bool = None,
active_usernames: Sequence[str] = None, active_usernames: Sequence[str] = None,
emoji_status_custom_emoji_id: 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, api_kwargs: JSONDict = None,
): ):
@ -357,6 +381,8 @@ class Chat(TelegramObject):
self.is_forum = is_forum self.is_forum = is_forum
self.active_usernames = parse_sequence_arg(active_usernames) self.active_usernames = parse_sequence_arg(active_usernames)
self.emoji_status_custom_emoji_id = emoji_status_custom_emoji_id 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,) self._id_attrs = (self.id,)
@ -1330,6 +1356,7 @@ class Chat(TelegramObject):
async def send_chat_action( async def send_chat_action(
self, self,
action: str, action: str,
message_thread_id: int = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1350,6 +1377,7 @@ class Chat(TelegramObject):
return await self.get_bot().send_chat_action( return await self.get_bot().send_chat_action(
chat_id=self.id, chat_id=self.id,
action=action, action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
@ -1372,6 +1400,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1408,6 +1437,7 @@ class Chat(TelegramObject):
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
pool_timeout=pool_timeout, pool_timeout=pool_timeout,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
) )
async def send_contact( async def send_contact(
@ -1818,6 +1848,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1858,6 +1889,7 @@ class Chat(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def send_sticker( async def send_sticker(
@ -1977,6 +2009,7 @@ class Chat(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2018,6 +2051,7 @@ class Chat(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def send_video_note( async def send_video_note(
@ -2663,8 +2697,8 @@ class Chat(TelegramObject):
async def edit_forum_topic( async def edit_forum_topic(
self, self,
message_thread_id: int, message_thread_id: int,
name: str, name: str = None,
icon_custom_emoji_id: str, icon_custom_emoji_id: str = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2825,6 +2859,164 @@ class Chat(TelegramObject):
api_kwargs=api_kwargs, 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( async def get_menu_button(
self, self,
*, *,

View file

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

View file

@ -142,3 +142,73 @@ class ForumTopicReopened(TelegramObject):
super().__init__(api_kwargs=api_kwargs) super().__init__(api_kwargs=api_kwargs)
self._freeze() 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.video import Video
from telegram._files.videonote import VideoNote from telegram._files.videonote import VideoNote
from telegram._files.voice import Voice 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._games.game import Game
from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup from telegram._inline.inlinekeyboardmarkup import InlineKeyboardMarkup
from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged from telegram._messageautodeletetimerchanged import MessageAutoDeleteTimerChanged
@ -59,6 +66,7 @@ from telegram._videochat import (
VideoChatStarted, VideoChatStarted,
) )
from telegram._webappdata import WebAppData from telegram._webappdata import WebAppData
from telegram._writeaccessallowed import WriteAccessAllowed
from telegram.constants import MessageAttachmentType, ParseMode from telegram.constants import MessageAttachmentType, ParseMode
from telegram.helpers import escape_markdown from telegram.helpers import escape_markdown
@ -277,15 +285,35 @@ class Message(TelegramObject):
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message: forum_topic_created (:class:`telegram.ForumTopicCreated`, optional): Service message:
forum topic created forum topic created.
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_closed (:class:`telegram.ForumTopicClosed`, optional): Service message: forum_topic_closed (:class:`telegram.ForumTopicClosed`, optional): Service message:
forum topic closed forum topic closed.
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_reopened (:class:`telegram.ForumTopicReopened`, optional): Service message: 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 .. versionadded:: 20.0
@ -484,15 +512,35 @@ class Message(TelegramObject):
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message: forum_topic_created (:class:`telegram.ForumTopicCreated`): Optional. Service message:
forum topic created forum topic created.
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_closed (:class:`telegram.ForumTopicClosed`): Optional. Service message: forum_topic_closed (:class:`telegram.ForumTopicClosed`): Optional. Service message:
forum topic closed forum topic closed.
.. versionadded:: 20.0 .. versionadded:: 20.0
forum_topic_reopened (:class:`telegram.ForumTopicReopened`): Optional. Service message: 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 .. versionadded:: 20.0
@ -567,6 +615,11 @@ class Message(TelegramObject):
"forum_topic_created", "forum_topic_created",
"forum_topic_closed", "forum_topic_closed",
"forum_topic_reopened", "forum_topic_reopened",
"forum_topic_edited",
"general_forum_topic_hidden",
"general_forum_topic_unhidden",
"write_access_allowed",
"has_media_spoiler",
) )
def __init__( def __init__(
@ -635,6 +688,11 @@ class Message(TelegramObject):
forum_topic_created: ForumTopicCreated = None, forum_topic_created: ForumTopicCreated = None,
forum_topic_closed: ForumTopicClosed = None, forum_topic_closed: ForumTopicClosed = None,
forum_topic_reopened: ForumTopicReopened = 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, api_kwargs: JSONDict = None,
): ):
@ -706,6 +764,11 @@ class Message(TelegramObject):
self.forum_topic_created = forum_topic_created self.forum_topic_created = forum_topic_created
self.forum_topic_closed = forum_topic_closed self.forum_topic_closed = forum_topic_closed
self.forum_topic_reopened = forum_topic_reopened 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 self._effective_attachment = DEFAULT_NONE
@ -805,6 +868,16 @@ class Message(TelegramObject):
data["forum_topic_reopened"] = ForumTopicReopened.de_json( data["forum_topic_reopened"] = ForumTopicReopened.de_json(
data.get("forum_topic_reopened"), bot 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) return super().de_json(data=data, bot=bot)
@ -1203,6 +1276,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
quote: bool = None, quote: bool = None,
@ -1246,6 +1320,7 @@ class Message(TelegramObject):
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
pool_timeout=pool_timeout, pool_timeout=pool_timeout,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
) )
async def reply_audio( async def reply_audio(
@ -1390,6 +1465,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
quote: bool = None, quote: bool = None,
@ -1438,6 +1514,7 @@ class Message(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def reply_sticker( async def reply_sticker(
@ -1506,6 +1583,7 @@ class Message(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
quote: bool = None, quote: bool = None,
@ -1554,6 +1632,7 @@ class Message(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def reply_video_note( async def reply_video_note(
@ -1980,6 +2059,7 @@ class Message(TelegramObject):
async def reply_chat_action( async def reply_chat_action(
self, self,
action: str, action: str,
message_thread_id: int = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2001,6 +2081,7 @@ class Message(TelegramObject):
""" """
return await self.get_bot().send_chat_action( return await self.get_bot().send_chat_action(
chat_id=self.chat_id, chat_id=self.chat_id,
message_thread_id=message_thread_id,
action=action, action=action,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
@ -2815,8 +2896,8 @@ class Message(TelegramObject):
async def edit_forum_topic( async def edit_forum_topic(
self, self,
name: str, name: str = None,
icon_custom_emoji_id: str, icon_custom_emoji_id: str = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,

View file

@ -68,6 +68,11 @@ class ReplyKeyboardMarkup(TelegramObject):
characters. characters.
.. versionadded:: 13.7 .. 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: Attributes:
keyboard (Tuple[Tuple[:class:`telegram.KeyboardButton`]]): Array of button rows, keyboard (Tuple[Tuple[:class:`telegram.KeyboardButton`]]): Array of button rows,
@ -97,6 +102,11 @@ class ReplyKeyboardMarkup(TelegramObject):
characters. characters.
.. versionadded:: 13.7 .. 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", "resize_keyboard",
"one_time_keyboard", "one_time_keyboard",
"input_field_placeholder", "input_field_placeholder",
"is_persistent",
) )
def __init__( def __init__(
@ -115,6 +126,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = None, one_time_keyboard: bool = None,
selective: bool = None, selective: bool = None,
input_field_placeholder: str = None, input_field_placeholder: str = None,
is_persistent: bool = None,
*, *,
api_kwargs: JSONDict = None, api_kwargs: JSONDict = None,
): ):
@ -136,6 +148,7 @@ class ReplyKeyboardMarkup(TelegramObject):
self.one_time_keyboard = one_time_keyboard self.one_time_keyboard = one_time_keyboard
self.selective = selective self.selective = selective
self.input_field_placeholder = input_field_placeholder self.input_field_placeholder = input_field_placeholder
self.is_persistent = is_persistent
self._id_attrs = (self.keyboard,) self._id_attrs = (self.keyboard,)
@ -149,6 +162,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False, one_time_keyboard: bool = False,
selective: bool = False, selective: bool = False,
input_field_placeholder: str = None, input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object, **kwargs: object,
) -> "ReplyKeyboardMarkup": ) -> "ReplyKeyboardMarkup":
"""Shortcut for:: """Shortcut for::
@ -182,6 +196,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active. field when the reply is active.
.. versionadded:: 13.7 .. 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( return cls(
[[button]], [[button]],
@ -189,6 +208,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard, one_time_keyboard=one_time_keyboard,
selective=selective, selective=selective,
input_field_placeholder=input_field_placeholder, input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type] **kwargs, # type: ignore[arg-type]
) )
@ -200,6 +220,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False, one_time_keyboard: bool = False,
selective: bool = False, selective: bool = False,
input_field_placeholder: str = None, input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object, **kwargs: object,
) -> "ReplyKeyboardMarkup": ) -> "ReplyKeyboardMarkup":
"""Shortcut for:: """Shortcut for::
@ -236,6 +257,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active. field when the reply is active.
.. versionadded:: 13.7 .. 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( return cls(
@ -244,6 +270,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard, one_time_keyboard=one_time_keyboard,
selective=selective, selective=selective,
input_field_placeholder=input_field_placeholder, input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type] **kwargs, # type: ignore[arg-type]
) )
@ -255,6 +282,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard: bool = False, one_time_keyboard: bool = False,
selective: bool = False, selective: bool = False,
input_field_placeholder: str = None, input_field_placeholder: str = None,
is_persistent: bool = None,
**kwargs: object, **kwargs: object,
) -> "ReplyKeyboardMarkup": ) -> "ReplyKeyboardMarkup":
"""Shortcut for:: """Shortcut for::
@ -291,6 +319,11 @@ class ReplyKeyboardMarkup(TelegramObject):
field when the reply is active. field when the reply is active.
.. versionadded:: 13.7 .. 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] button_grid = [[button] for button in button_column]
@ -300,6 +333,7 @@ class ReplyKeyboardMarkup(TelegramObject):
one_time_keyboard=one_time_keyboard, one_time_keyboard=one_time_keyboard,
selective=selective, selective=selective,
input_field_placeholder=input_field_placeholder, input_field_placeholder=input_field_placeholder,
is_persistent=is_persistent,
**kwargs, # type: ignore[arg-type] **kwargs, # type: ignore[arg-type]
) )

View file

@ -427,6 +427,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -463,6 +464,7 @@ class User(TelegramObject):
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
pool_timeout=pool_timeout, pool_timeout=pool_timeout,
api_kwargs=api_kwargs, api_kwargs=api_kwargs,
has_spoiler=has_spoiler,
) )
async def send_media_group( async def send_media_group(
@ -575,6 +577,7 @@ class User(TelegramObject):
async def send_chat_action( async def send_chat_action(
self, self,
action: str, action: str,
message_thread_id: int = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -595,6 +598,7 @@ class User(TelegramObject):
return await self.get_bot().send_chat_action( return await self.get_bot().send_chat_action(
chat_id=self.id, chat_id=self.id,
action=action, action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
@ -955,6 +959,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -995,6 +1000,7 @@ class User(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def send_sticker( async def send_sticker(
@ -1056,6 +1062,7 @@ class User(TelegramObject):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1097,6 +1104,7 @@ class User(TelegramObject):
filename=filename, filename=filename,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
) )
async def send_venue( 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__`. #: :data:`telegram.__bot_api_version_info__`.
#: #:
#: .. versionadded:: 20.0 #: .. 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 #: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as #: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`. #: :data:`telegram.__bot_api_version__`.
@ -1568,14 +1568,22 @@ class ForumTopicLimit(IntEnum):
__slots__ = () __slots__ = ()
MIN_NAME_LENGTH = 1 MIN_NAME_LENGTH = 1
""":obj:`int`: Minimum length of a :obj:`str` passed as the """: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` and :paramref:`~telegram.Bot.edit_forum_topic.name` * :paramref:`~telegram.Bot.create_forum_topic.name` parameter of
parameter of :meth:`telegram.Bot.edit_forum_topic`. :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 MAX_NAME_LENGTH = 128
""":obj:`int`: Maximum length of a :obj:`str` passed as the """: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` and :paramref:`~telegram.Bot.edit_forum_topic.name` * :paramref:`~telegram.Bot.create_forum_topic.name` parameter of
parameter of :meth:`telegram.Bot.edit_forum_topic`. :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, self,
chat_id: Union[str, int], chat_id: Union[str, int],
message_thread_id: int, message_thread_id: int,
name: str, name: str = None,
icon_custom_emoji_id: str, icon_custom_emoji_id: str = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -1286,6 +1286,28 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args), 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( async def edit_message_caption(
self, self,
chat_id: Union[str, int] = None, 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), 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( async def create_forum_topic(
self, self,
chat_id: Union[str, int], 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), 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( async def pin_chat_message(
self, self,
chat_id: Union[str, int], chat_id: Union[str, int],
@ -2045,6 +2147,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2070,6 +2173,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities, caption_entities=caption_entities,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename, filename=filename,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
@ -2132,6 +2236,7 @@ class ExtBot(Bot, Generic[RLARGS]):
self, self,
chat_id: Union[str, int], chat_id: Union[str, int],
action: str, action: str,
message_thread_id: int = None,
*, *,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE, write_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2143,6 +2248,7 @@ class ExtBot(Bot, Generic[RLARGS]):
return await super().send_chat_action( return await super().send_chat_action(
chat_id=chat_id, chat_id=chat_id,
action=action, action=action,
message_thread_id=message_thread_id,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
connect_timeout=connect_timeout, connect_timeout=connect_timeout,
@ -2519,6 +2625,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2540,6 +2647,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities, caption_entities=caption_entities,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename, filename=filename,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
@ -2706,6 +2814,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities: Sequence["MessageEntity"] = None, caption_entities: Sequence["MessageEntity"] = None,
protect_content: ODVInput[bool] = DEFAULT_NONE, protect_content: ODVInput[bool] = DEFAULT_NONE,
message_thread_id: int = None, message_thread_id: int = None,
has_spoiler: bool = None,
*, *,
filename: str = None, filename: str = None,
read_timeout: ODVInput[float] = DEFAULT_NONE, read_timeout: ODVInput[float] = DEFAULT_NONE,
@ -2732,6 +2841,7 @@ class ExtBot(Bot, Generic[RLARGS]):
caption_entities=caption_entities, caption_entities=caption_entities,
protect_content=protect_content, protect_content=protect_content,
message_thread_id=message_thread_id, message_thread_id=message_thread_id,
has_spoiler=has_spoiler,
filename=filename, filename=filename,
read_timeout=read_timeout, read_timeout=read_timeout,
write_timeout=write_timeout, write_timeout=write_timeout,
@ -3414,3 +3524,8 @@ class ExtBot(Bot, Generic[RLARGS]):
reopenForumTopic = reopen_forum_topic reopenForumTopic = reopen_forum_topic
deleteForumTopic = delete_forum_topic deleteForumTopic = delete_forum_topic
unpinAllForumTopicMessages = unpin_all_forum_topic_messages 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", "FORWARDED",
"ForwardedFrom", "ForwardedFrom",
"GAME", "GAME",
"HAS_MEDIA_SPOILER",
"HAS_PROTECTED_CONTENT", "HAS_PROTECTED_CONTENT",
"INVOICE", "INVOICE",
"IS_AUTOMATIC_FORWARD", "IS_AUTOMATIC_FORWARD",
@ -1383,6 +1384,20 @@ GAME = _Game(name="filters.GAME")
"""Messages that contain :attr:`telegram.Message.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): class _HasProtectedContent(MessageFilter):
__slots__ = () __slots__ = ()
@ -1721,6 +1736,10 @@ class StatusUpdate:
or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update) or StatusUpdate.FORUM_TOPIC_CREATED.check_update(update)
or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update) or StatusUpdate.FORUM_TOPIC_CLOSED.check_update(update)
or StatusUpdate.FORUM_TOPIC_REOPENED.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") ALL = _All(name="filters.StatusUpdate.ALL")
@ -1783,6 +1802,18 @@ class StatusUpdate:
.. versionadded:: 20.0 .. 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): class _ForumTopicReopened(MessageFilter):
__slots__ = () __slots__ = ()
@ -1795,6 +1826,34 @@ class StatusUpdate:
.. versionadded:: 20.0 .. 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): class _LeftChatMember(MessageFilter):
__slots__ = () __slots__ = ()
@ -1945,6 +2004,18 @@ class StatusUpdate:
.. versionadded:: 20.0 .. 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: class Sticker:
"""Filters messages which contain a sticker. """Filters messages which contain a sticker.

View file

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

View file

@ -93,6 +93,7 @@ class TestAnimation:
disable_notification=False, disable_notification=False,
protect_content=True, protect_content=True,
thumb=thumb_file, thumb=thumb_file,
has_spoiler=True,
) )
assert isinstance(message.animation, Animation) assert isinstance(message.animation, Animation)
@ -106,6 +107,10 @@ class TestAnimation:
assert message.animation.thumb.width == self.width assert message.animation.thumb.width == self.width
assert message.animation.thumb.height == self.height assert message.animation.thumb.height == self.height
assert message.has_protected_content 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) @pytest.mark.flaky(3, 1)
async def test_send_animation_custom_filename(self, bot, chat_id, animation_file, monkeypatch): 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"): with pytest.raises(BadRequest, match="Wrong parameter action"):
await bot.send_chat_action(chat_id, "unknown 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 @pytest.mark.asyncio
async def test_answer_web_app_query(self, bot, raw_bot, monkeypatch): async def test_answer_web_app_query(self, bot, raw_bot, monkeypatch):
params = False params = False
@ -2525,7 +2537,7 @@ class TestBot:
# set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers # set_sticker_position_in_set, delete_sticker_from_set and get_custom_emoji_stickers
# are tested in the test_sticker module. # 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. # are tested in the test_forum module.
async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id): async def test_timeout_propagation_explicit(self, monkeypatch, bot, chat_id):

View file

@ -51,6 +51,8 @@ def chat(bot):
is_forum=True, is_forum=True,
active_usernames=TestChat.active_usernames, active_usernames=TestChat.active_usernames,
emoji_status_custom_emoji_id=TestChat.emoji_status_custom_emoji_id, 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.set_bot(bot)
chat._unfreeze() chat._unfreeze()
@ -82,6 +84,8 @@ class TestChat:
is_forum = True is_forum = True
active_usernames = ["These", "Are", "Usernames!"] active_usernames = ["These", "Are", "Usernames!"]
emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID" emoji_status_custom_emoji_id = "VeryUniqueCustomEmojiID"
has_aggressive_anti_spam_enabled = True
has_hidden_members = True
def test_slot_behaviour(self, chat, mro_slots): def test_slot_behaviour(self, chat, mro_slots):
for attr in chat.__slots__: for attr in chat.__slots__:
@ -112,6 +116,8 @@ class TestChat:
"is_forum": self.is_forum, "is_forum": self.is_forum,
"active_usernames": self.active_usernames, "active_usernames": self.active_usernames,
"emoji_status_custom_emoji_id": self.emoji_status_custom_emoji_id, "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) chat = Chat.de_json(json_dict, bot)
@ -141,6 +147,8 @@ class TestChat:
assert chat.is_forum == self.is_forum assert chat.is_forum == self.is_forum
assert chat.active_usernames == tuple(self.active_usernames) assert chat.active_usernames == tuple(self.active_usernames)
assert chat.emoji_status_custom_emoji_id == self.emoji_status_custom_emoji_id 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): def test_to_dict(self, chat):
chat_dict = chat.to_dict() chat_dict = chat.to_dict()
@ -166,6 +174,10 @@ class TestChat:
assert chat_dict["is_forum"] == chat.is_forum assert chat_dict["is_forum"] == chat.is_forum
assert chat_dict["active_usernames"] == list(chat.active_usernames) 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["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): def test_always_tuples_attributes(self):
chat = Chat( chat = Chat(
@ -1032,6 +1044,111 @@ class TestChat:
monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion) monkeypatch.setattr(chat.get_bot(), "unpin_all_forum_topic_messages", make_assertion)
assert await chat.unpin_all_forum_topic_messages(message_thread_id=42) 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): def test_mention_html(self):
with pytest.raises(TypeError, match="Can not create a mention to a private group chat"): with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):
chat = Chat(id=1, type="foo") chat = Chat(id=1, type="foo")

View file

@ -1026,6 +1026,26 @@ class TestFilters:
assert filters.StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update) assert filters.StatusUpdate.FORUM_TOPIC_REOPENED.check_update(update)
update.message.forum_topic_reopened = None 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): def test_filters_forwarded(self, update):
assert not filters.FORWARDED.check_update(update) assert not filters.FORWARDED.check_update(update)
update.message.forward_date = datetime.datetime.utcnow() update.message.forward_date = datetime.datetime.utcnow()
@ -1815,6 +1835,11 @@ class TestFilters:
update.message.is_topic_message = True update.message.is_topic_message = True
assert filters.IS_TOPIC_MESSAGE.check_update(update) 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): def test_filters_has_protected_content(self, update):
assert not filters.HAS_PROTECTED_CONTENT.check_update(update) assert not filters.HAS_PROTECTED_CONTENT.check_update(update)
update.message.has_protected_content = True update.message.has_protected_content = True

View file

@ -16,9 +16,20 @@
# #
# You should have received a copy of the GNU Lesser Public License # You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/]. # along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime
import pytest 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_MSG_TEXT = "Topics are forever"
TEST_TOPIC_ICON_COLOR = 0x6FB9F0 TEST_TOPIC_ICON_COLOR = 0x6FB9F0
@ -139,7 +150,9 @@ class TestForumTopic:
assert a != e assert a != e
assert hash(a) != hash(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): async def test_create_forum_topic(self, real_topic):
result = real_topic result = real_topic
assert isinstance(result, ForumTopic) assert isinstance(result, ForumTopic)
@ -161,7 +174,6 @@ class TestForumTopic:
) )
assert result is True, "Failed to delete forum topic" assert result is True, "Failed to delete forum topic"
@pytest.mark.flaky(3, 1)
async def test_get_forum_topic_icon_stickers(self, bot): async def test_get_forum_topic_icon_stickers(self, bot):
emoji_sticker_list = await bot.get_forum_topic_icon_stickers() emoji_sticker_list = await bot.get_forum_topic_icon_stickers()
first_sticker = emoji_sticker_list[0] first_sticker = emoji_sticker_list[0]
@ -194,7 +206,6 @@ class TestForumTopic:
assert result is True, "Failed to edit forum topic" assert result is True, "Failed to edit forum topic"
# no way of checking the edited name, just the boolean result # 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): async def test_send_message_to_topic(self, bot, forum_group_id, real_topic):
message_thread_id = real_topic.message_thread_id message_thread_id = real_topic.message_thread_id
@ -223,7 +234,6 @@ class TestForumTopic:
) )
assert result is True, "Failed to reopen forum topic" 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): async def test_unpin_all_forum_topic_messages(self, bot, forum_group_id, real_topic):
message_thread_id = real_topic.message_thread_id 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" 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 @pytest.fixture
def topic_created(): def topic_created():
@ -333,3 +380,95 @@ class TestForumTopicReopened:
action = ForumTopicReopened() action = ForumTopicReopened()
action_dict = action.to_dict() action_dict = action.to_dict()
assert action_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, caption_entities=TestInputMediaVideo.caption_entities,
thumb=class_thumb_file, thumb=class_thumb_file,
supports_streaming=TestInputMediaVideo.supports_streaming, supports_streaming=TestInputMediaVideo.supports_streaming,
has_spoiler=TestInputMediaVideo.has_spoiler,
) )
@ -78,6 +79,7 @@ def input_media_photo(class_thumb_file):
caption=TestInputMediaPhoto.caption, caption=TestInputMediaPhoto.caption,
parse_mode=TestInputMediaPhoto.parse_mode, parse_mode=TestInputMediaPhoto.parse_mode,
caption_entities=TestInputMediaPhoto.caption_entities, caption_entities=TestInputMediaPhoto.caption_entities,
has_spoiler=TestInputMediaPhoto.has_spoiler,
) )
@ -92,6 +94,7 @@ def input_media_animation(class_thumb_file):
height=TestInputMediaAnimation.height, height=TestInputMediaAnimation.height,
thumb=class_thumb_file, thumb=class_thumb_file,
duration=TestInputMediaAnimation.duration, duration=TestInputMediaAnimation.duration,
has_spoiler=TestInputMediaAnimation.has_spoiler,
) )
@ -142,6 +145,7 @@ class TestInputMediaVideo:
parse_mode = "HTML" parse_mode = "HTML"
supports_streaming = True supports_streaming = True
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_video, mro_slots): def test_slot_behaviour(self, input_media_video, mro_slots):
inst = input_media_video inst = input_media_video
@ -160,6 +164,7 @@ class TestInputMediaVideo:
assert input_media_video.caption_entities == tuple(self.caption_entities) assert input_media_video.caption_entities == tuple(self.caption_entities)
assert input_media_video.supports_streaming == self.supports_streaming assert input_media_video.supports_streaming == self.supports_streaming
assert isinstance(input_media_video.thumb, InputFile) assert isinstance(input_media_video.thumb, InputFile)
assert input_media_video.has_spoiler == self.has_spoiler
def test_caption_entities_always_tuple(self): def test_caption_entities_always_tuple(self):
input_media_video = InputMediaVideo(self.media) input_media_video = InputMediaVideo(self.media)
@ -178,6 +183,7 @@ class TestInputMediaVideo:
ce.to_dict() for ce in input_media_video.caption_entities 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["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 def test_with_video(self, video): # noqa: F811
# fixture found in test_video # fixture found in test_video
@ -210,6 +216,7 @@ class TestInputMediaPhoto:
caption = "My Caption" caption = "My Caption"
parse_mode = "Markdown" parse_mode = "Markdown"
caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)] caption_entities = [MessageEntity(MessageEntity.BOLD, 0, 2)]
has_spoiler = True
def test_slot_behaviour(self, input_media_photo, mro_slots): def test_slot_behaviour(self, input_media_photo, mro_slots):
inst = input_media_photo inst = input_media_photo
@ -223,6 +230,7 @@ class TestInputMediaPhoto:
assert input_media_photo.caption == self.caption assert input_media_photo.caption == self.caption
assert input_media_photo.parse_mode == self.parse_mode assert input_media_photo.parse_mode == self.parse_mode
assert input_media_photo.caption_entities == tuple(self.caption_entities) 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): def test_caption_entities_always_tuple(self):
input_media_photo = InputMediaPhoto(self.media) input_media_photo = InputMediaPhoto(self.media)
@ -237,6 +245,7 @@ class TestInputMediaPhoto:
assert input_media_photo_dict["caption_entities"] == [ assert input_media_photo_dict["caption_entities"] == [
ce.to_dict() for ce in input_media_photo.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 def test_with_photo(self, photo): # noqa: F811
# fixture found in test_photo # fixture found in test_photo
@ -266,6 +275,7 @@ class TestInputMediaAnimation:
width = 30 width = 30
height = 30 height = 30
duration = 1 duration = 1
has_spoiler = True
def test_slot_behaviour(self, input_media_animation, mro_slots): def test_slot_behaviour(self, input_media_animation, mro_slots):
inst = input_media_animation inst = input_media_animation
@ -280,6 +290,7 @@ class TestInputMediaAnimation:
assert input_media_animation.parse_mode == self.parse_mode assert input_media_animation.parse_mode == self.parse_mode
assert input_media_animation.caption_entities == tuple(self.caption_entities) assert input_media_animation.caption_entities == tuple(self.caption_entities)
assert isinstance(input_media_animation.thumb, InputFile) assert isinstance(input_media_animation.thumb, InputFile)
assert input_media_animation.has_spoiler == self.has_spoiler
def test_caption_entities_always_tuple(self): def test_caption_entities_always_tuple(self):
input_media_animation = InputMediaAnimation(self.media) 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["width"] == input_media_animation.width
assert input_media_animation_dict["height"] == input_media_animation.height 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["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 def test_with_animation(self, animation): # noqa: F811
# fixture found in test_animation # fixture found in test_animation
@ -621,6 +633,22 @@ class TestSendMediaGroup:
) )
assert all(mes.has_protected_content for mes in messages) 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) @pytest.mark.flaky(3, 1)
async def test_send_media_group_custom_filename( async def test_send_media_group_custom_filename(
self, self,

View file

@ -107,6 +107,7 @@ class TestPhoto:
disable_notification=False, disable_notification=False,
protect_content=True, protect_content=True,
parse_mode="Markdown", parse_mode="Markdown",
has_spoiler=True,
) )
assert isinstance(message.photo[-2], PhotoSize) assert isinstance(message.photo[-2], PhotoSize)
@ -123,6 +124,7 @@ class TestPhoto:
assert message.caption == TestPhoto.caption.replace("*", "") assert message.caption == TestPhoto.caption.replace("*", "")
assert message.has_protected_content assert message.has_protected_content
assert message.has_media_spoiler
@pytest.mark.flaky(3, 1) @pytest.mark.flaky(3, 1)
async def test_send_photo_custom_filename(self, bot, chat_id, photo_file, monkeypatch): 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, resize_keyboard=TestReplyKeyboardMarkup.resize_keyboard,
one_time_keyboard=TestReplyKeyboardMarkup.one_time_keyboard, one_time_keyboard=TestReplyKeyboardMarkup.one_time_keyboard,
selective=TestReplyKeyboardMarkup.selective, selective=TestReplyKeyboardMarkup.selective,
is_persistent=TestReplyKeyboardMarkup.is_persistent,
) )
@ -37,6 +38,7 @@ class TestReplyKeyboardMarkup:
resize_keyboard = True resize_keyboard = True
one_time_keyboard = True one_time_keyboard = True
selective = True selective = True
is_persistent = True
def test_slot_behaviour(self, reply_keyboard_markup, mro_slots): def test_slot_behaviour(self, reply_keyboard_markup, mro_slots):
inst = reply_keyboard_markup inst = reply_keyboard_markup
@ -103,6 +105,7 @@ class TestReplyKeyboardMarkup:
assert reply_keyboard_markup.resize_keyboard == self.resize_keyboard assert reply_keyboard_markup.resize_keyboard == self.resize_keyboard
assert reply_keyboard_markup.one_time_keyboard == self.one_time_keyboard assert reply_keyboard_markup.one_time_keyboard == self.one_time_keyboard
assert reply_keyboard_markup.selective == self.selective assert reply_keyboard_markup.selective == self.selective
assert reply_keyboard_markup.is_persistent == self.is_persistent
def test_wrong_keyboard_inputs(self): def test_wrong_keyboard_inputs(self):
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -134,6 +137,7 @@ class TestReplyKeyboardMarkup:
== reply_keyboard_markup.one_time_keyboard == reply_keyboard_markup.one_time_keyboard
) )
assert reply_keyboard_markup_dict["selective"] == reply_keyboard_markup.selective 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): def test_equality(self):
a = ReplyKeyboardMarkup.from_column(["button1", "button2", "button3"]) a = ReplyKeyboardMarkup.from_column(["button1", "button2", "button3"])

View file

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