Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
This commit is contained in:
Bibo-Joshi 2021-12-11 15:21:56 +01:00 committed by GitHub
parent 2f6c4075c8
commit cb95868c4a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 448 additions and 27 deletions

View file

@ -1,5 +1,5 @@
.. ..
Make user to apply any changes to this file to README_RAW.rst as well! Make sure to apply any changes to this file to README_RAW.rst as well!
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true .. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-logo-text_768.png?raw=true
:align: center :align: center
@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot/ :target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions :alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-5.4-blue?logo=telegram .. image:: https://img.shields.io/badge/Bot%20API-5.5-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
@ -111,7 +111,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 **5.4** are supported. All types and methods of the Telegram Bot API **5.5** are supported.
========== ==========
Installing Installing

View file

@ -1,5 +1,5 @@
.. ..
Make user to apply any changes to this file to README.rst as well! Make sure to apply any changes to this file to README.rst as well!
.. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true .. image:: https://github.com/python-telegram-bot/logos/blob/master/logo-text/png/ptb-raw-logo-text_768.png?raw=true
:align: center :align: center
@ -20,7 +20,7 @@ We have a vibrant community of developers helping each other in our `Telegram gr
:target: https://pypi.org/project/python-telegram-bot-raw/ :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-5.4-blue?logo=telegram .. image:: https://img.shields.io/badge/Bot%20API-5.5-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
@ -105,7 +105,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 **5.4** are supported. All types and methods of the Telegram Bot API **5.5** are supported.
========== ==========
Installing Installing

View file

@ -579,6 +579,14 @@ class Bot(TelegramObject):
) -> Message: ) -> Message:
"""Use this method to forward messages of any kind. Service messages can't be forwarded. """Use this method to forward messages of any kind. Service messages can't be forwarded.
Note:
Since the release of Bot API 5.5 it can be impossible to forward messages from
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
:attr:`telegram.Chat.has_protected_content` to check this.
As a workaround, it is still possible to use :meth:`copy_message`. However, this
behaviour is undocumented and might be changed by Telegram.
Args: Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target channel (in the format ``@channelusername``). of the target channel (in the format ``@channelusername``).
@ -2408,6 +2416,45 @@ class Bot(TelegramObject):
return result # type: ignore[return-value] return result # type: ignore[return-value]
@log
def ban_chat_sender_chat(
self,
chat_id: Union[str, int],
sender_chat_id: int,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""
Use this method to ban a channel chat in a supergroup or a channel. Until the chat is
unbanned, the owner of the banned chat won't be able to send messages on behalf of **any of
their channels**. The bot must be an administrator in the supergroup or channel for this
to work and must have the appropriate administrator rights.
.. versionadded:: 13.9
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target group or username
of the target supergroup or channel (in the format ``@channelusername``).
sender_chat_id (:obj:`int`): Unique identifier of the target sender chat.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
result = self._post('banChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs)
return result # type: ignore[return-value]
@log @log
def unban_chat_member( def unban_chat_member(
self, self,
@ -2437,7 +2484,7 @@ class Bot(TelegramObject):
Telegram API. Telegram API.
Returns: Returns:
:obj:`bool` On success, :obj:`True` is returned. :obj:`bool`: On success, :obj:`True` is returned.
Raises: Raises:
:class:`telegram.error.TelegramError` :class:`telegram.error.TelegramError`
@ -2452,6 +2499,43 @@ class Bot(TelegramObject):
return result # type: ignore[return-value] return result # type: ignore[return-value]
@log
def unban_chat_sender_chat(
self,
chat_id: Union[str, int],
sender_chat_id: int,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Use this method to unban a previously banned channel in a supergroup or channel.
The bot must be an administrator for this to work and must have the
appropriate administrator rights.
.. versionadded:: 13.9
Args:
chat_id (:obj:`int` | :obj:`str`): Unique identifier for the target chat or username
of the target supergroup or channel (in the format ``@channelusername``).
sender_chat_id (:obj:`int`): Unique identifier of the target sender chat.
timeout (:obj:`int` | :obj:`float`, optional): If this value is specified, use it as
the read timeout from the server (instead of the one specified during creation of
the connection pool).
api_kwargs (:obj:`dict`, optional): Arbitrary keyword arguments to be passed to the
Telegram API.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {'chat_id': chat_id, 'sender_chat_id': sender_chat_id}
result = self._post('unbanChatSenderChat', data, timeout=timeout, api_kwargs=api_kwargs)
return result # type: ignore[return-value]
@log @log
def answer_callback_query( def answer_callback_query(
self, self,
@ -5499,10 +5583,14 @@ class Bot(TelegramObject):
"""Alias for :meth:`get_file`""" """Alias for :meth:`get_file`"""
banChatMember = ban_chat_member banChatMember = ban_chat_member
"""Alias for :meth:`ban_chat_member`""" """Alias for :meth:`ban_chat_member`"""
banChatSenderChat = ban_chat_sender_chat
"""Alias for :meth:`ban_chat_sender_chat`"""
kickChatMember = kick_chat_member kickChatMember = kick_chat_member
"""Alias for :meth:`kick_chat_member`""" """Alias for :meth:`kick_chat_member`"""
unbanChatMember = unban_chat_member unbanChatMember = unban_chat_member
"""Alias for :meth:`unban_chat_member`""" """Alias for :meth:`unban_chat_member`"""
unbanChatSenderChat = unban_chat_sender_chat
"""Alias for :meth:`unban_chat_sender_chat`"""
answerCallbackQuery = answer_callback_query answerCallbackQuery = answer_callback_query
"""Alias for :meth:`answer_callback_query`""" """Alias for :meth:`answer_callback_query`"""
editMessageText = edit_message_text editMessageText = edit_message_text

View file

@ -81,6 +81,11 @@ class Chat(TelegramObject):
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in bio (:obj:`str`, optional): Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
has_private_forwards (:obj:`bool`, optional): :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
description (:obj:`str`, optional): Description, for groups, supergroups and channel chats. description (:obj:`str`, optional): Description, for groups, supergroups and channel chats.
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and invite_link (:obj:`str`, optional): Primary invite link, for groups, supergroups and
@ -97,6 +102,10 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4 .. versionadded:: 13.4
has_protected_content (:obj:`bool`, optional): :obj:`True`, if messages from the chat can't
be forwarded to other chats. Returned only in :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.9
bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods. bot (:class:`telegram.Bot`, optional): The Bot to use for instance methods.
sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set. sticker_set_name (:obj:`str`, optional): For supergroups, name of group sticker set.
Returned only in :meth:`telegram.Bot.get_chat`. Returned only in :meth:`telegram.Bot.get_chat`.
@ -119,6 +128,11 @@ class Chat(TelegramObject):
photo (:class:`telegram.ChatPhoto`): Optional. Chat photo. photo (:class:`telegram.ChatPhoto`): Optional. Chat photo.
bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in bio (:obj:`str`): Optional. Bio of the other party in a private chat. Returned only in
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
has_private_forwards (:obj:`bool`): Optional. :obj:`True`, if privacy settings of the other
party in the private chat allows to use ``tg://user?id=<user_id>`` links only in chats
with the user.
.. versionadded:: 13.9
description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats. description (:obj:`str`): Optional. Description, for groups, supergroups and channel chats.
invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and invite_link (:obj:`str`): Optional. Primary invite link, for groups, supergroups and
channel. Returned only in :meth:`telegram.Bot.get_chat`. channel. Returned only in :meth:`telegram.Bot.get_chat`.
@ -134,6 +148,10 @@ class Chat(TelegramObject):
:meth:`telegram.Bot.get_chat`. :meth:`telegram.Bot.get_chat`.
.. versionadded:: 13.4 .. versionadded:: 13.4
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if messages from the chat can't
be forwarded to other chats.
.. versionadded:: 13.9
sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set. sticker_set_name (:obj:`str`): Optional. For supergroups, name of Group sticker set.
can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the can_set_sticker_set (:obj:`bool`): Optional. :obj:`True`, if the bot can change group the
sticker set. sticker set.
@ -166,6 +184,8 @@ class Chat(TelegramObject):
'linked_chat_id', 'linked_chat_id',
'all_members_are_administrators', 'all_members_are_administrators',
'message_auto_delete_time', 'message_auto_delete_time',
'has_protected_content',
'has_private_forwards',
'_id_attrs', '_id_attrs',
) )
@ -204,6 +224,8 @@ class Chat(TelegramObject):
linked_chat_id: int = None, linked_chat_id: int = None,
location: ChatLocation = None, location: ChatLocation = None,
message_auto_delete_time: int = None, message_auto_delete_time: int = None,
has_private_forwards: bool = None,
has_protected_content: bool = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -218,6 +240,7 @@ class Chat(TelegramObject):
self.all_members_are_administrators = _kwargs.get('all_members_are_administrators') self.all_members_are_administrators = _kwargs.get('all_members_are_administrators')
self.photo = photo self.photo = photo
self.bio = bio self.bio = bio
self.has_private_forwards = has_private_forwards
self.description = description self.description = description
self.invite_link = invite_link self.invite_link = invite_link
self.pinned_message = pinned_message self.pinned_message = pinned_message
@ -226,6 +249,7 @@ class Chat(TelegramObject):
self.message_auto_delete_time = ( self.message_auto_delete_time = (
int(message_auto_delete_time) if message_auto_delete_time is not None else None int(message_auto_delete_time) if message_auto_delete_time is not None else None
) )
self.has_protected_content = has_protected_content
self.sticker_set_name = sticker_set_name self.sticker_set_name = sticker_set_name
self.can_set_sticker_set = can_set_sticker_set self.can_set_sticker_set = can_set_sticker_set
self.linked_chat_id = linked_chat_id self.linked_chat_id = linked_chat_id
@ -433,6 +457,98 @@ class Chat(TelegramObject):
revoke_messages=revoke_messages, revoke_messages=revoke_messages,
) )
def ban_sender_chat(
self,
sender_chat_id: int,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.ban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.ban_chat_sender_chat`.
.. versionadded:: 13.9
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.ban_chat_sender_chat(
chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs
)
def ban_chat(
self,
chat_id: Union[str, int],
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.ban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.ban_chat_sender_chat`.
.. versionadded:: 13.9
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.ban_chat_sender_chat(
chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
)
def unban_sender_chat(
self,
sender_chat_id: int,
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.unban_chat_sender_chat(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.unban_chat_sender_chat`.
.. versionadded:: 13.9
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.unban_chat_sender_chat(
chat_id=self.id, sender_chat_id=sender_chat_id, timeout=timeout, api_kwargs=api_kwargs
)
def unban_chat(
self,
chat_id: Union[str, int],
timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict = None,
) -> bool:
"""Shortcut for::
bot.unban_chat_sender_chat(sender_chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.unban_chat_sender_chat`.
.. versionadded:: 13.9
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return self.bot.unban_chat_sender_chat(
chat_id=chat_id, sender_chat_id=self.id, timeout=timeout, api_kwargs=api_kwargs
)
def unban_member( def unban_member(
self, self,
user_id: Union[str, int], user_id: Union[str, int],

View file

@ -34,6 +34,12 @@ class ChatJoinRequest(TelegramObject):
Objects of this class are comparable in terms of equality. Two objects of this class are Objects of this class are comparable in terms of equality. Two objects of this class are
considered equal, if their :attr:`chat`, :attr:`from_user` and :attr:`date` are equal. considered equal, if their :attr:`chat`, :attr:`from_user` and :attr:`date` are equal.
Note:
Since Bot API 5.5, bots are allowed to contact users who sent a join request to a chat
where the bot is an administrator with the
:attr:`~telegram.ChatMemberAdministrator.can_invite_users` administrator right even if
the user never interacted with the bot before.
.. versionadded:: 13.8 .. versionadded:: 13.8
Args: Args:

View file

@ -21,7 +21,7 @@ The following constants were extracted from the
`Telegram Bots API <https://core.telegram.org/bots/api>`_. `Telegram Bots API <https://core.telegram.org/bots/api>`_.
Attributes: Attributes:
BOT_API_VERSION (:obj:`str`): `5.3`. Telegram Bot API version supported by this BOT_API_VERSION (:obj:`str`): `5.5`. Telegram Bot API version supported by this
version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``. version of `python-telegram-bot`. Also available as ``telegram.bot_api_version``.
.. versionadded:: 13.4 .. versionadded:: 13.4
@ -48,6 +48,10 @@ Attributes:
ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin) ANONYMOUS_ADMIN_ID (:obj:`int`): ``1087968824`` (User id in groups for anonymous admin)
SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of SERVICE_CHAT_ID (:obj:`int`): ``777000`` (Telegram service chat, that also acts as sender of
channel posts forwarded to discussion groups) channel posts forwarded to discussion groups)
FAKE_CHANNEL_ID (:obj:`int`): ``136817688`` (User id in groups when message is sent on behalf
of a channel).
.. versionadded:: 13.9
The following constants are related to specific classes and are also available The following constants are related to specific classes and are also available
as attributes of those classes: as attributes of those classes:
@ -240,11 +244,12 @@ Attributes:
""" """
from typing import List from typing import List
BOT_API_VERSION: str = '5.4' BOT_API_VERSION: str = '5.5'
MAX_MESSAGE_LENGTH: int = 4096 MAX_MESSAGE_LENGTH: int = 4096
MAX_CAPTION_LENGTH: int = 1024 MAX_CAPTION_LENGTH: int = 1024
ANONYMOUS_ADMIN_ID: int = 1087968824 ANONYMOUS_ADMIN_ID: int = 1087968824
SERVICE_CHAT_ID: int = 777000 SERVICE_CHAT_ID: int = 777000
FAKE_CHANNEL_ID: int = 136817688
# constants above this line are tested # constants above this line are tested

View file

@ -1965,16 +1965,16 @@ officedocument.wordprocessingml.document")``.
class sender_chat(_ChatUserBaseFilter): class sender_chat(_ChatUserBaseFilter):
# pylint: disable=W0235 # pylint: disable=W0235
"""Filters messages to allow only those which are from a specified sender chats chat ID or """Filters messages to allow only those which are from a specified sender chat's chat ID or
username. username.
Examples: Examples:
* To filter for messages forwarded to a discussion group from a channel with ID * To filter for messages sent to a group by a channel with ID
``-1234``, use ``MessageHandler(Filters.sender_chat(-1234), callback_method)``. ``-1234``, use ``MessageHandler(Filters.sender_chat(-1234), callback_method)``.
* To filter for messages of anonymous admins in a super group with username * To filter for messages of anonymous admins in a super group with username
``@anonymous``, use ``@anonymous``, use
``MessageHandler(Filters.sender_chat(username='anonymous'), callback_method)``. ``MessageHandler(Filters.sender_chat(username='anonymous'), callback_method)``.
* To filter for messages forwarded to a discussion group from *any* channel, use * To filter for messages sent to a group by *any* channel, use
``MessageHandler(Filters.sender_chat.channel, callback_method)``. ``MessageHandler(Filters.sender_chat.channel, callback_method)``.
* To filter for messages of anonymous admins in *any* super group, use * To filter for messages of anonymous admins in *any* super group, use
``MessageHandler(Filters.sender_chat.super_group, callback_method)``. ``MessageHandler(Filters.sender_chat.super_group, callback_method)``.
@ -1983,7 +1983,10 @@ officedocument.wordprocessingml.document")``.
Remember, ``sender_chat`` is also set for messages in a channel as the channel itself, Remember, ``sender_chat`` is also set for messages in a channel as the channel itself,
so when your bot is an admin in a channel and the linked discussion group, you would so when your bot is an admin in a channel and the linked discussion group, you would
receive the message twice (once from inside the channel, once inside the discussion receive the message twice (once from inside the channel, once inside the discussion
group). group). Since v13.9, the field :attr:`telegram.Message.is_automatic_forward` will be
:obj:`True` for the discussion group message.
.. seealso:: :attr:`Filters.is_automatic_forward`
Warning: Warning:
:attr:`chat_ids` will return a *copy* of the saved chat ids as :class:`frozenset`. This :attr:`chat_ids` will return a *copy* of the saved chat ids as :class:`frozenset`. This
@ -2089,6 +2092,32 @@ officedocument.wordprocessingml.document")``.
super_group = _SuperGroup() super_group = _SuperGroup()
channel = _Channel() channel = _Channel()
class _IsAutomaticForward(MessageFilter):
__slots__ = ()
name = 'Filters.is_automatic_forward'
def filter(self, message: Message) -> bool:
return bool(message.is_automatic_forward)
is_automatic_forward = _IsAutomaticForward()
"""Messages that contain :attr:`telegram.Message.is_automatic_forward`.
.. versionadded:: 13.9
"""
class _HasProtectedContent(MessageFilter):
__slots__ = ()
name = 'Filters.has_protected_content'
def filter(self, message: Message) -> bool:
return bool(message.has_protected_content)
has_protected_content = _HasProtectedContent()
"""Messages that contain :attr:`telegram.Message.has_protected_content`.
.. versionadded:: 13.9
"""
class _Invoice(MessageFilter): class _Invoice(MessageFilter):
__slots__ = () __slots__ = ()
name = 'Filters.invoice' name = 'Filters.invoice'

View file

@ -45,6 +45,10 @@ class InlineKeyboardButton(TelegramObject):
.. versionadded:: 13.6 .. versionadded:: 13.6
* Since Bot API 5.5, it's now allowed to mention users by their ID in inline keyboards.
This will only work in Telegram versions released after December 7, 2021.
Older clients will display *unsupported message*.
Warning: Warning:
If your bot allows your arbitrary callback data, buttons whose callback data is a If your bot allows your arbitrary callback data, buttons whose callback data is a
non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will non-hashable object will become unhashable. Trying to evaluate ``hash(button)`` will
@ -54,7 +58,12 @@ class InlineKeyboardButton(TelegramObject):
Args: Args:
text (:obj:`str`): Label text on the button. text (:obj:`str`): Label text on the button.
url (:obj:`str`, optional): HTTP or tg:// url to be opened when button is pressed. url (:obj:`str`, optional): HTTP or tg:// url to be opened when the button is pressed.
Links ``tg://user?id=<user_id>`` can be used to mention a user by
their ID without using a username, if this is allowed by their privacy settings.
.. versionchanged:: 13.9
You can now mention a user using ``tg://user?id=<user_id>``.
login_url (:class:`telegram.LoginUrl`, optional): An HTTP URL used to automatically login_url (:class:`telegram.LoginUrl`, optional): An HTTP URL used to automatically
authorize the user. Can be used as a replacement for the Telegram Login Widget. authorize the user. Can be used as a replacement for the Telegram Login Widget.
callback_data (:obj:`str` | :obj:`Any`, optional): Data to be sent in a callback query to callback_data (:obj:`str` | :obj:`Any`, optional): Data to be sent in a callback query to
@ -76,12 +85,18 @@ class InlineKeyboardButton(TelegramObject):
be launched when the user presses the button. This type of button must always be be launched when the user presses the button. This type of button must always be
the ``first`` button in the first row. the ``first`` button in the first row.
pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button pay (:obj:`bool`, optional): Specify :obj:`True`, to send a Pay button. This type of button
must always be the ``first`` button in the first row. must always be the `first` button in the first row and can only be used in invoice
messages.
**kwargs (:obj:`dict`): Arbitrary keyword arguments. **kwargs (:obj:`dict`): Arbitrary keyword arguments.
Attributes: Attributes:
text (:obj:`str`): Label text on the button. text (:obj:`str`): Label text on the button.
url (:obj:`str`): Optional. HTTP or tg:// url to be opened when button is pressed. url (:obj:`str`): Optional. HTTP or tg:// url to be opened when the button is pressed.
Links ``tg://user?id=<user_id>`` can be used to mention a user by
their ID without using a username, if this is allowed by their privacy settings.
.. versionchanged:: 13.9
You can now mention a user using ``tg://user?id=<user_id>``.
login_url (:class:`telegram.LoginUrl`): Optional. An HTTP URL used to automatically login_url (:class:`telegram.LoginUrl`): Optional. An HTTP URL used to automatically
authorize the user. Can be used as a replacement for the Telegram Login Widget. authorize the user. Can be used as a replacement for the Telegram Login Widget.
callback_data (:obj:`str` | :obj:`object`): Optional. Data to be sent in a callback query callback_data (:obj:`str` | :obj:`object`): Optional. Data to be sent in a callback query

View file

@ -90,12 +90,15 @@ class Message(TelegramObject):
Args: Args:
message_id (:obj:`int`): Unique message identifier inside this chat. message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`, optional): Sender, empty for messages sent from_user (:class:`telegram.User`, optional): Sender of the message; empty for messages
to channels. sent to channels. For backward compatibility, this will contain a fake sender user in
non-channel chats, if the message was sent on behalf of a chat.
sender_chat (:class:`telegram.Chat`, optional): Sender of the message, sent on behalf of a sender_chat (:class:`telegram.Chat`, optional): Sender of the message, sent on behalf of a
chat. The channel itself for channel messages. The supergroup itself for messages from chat. For example, the channel itself for channel posts, the supergroup itself for
anonymous group administrators. The linked channel for messages automatically forwarded messages from anonymous group administrators, the linked channel for messages
to the discussion group. automatically forwarded to the discussion group. For backward compatibility,
:attr:`from_user` contains a fake sender user in non-channel chats, if the message was
sent on behalf of a chat.
date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to date (:class:`datetime.datetime`): Date the message was sent in Unix time. Converted to
:class:`datetime.datetime`. :class:`datetime.datetime`.
chat (:class:`telegram.Chat`): Conversation the message belongs to. chat (:class:`telegram.Chat`): Conversation the message belongs to.
@ -109,9 +112,17 @@ class Message(TelegramObject):
who disallow adding a link to their account in forwarded messages. who disallow adding a link to their account in forwarded messages.
forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the forward_date (:class:`datetime.datetime`, optional): For forwarded messages, date the
original message was sent in Unix time. Converted to :class:`datetime.datetime`. original message was sent in Unix time. Converted to :class:`datetime.datetime`.
is_automatic_forward (:obj:`bool`, optional): :obj:`True`, if the message is a channel post
that was automatically forwarded to the connected discussion group.
.. versionadded:: 13.9
reply_to_message (:class:`telegram.Message`, optional): For replies, the original message. reply_to_message (:class:`telegram.Message`, optional): For replies, the original message.
edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix edit_date (:class:`datetime.datetime`, optional): Date the message was last edited in Unix
time. Converted to :class:`datetime.datetime`. time. Converted to :class:`datetime.datetime`.
has_protected_content (:obj:`bool`, optional): :obj:`True`, if the message can't be
forwarded.
.. versionadded:: 13.9
media_group_id (:obj:`str`, optional): The unique identifier of a media message group this media_group_id (:obj:`str`, optional): The unique identifier of a media message group this
message belongs to. message belongs to.
text (str, optional): For text messages, the actual UTF-8 text of the message, 0-4096 text (str, optional): For text messages, the actual UTF-8 text of the message, 0-4096
@ -225,11 +236,12 @@ class Message(TelegramObject):
Attributes: Attributes:
message_id (:obj:`int`): Unique message identifier inside this chat. message_id (:obj:`int`): Unique message identifier inside this chat.
from_user (:class:`telegram.User`): Optional. Sender. from_user (:class:`telegram.User`): Optional. Sender of the message; empty for messages
sent to channels. For backward compatibility, this will contain a fake sender user in
non-channel chats, if the message was sent on behalf of a chat.
sender_chat (:class:`telegram.Chat`): Optional. Sender of the message, sent on behalf of a sender_chat (:class:`telegram.Chat`): Optional. Sender of the message, sent on behalf of a
chat. The channel itself for channel messages. The supergroup itself for messages from chat. For backward compatibility, :attr:`from_user` contains a fake sender user in
anonymous group administrators. The linked channel for messages automatically forwarded non-channel chats, if the message was sent on behalf of a chat.
to the discussion group.
date (:class:`datetime.datetime`): Date the message was sent. date (:class:`datetime.datetime`): Date the message was sent.
chat (:class:`telegram.Chat`): Conversation the message belongs to. chat (:class:`telegram.Chat`): Conversation the message belongs to.
forward_from (:class:`telegram.User`): Optional. Sender of the original message. forward_from (:class:`telegram.User`): Optional. Sender of the original message.
@ -238,10 +250,18 @@ class Message(TelegramObject):
forward_from_message_id (:obj:`int`): Optional. Identifier of the original message in the forward_from_message_id (:obj:`int`): Optional. Identifier of the original message in the
channel. channel.
forward_date (:class:`datetime.datetime`): Optional. Date the original message was sent. forward_date (:class:`datetime.datetime`): Optional. Date the original message was sent.
is_automatic_forward (:obj:`bool`): Optional. :obj:`True`, if the message is a channel post
that was automatically forwarded to the connected discussion group.
.. versionadded:: 13.9
reply_to_message (:class:`telegram.Message`): Optional. For replies, the original message. reply_to_message (:class:`telegram.Message`): Optional. For replies, the original message.
Note that the Message object in this field will not contain further Note that the Message object in this field will not contain further
``reply_to_message`` fields even if it itself is a reply. ``reply_to_message`` fields even if it itself is a reply.
edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited. edit_date (:class:`datetime.datetime`): Optional. Date the message was last edited.
has_protected_content (:obj:`bool`): Optional. :obj:`True`, if the message can't be
forwarded.
.. versionadded:: 13.9
media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this
message belongs to. message belongs to.
text (:obj:`str`): Optional. The actual UTF-8 text of the message. text (:obj:`str`): Optional. The actual UTF-8 text of the message.
@ -390,6 +410,8 @@ class Message(TelegramObject):
'voice_chat_participants_invited', 'voice_chat_participants_invited',
'voice_chat_started', 'voice_chat_started',
'voice_chat_scheduled', 'voice_chat_scheduled',
'is_automatic_forward',
'has_protected_content',
'_id_attrs', '_id_attrs',
) )
@ -492,6 +514,8 @@ class Message(TelegramObject):
voice_chat_participants_invited: VoiceChatParticipantsInvited = None, voice_chat_participants_invited: VoiceChatParticipantsInvited = None,
message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None, message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = None,
voice_chat_scheduled: VoiceChatScheduled = None, voice_chat_scheduled: VoiceChatScheduled = None,
is_automatic_forward: bool = None,
has_protected_content: bool = None,
**_kwargs: Any, **_kwargs: Any,
): ):
# Required # Required
@ -504,8 +528,10 @@ class Message(TelegramObject):
self.forward_from = forward_from self.forward_from = forward_from
self.forward_from_chat = forward_from_chat self.forward_from_chat = forward_from_chat
self.forward_date = forward_date self.forward_date = forward_date
self.is_automatic_forward = is_automatic_forward
self.reply_to_message = reply_to_message self.reply_to_message = reply_to_message
self.edit_date = edit_date self.edit_date = edit_date
self.has_protected_content = has_protected_content
self.text = text self.text = text
self.entities = entities or [] self.entities = entities or []
self.caption_entities = caption_entities or [] self.caption_entities = caption_entities or []
@ -1795,6 +1821,14 @@ class Message(TelegramObject):
For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`. For the documentation of the arguments, please see :meth:`telegram.Bot.forward_message`.
Note:
Since the release of Bot API 5.5 it can be impossible to forward messages from
some chats. Use the attributes :attr:`telegram.Message.has_protected_content` and
:attr:`telegram.Chat.has_protected_content` to check this.
As a workaround, it is still possible to use :meth:`copy`. However, this
behaviour is undocumented and might be changed by Telegram.
Returns: Returns:
:class:`telegram.Message`: On success, instance representing the message forwarded. :class:`telegram.Message`: On success, instance representing the message forwarded.

View file

@ -22,6 +22,7 @@ from datetime import datetime
from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple from typing import TYPE_CHECKING, Any, List, Optional, Union, Tuple
from telegram import TelegramObject, constants from telegram import TelegramObject, constants
from telegram.inline.inlinekeyboardbutton import InlineKeyboardButton
from telegram.utils.helpers import ( from telegram.utils.helpers import (
mention_html as util_mention_html, mention_html as util_mention_html,
DEFAULT_NONE, DEFAULT_NONE,
@ -233,6 +234,22 @@ class User(TelegramObject):
return util_mention_html(self.id, name) return util_mention_html(self.id, name)
return util_mention_html(self.id, self.full_name) return util_mention_html(self.id, self.full_name)
def mention_button(self, name: str = None) -> InlineKeyboardButton:
"""
Shortcut for::
InlineKeyboardButton(text=name, url=f"tg://user?id={update.effective_user.id}")
.. versionadded:: 13.9
Args:
name (:obj:`str`): The name used as a link for the user. Defaults to :attr:`full_name`.
Returns:
:class:`telegram.InlineKeyboardButton`: InlineButton with url set to the user mention
"""
return InlineKeyboardButton(text=name or self.full_name, url=f"tg://user?id={self.id}")
def pin_message( def pin_message(
self, self,
message_id: int, message_id: int,

View file

@ -1012,6 +1012,17 @@ class TestBot:
assert tz_bot.ban_chat_member(2, 32, until_date=until) assert tz_bot.ban_chat_member(2, 32, until_date=until)
assert tz_bot.ban_chat_member(2, 32, until_date=until_timestamp) assert tz_bot.ban_chat_member(2, 32, until_date=until_timestamp)
def test_ban_chat_sender_chat(self, monkeypatch, bot):
# For now, we just test that we pass the correct data to TG
def make_assertion(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2
sender_chat_id = data['sender_chat_id'] == 32
return chat_id and sender_chat_id
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.ban_chat_sender_chat(2, 32)
monkeypatch.delattr(bot.request, 'post')
def test_kick_chat_member_warning(self, monkeypatch, bot, recwarn): def test_kick_chat_member_warning(self, monkeypatch, bot, recwarn):
def test(url, data, *args, **kwargs): def test(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2 chat_id = data['chat_id'] == 2
@ -1037,6 +1048,15 @@ class TestBot:
assert bot.unban_chat_member(2, 32, only_if_banned=only_if_banned) assert bot.unban_chat_member(2, 32, only_if_banned=only_if_banned)
def test_unban_chat_sender_chat(self, monkeypatch, bot):
def make_assertion(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2
sender_chat_id = data['sender_chat_id'] == 32
return chat_id and sender_chat_id
monkeypatch.setattr(bot.request, 'post', make_assertion)
assert bot.unbanChatSenderChat(2, 32)
def test_set_chat_permissions(self, monkeypatch, bot, chat_permissions): def test_set_chat_permissions(self, monkeypatch, bot, chat_permissions):
def test(url, data, *args, **kwargs): def test(url, data, *args, **kwargs):
chat_id = data['chat_id'] == 2 chat_id = data['chat_id'] == 2

View file

@ -41,6 +41,8 @@ def chat(bot):
bio=TestChat.bio, bio=TestChat.bio,
linked_chat_id=TestChat.linked_chat_id, linked_chat_id=TestChat.linked_chat_id,
location=TestChat.location, location=TestChat.location,
has_private_forwards=True,
has_protected_content=True,
) )
@ -62,6 +64,8 @@ class TestChat:
bio = "I'm a Barbie Girl in a Barbie World" bio = "I'm a Barbie Girl in a Barbie World"
linked_chat_id = 11880 linked_chat_id = 11880
location = ChatLocation(Location(123, 456), 'Barbie World') location = ChatLocation(Location(123, 456), 'Barbie World')
has_protected_content = True
has_private_forwards = True
def test_slot_behaviour(self, chat, recwarn, mro_slots): def test_slot_behaviour(self, chat, recwarn, mro_slots):
for attr in chat.__slots__: for attr in chat.__slots__:
@ -84,6 +88,8 @@ class TestChat:
'slow_mode_delay': self.slow_mode_delay, 'slow_mode_delay': self.slow_mode_delay,
'message_auto_delete_time': self.message_auto_delete_time, 'message_auto_delete_time': self.message_auto_delete_time,
'bio': self.bio, 'bio': self.bio,
'has_protected_content': self.has_protected_content,
'has_private_forwards': self.has_private_forwards,
'linked_chat_id': self.linked_chat_id, 'linked_chat_id': self.linked_chat_id,
'location': self.location.to_dict(), 'location': self.location.to_dict(),
} }
@ -100,6 +106,8 @@ class TestChat:
assert chat.slow_mode_delay == self.slow_mode_delay assert chat.slow_mode_delay == self.slow_mode_delay
assert chat.message_auto_delete_time == self.message_auto_delete_time assert chat.message_auto_delete_time == self.message_auto_delete_time
assert chat.bio == self.bio assert chat.bio == self.bio
assert chat.has_protected_content == self.has_protected_content
assert chat.has_private_forwards == self.has_private_forwards
assert chat.linked_chat_id == self.linked_chat_id assert chat.linked_chat_id == self.linked_chat_id
assert chat.location.location == self.location.location assert chat.location.location == self.location.location
assert chat.location.address == self.location.address assert chat.location.address == self.location.address
@ -117,6 +125,8 @@ class TestChat:
assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay assert chat_dict['slow_mode_delay'] == chat.slow_mode_delay
assert chat_dict['message_auto_delete_time'] == chat.message_auto_delete_time assert chat_dict['message_auto_delete_time'] == chat.message_auto_delete_time
assert chat_dict['bio'] == chat.bio assert chat_dict['bio'] == chat.bio
assert chat_dict['has_private_forwards'] == chat.has_private_forwards
assert chat_dict['has_protected_content'] == chat.has_protected_content
assert chat_dict['linked_chat_id'] == chat.linked_chat_id assert chat_dict['linked_chat_id'] == chat.linked_chat_id
assert chat_dict['location'] == chat.location.to_dict() assert chat_dict['location'] == chat.location.to_dict()
@ -225,6 +235,36 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'ban_chat_member', make_assertion) monkeypatch.setattr(chat.bot, 'ban_chat_member', make_assertion)
assert chat.ban_member(user_id=42, until_date=43) assert chat.ban_member(user_id=42, until_date=43)
def test_ban_sender_chat(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == chat.id
sender_chat_id = kwargs['sender_chat_id'] == 42
return chat_id and sender_chat_id
assert check_shortcut_signature(
Chat.ban_sender_chat, Bot.ban_chat_sender_chat, ['chat_id'], []
)
assert check_shortcut_call(chat.ban_sender_chat, chat.bot, 'ban_chat_sender_chat')
assert check_defaults_handling(chat.ban_sender_chat, chat.bot)
monkeypatch.setattr(chat.bot, 'ban_chat_sender_chat', make_assertion)
assert chat.ban_sender_chat(42)
def test_ban_chat(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == 42
sender_chat_id = kwargs['sender_chat_id'] == chat.id
return chat_id and sender_chat_id
assert check_shortcut_signature(
Chat.ban_chat, Bot.ban_chat_sender_chat, ['sender_chat_id'], []
)
assert check_shortcut_call(chat.ban_chat, chat.bot, 'ban_chat_sender_chat')
assert check_defaults_handling(chat.ban_chat, chat.bot)
monkeypatch.setattr(chat.bot, 'ban_chat_sender_chat', make_assertion)
assert chat.ban_chat(42)
def test_kick_member_warning(self, chat, monkeypatch, recwarn): def test_kick_member_warning(self, chat, monkeypatch, recwarn):
def make_assertion(*_, **kwargs): def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == chat.id chat_id = kwargs['chat_id'] == chat.id
@ -252,6 +292,36 @@ class TestChat:
monkeypatch.setattr(chat.bot, 'unban_chat_member', make_assertion) monkeypatch.setattr(chat.bot, 'unban_chat_member', make_assertion)
assert chat.unban_member(user_id=42, only_if_banned=only_if_banned) assert chat.unban_member(user_id=42, only_if_banned=only_if_banned)
def test_unban_sender_chat(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == chat.id
sender_chat_id = kwargs['sender_chat_id'] == 42
return chat_id and sender_chat_id
assert check_shortcut_signature(
Chat.unban_sender_chat, Bot.unban_chat_sender_chat, ['chat_id'], []
)
assert check_shortcut_call(chat.unban_sender_chat, chat.bot, 'unban_chat_sender_chat')
assert check_defaults_handling(chat.unban_sender_chat, chat.bot)
monkeypatch.setattr(chat.bot, 'unban_chat_sender_chat', make_assertion)
assert chat.unban_sender_chat(42)
def test_unban_chat(self, monkeypatch, chat):
def make_assertion(*_, **kwargs):
chat_id = kwargs['chat_id'] == 42
sender_chat_id = kwargs['sender_chat_id'] == chat.id
return chat_id and sender_chat_id
assert check_shortcut_signature(
Chat.unban_chat, Bot.ban_chat_sender_chat, ['sender_chat_id'], []
)
assert check_shortcut_call(chat.unban_chat, chat.bot, 'unban_chat_sender_chat')
assert check_defaults_handling(chat.unban_chat, chat.bot)
monkeypatch.setattr(chat.bot, 'unban_chat_sender_chat', make_assertion)
assert chat.unban_chat(42)
@pytest.mark.parametrize('is_anonymous', [True, False, None]) @pytest.mark.parametrize('is_anonymous', [True, False, None])
def test_promote_member(self, monkeypatch, chat, is_anonymous): def test_promote_member(self, monkeypatch, chat, is_anonymous):
def make_assertion(*_, **kwargs): def make_assertion(*_, **kwargs):

View file

@ -1718,6 +1718,16 @@ class TestFilters:
update.message.sender_chat = None update.message.sender_chat = None
assert not Filters.sender_chat.channel(update) assert not Filters.sender_chat.channel(update)
def test_filters_is_automatic_forward(self, update):
assert not Filters.is_automatic_forward(update)
update.message.is_automatic_forward = True
assert Filters.is_automatic_forward(update)
def test_filters_has_protected_content(self, update):
assert not Filters.has_protected_content(update)
update.message.has_protected_content = True
assert Filters.has_protected_content(update)
def test_filters_invoice(self, update): def test_filters_invoice(self, update):
assert not Filters.invoice(update) assert not Filters.invoice(update)
update.message.invoice = 'test' update.message.invoice = 'test'

View file

@ -180,6 +180,8 @@ def message(bot):
) )
}, },
{'sender_chat': Chat(-123, 'discussion_channel')}, {'sender_chat': Chat(-123, 'discussion_channel')},
{'is_automatic_forward': True},
{'has_protected_content': True},
], ],
ids=[ ids=[
'forwarded_user', 'forwarded_user',
@ -229,6 +231,8 @@ def message(bot):
'voice_chat_ended', 'voice_chat_ended',
'voice_chat_participants_invited', 'voice_chat_participants_invited',
'sender_chat', 'sender_chat',
'is_automatic_forward',
'has_protected_content',
], ],
) )
def message_params(bot, request): def message_params(bot, request):

View file

@ -18,7 +18,7 @@
# 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 pytest import pytest
from telegram import Update, User, Bot from telegram import Update, User, Bot, InlineKeyboardButton
from telegram.utils.helpers import escape_markdown from telegram.utils.helpers import escape_markdown
from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling from tests.conftest import check_shortcut_signature, check_shortcut_call, check_defaults_handling
@ -473,6 +473,13 @@ class TestUser:
) )
assert user.mention_html(user.username) == expected.format(user.id, user.username) assert user.mention_html(user.username) == expected.format(user.id, user.username)
def test_mention_button(self, user):
expected_name = InlineKeyboardButton(text="Bob", url=f"tg://user?id={user.id}")
expected_full = InlineKeyboardButton(text=user.full_name, url=f"tg://user?id={user.id}")
assert user.mention_button("Bob") == expected_name
assert user.mention_button() == expected_full
def test_mention_markdown(self, user): def test_mention_markdown(self, user):
expected = '[{}](tg://user?id={})' expected = '[{}](tg://user?id={})'