Full support for Bot API 9.5 (#5155)

Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: OuYoung <212045739+ouyooung@users.noreply.github.com>
Co-authored-by: Hethon <65696516+hethon@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
This commit is contained in:
Poolitzer
2026-03-15 07:58:30 +01:00
committed by GitHub
parent fb5234d9f5
commit 0cceafcab3
22 changed files with 569 additions and 24 deletions
+2 -2
View File
@@ -11,7 +11,7 @@
:target: https://pypi.org/project/python-telegram-bot/
:alt: Supported Python versions
.. image:: https://img.shields.io/badge/Bot%20API-9.4-blue?logo=telegram
.. image:: https://img.shields.io/badge/Bot%20API-9.5-blue?logo=telegram
:target: https://core.telegram.org/bots/api-changelog
:alt: Supported Bot API version
@@ -77,7 +77,7 @@ After installing_ the library, be sure to check out the section on `working with
Telegram API support
~~~~~~~~~~~~~~~~~~~~
All types and methods of the Telegram Bot API **9.4** are natively supported by this library.
All types and methods of the Telegram Bot API **9.5** are natively supported by this library.
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
Notable Features
@@ -0,0 +1,5 @@
features = "Full support for Bot API 9.5"
[[pull_requests]]
uid = "5155"
author_uids = ["Poolitzer"]
closes_threads = []
+2
View File
@@ -183,6 +183,8 @@
- Used for getting the list of boosts added to a chat
* - :meth:`~telegram.Bot.leave_chat`
- Used for leaving a chat
* - :meth:`~telegram.Bot.set_chat_member_tag`
- Used for setting the tag of a chat member
.. raw:: html
+61 -1
View File
@@ -1218,10 +1218,14 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
api_kwargs: JSONDict | None = None,
) -> bool:
"""Use this method to stream a partial message to a user while the message is being
generated; supported only for bots with forum topic mode enabled.
generated.
.. versionadded:: 22.6
.. versionchanged:: NEXT.VERSION
Now all bots can use this method.
Args:
chat_id (:obj:`int`): Unique identifier for the target private chat.
draft_id (:obj:`int`): Unique identifier of the message draft; must be non-zero.
@@ -5932,6 +5936,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
can_edit_stories: bool | None = None,
can_delete_stories: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -6007,6 +6012,10 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
posts; for channels only
.. versionadded:: 22.4
can_manage_tags (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
edit the tags of regular members; for groups and supergroups only.
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
@@ -6034,6 +6043,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
"can_edit_stories": can_edit_stories,
"can_delete_stories": can_delete_stories,
"can_manage_direct_messages": can_manage_direct_messages,
"can_manage_tags": can_manage_tags,
}
return await self._post(
@@ -12019,6 +12029,54 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
return UserProfileAudios.de_json(result, self)
async def set_chat_member_tag(
self,
chat_id: int | str,
user_id: int,
tag: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""
Use this method to set a tag for a regular member in a group or a supergroup. The bot must
be an administrator in the chat for this to work and must have the
:attr:`~telegram.ChatMemberAdministrator.can_manage_tags` administrator right.
.. versionadded:: NEXT.VERSION
Args:
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
user_id (:obj:`int`): Unique identifier of the target user.
tag (:obj:`str`, optional): New tag for the member;
0-:tg-const:`telegram.constants.TagLimit.MAX_TAG_LENGTH` characters, emoji are not
allowed.
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
Raises:
:class:`telegram.error.TelegramError`
"""
data: JSONDict = {
"chat_id": chat_id,
"user_id": user_id,
"tag": tag,
}
return await self._post(
"setChatMemberTag",
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: # noqa: ARG002
"""See :meth:`telegram.TelegramObject.to_dict`."""
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
@@ -12357,3 +12415,5 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
"""Alias for :meth:`set_my_profile_photo`"""
getUserProfileAudios = get_user_profile_audios
"""Alias for :meth:`get_user_profile_audios`"""
setChatMemberTag = set_chat_member_tag
"""Alias for :meth:`set_chat_member_tag`"""
+37
View File
@@ -619,6 +619,7 @@ class _ChatBase(TelegramObject):
can_edit_stories: bool | None = None,
can_delete_stories: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -670,6 +671,7 @@ class _ChatBase(TelegramObject):
can_edit_stories=can_edit_stories,
can_delete_stories=can_delete_stories,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
)
async def restrict_member(
@@ -3995,6 +3997,41 @@ class _ChatBase(TelegramObject):
api_kwargs=api_kwargs,
)
async def set_chat_member_tag(
self,
user_id: int,
tag: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""
Shortcut for::
await bot.set_chat_member_tag(chat_id=update.effective_chat.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_member_tag`.
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().set_chat_member_tag(
chat_id=self.id,
user_id=user_id,
tag=tag,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
class Chat(_ChatBase):
"""This object represents a chat.
+20 -1
View File
@@ -31,7 +31,8 @@ class ChatAdministratorRights(TelegramObject):
:attr:`can_promote_members`, :attr:`can_change_info`, :attr:`can_invite_users`,
:attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages`,
:attr:`can_manage_topics`, :attr:`can_post_stories`, :attr:`can_delete_stories`,
:attr:`can_edit_stories` and :attr:`can_manage_direct_messages` are equal.
:attr:`can_edit_stories`, :attr:`can_manage_direct_messages` and :attr:`can_manage_tags` are
equal.
.. versionadded:: 20.0
@@ -52,6 +53,10 @@ class ChatAdministratorRights(TelegramObject):
:attr:`can_manage_direct_messages` is considered as well when comparing objects of
this type in terms of equality.
.. versionchanged:: NEXT.VERSION
:attr:`can_manage_tags` is considered as well when comparing objects of this type in terms
of equality.
Args:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
@@ -104,6 +109,11 @@ class ChatAdministratorRights(TelegramObject):
manage direct messages of the channel and decline suggested posts; for channels only.
.. versionadded:: 22.4
can_manage_tags (:obj:`bool`, optional): :obj:`True`, if the administrator can edit the
tags of regular members; for groups and supergroups only. If omitted defaults to the
value of :attr:`can_pin_messages`.
.. versionadded:: NEXT.VERSION
Attributes:
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
@@ -157,6 +167,11 @@ class ChatAdministratorRights(TelegramObject):
manage direct messages of the channel and decline suggested posts; for channels only.
.. versionadded:: 22.4
can_manage_tags (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit the
tags of regular members; for groups and supergroups only. If omitted defaults to the
value of :attr:`can_pin_messages`.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
@@ -168,6 +183,7 @@ class ChatAdministratorRights(TelegramObject):
"can_invite_users",
"can_manage_chat",
"can_manage_direct_messages",
"can_manage_tags",
"can_manage_topics",
"can_manage_video_chats",
"can_pin_messages",
@@ -196,6 +212,7 @@ class ChatAdministratorRights(TelegramObject):
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
) -> None:
@@ -218,6 +235,7 @@ class ChatAdministratorRights(TelegramObject):
self.can_pin_messages: bool | None = can_pin_messages
self.can_manage_topics: bool | None = can_manage_topics
self.can_manage_direct_messages: bool | None = can_manage_direct_messages
self.can_manage_tags: bool | None = can_manage_tags
self._id_attrs = (
self.is_anonymous,
@@ -236,6 +254,7 @@ class ChatAdministratorRights(TelegramObject):
self.can_edit_stories,
self.can_delete_stories,
self.can_manage_direct_messages,
self.can_manage_tags,
)
self._freeze()
+43 -1
View File
@@ -257,6 +257,11 @@ class ChatMemberAdministrator(ChatMember):
manage direct messages of the channel and decline suggested posts; for channels only.
.. versionadded:: 22.4
can_manage_tags (:obj:`bool`, optional): :obj:`True`, if the administrator can edit the
tags of regular members; for groups and supergroups only. If omitted defaults to the
value of :attr:`can_pin_messages`.
.. versionadded:: NEXT.VERSION
Attributes:
status (:obj:`str`): The member's status in the chat,
@@ -321,6 +326,11 @@ class ChatMemberAdministrator(ChatMember):
manage direct messages of the channel and decline suggested posts; for channels only.
.. versionadded:: 22.4
can_manage_tags (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit the
tags of regular members; for groups and supergroups only. If omitted defaults to the
value of :attr:`can_pin_messages`.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
@@ -333,6 +343,7 @@ class ChatMemberAdministrator(ChatMember):
"can_invite_users",
"can_manage_chat",
"can_manage_direct_messages",
"can_manage_tags",
"can_manage_topics",
"can_manage_video_chats",
"can_pin_messages",
@@ -365,6 +376,7 @@ class ChatMemberAdministrator(ChatMember):
can_manage_topics: bool | None = None,
custom_title: str | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -389,6 +401,7 @@ class ChatMemberAdministrator(ChatMember):
self.can_manage_topics: bool | None = can_manage_topics
self.custom_title: str | None = custom_title
self.can_manage_direct_messages: bool | None = can_manage_direct_messages
self.can_manage_tags: bool | None = can_manage_tags
class ChatMemberMember(ChatMember):
@@ -404,6 +417,9 @@ class ChatMemberMember(ChatMember):
expire.
.. versionadded:: 21.5
tag (:obj:`str`, optional): Tag of the member.
.. versionadded:: NEXT.VERSION
Attributes:
status (:obj:`str`): The member's status in the chat,
@@ -413,21 +429,29 @@ class ChatMemberMember(ChatMember):
expire.
.. versionadded:: 21.5
tag (:obj:`str`): Optional. Tag of the member.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("until_date",)
__slots__ = (
"tag",
"until_date",
)
def __init__(
self,
user: User,
until_date: dtm.datetime | None = None,
tag: str | None = None,
*,
api_kwargs: JSONDict | None = None,
):
super().__init__(status=ChatMember.MEMBER, user=user, api_kwargs=api_kwargs)
with self._unfrozen():
self.until_date: dtm.datetime | None = until_date
self.tag: str | None = tag
class ChatMemberRestricted(ChatMember):
@@ -490,6 +514,12 @@ class ChatMemberRestricted(ChatMember):
notes.
.. versionadded:: 20.1
can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag.
.. versionadded:: NEXT.VERSION
tag (:obj:`str`, optional): Tag of the member.
.. versionadded:: NEXT.VERSION
Attributes:
status (:obj:`str`): The member's status in the chat,
@@ -540,12 +570,19 @@ class ChatMemberRestricted(ChatMember):
notes.
.. versionadded:: 20.1
can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag.
.. versionadded:: NEXT.VERSION
tag (:obj:`str`): Optional. Tag of the member.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"can_add_web_page_previews",
"can_change_info",
"can_edit_tag",
"can_invite_users",
"can_manage_topics",
"can_pin_messages",
@@ -559,6 +596,7 @@ class ChatMemberRestricted(ChatMember):
"can_send_videos",
"can_send_voice_notes",
"is_member",
"tag",
"until_date",
)
@@ -581,6 +619,8 @@ class ChatMemberRestricted(ChatMember):
can_send_videos: bool,
can_send_video_notes: bool,
can_send_voice_notes: bool,
can_edit_tag: bool,
tag: str | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -602,6 +642,8 @@ class ChatMemberRestricted(ChatMember):
self.can_send_videos: bool = can_send_videos
self.can_send_video_notes: bool = can_send_video_notes
self.can_send_voice_notes: bool = can_send_voice_notes
self.can_edit_tag: bool = can_edit_tag
self.tag: str | None = tag
class ChatMemberLeft(ChatMember):
+19 -4
View File
@@ -35,8 +35,8 @@ class ChatPermissions(TelegramObject):
:attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`,
:attr:`can_change_info`, :attr:`can_invite_users`, :attr:`can_pin_messages`,
:attr:`can_send_audios`, :attr:`can_send_documents`, :attr:`can_send_photos`,
:attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`, and
:attr:`can_manage_topics` are equal.
:attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`,
:attr:`can_manage_topics` and :attr:`can_edit_tag` are equal.
.. versionchanged:: 20.0
:attr:`can_manage_topics` is considered as well when comparing objects of
@@ -47,6 +47,9 @@ class ChatPermissions(TelegramObject):
:attr:`can_send_videos`, :attr:`can_send_video_notes` and :attr:`can_send_voice_notes`
are considered as well when comparing objects of this type in terms of equality.
* Removed deprecated argument and attribute ``can_send_media_messages``.
.. versionchanged:: NEXT.VERSION
:attr:`can_edit_tag` is considered as well when comparing objects of
this type in terms of equality.
Note:
@@ -93,6 +96,10 @@ class ChatPermissions(TelegramObject):
notes.
.. versionadded:: 20.1
can_edit_tag (:obj:`bool`, optional): :obj:`True`, if the user is allowed to edit their own
tag.
.. versionadded:: NEXT.VERSION
Attributes:
can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text
@@ -134,12 +141,17 @@ class ChatPermissions(TelegramObject):
notes.
.. versionadded:: 20.1
can_edit_tag (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to edit their own
tag.
.. versionadded:: NEXT.VERSION
"""
__slots__ = (
"can_add_web_page_previews",
"can_change_info",
"can_edit_tag",
"can_invite_users",
"can_manage_topics",
"can_pin_messages",
@@ -170,6 +182,7 @@ class ChatPermissions(TelegramObject):
can_send_videos: bool | None = None,
can_send_video_notes: bool | None = None,
can_send_voice_notes: bool | None = None,
can_edit_tag: bool | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -189,6 +202,7 @@ class ChatPermissions(TelegramObject):
self.can_send_videos: bool | None = can_send_videos
self.can_send_video_notes: bool | None = can_send_video_notes
self.can_send_voice_notes: bool | None = can_send_voice_notes
self.can_edit_tag: bool | None = can_edit_tag
self._id_attrs = (
self.can_send_messages,
@@ -205,6 +219,7 @@ class ChatPermissions(TelegramObject):
self.can_send_videos,
self.can_send_video_notes,
self.can_send_voice_notes,
self.can_edit_tag,
)
self._freeze()
@@ -219,7 +234,7 @@ class ChatPermissions(TelegramObject):
.. versionadded:: 20.0
"""
return cls(*(14 * (True,)))
return cls(*(True,) * len(cls.__slots__))
@classmethod
def no_permissions(cls) -> "ChatPermissions":
@@ -229,7 +244,7 @@ class ChatPermissions(TelegramObject):
.. versionadded:: 20.0
"""
return cls(*(14 * (False,)))
return cls(*(False,) * len(cls.__slots__))
@classmethod
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatPermissions":
+36 -5
View File
@@ -74,7 +74,7 @@ from telegram._telegramobject import TelegramObject
from telegram._uniquegift import UniqueGiftInfo
from telegram._user import User
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp, to_timestamp
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
from telegram._utils.entities import parse_message_entities, parse_message_entity
from telegram._utils.strings import TextEncoding
@@ -330,8 +330,8 @@ class Message(MaybeInaccessibleMessage):
or as a scheduled message.
.. versionadded:: 21.1
media_group_id (:obj:`str`, optional): The unique identifier of a media message group this
message belongs to.
media_group_id (:obj:`str`, optional): The unique identifier inside this chat of a media
message group this message belongs to.
text (:obj:`str`, optional): For text messages, the actual UTF-8 text of the message,
0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters.
entities (Sequence[:class:`telegram.MessageEntity`], optional): For text messages, special
@@ -684,6 +684,10 @@ class Message(MaybeInaccessibleMessage):
chat_owner_changed (:class:`telegram.ChatOwnerChanged`, optional): Service message: chat
owner has changed.
.. versionadded:: NEXT.VERSION
sender_tag (:obj:`str`, optional): Tag or custom title of the sender of the message; for
supergroups only
.. versionadded:: NEXT.VERSION
Attributes:
@@ -726,8 +730,8 @@ class Message(MaybeInaccessibleMessage):
or as a scheduled message.
.. versionadded:: 21.1
media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this
message belongs to.
media_group_id (:obj:`str`): Optional. The unique identifier inside this chat of a media
message group this message belongs to.
text (:obj:`str`): Optional. For text messages, the actual UTF-8 text of the message,
0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters.
entities (tuple[:class:`telegram.MessageEntity`]): Optional. For text messages, special
@@ -1096,6 +1100,10 @@ class Message(MaybeInaccessibleMessage):
chat_owner_changed (:class:`telegram.ChatOwnerChanged`): Optional. Service message: chat
owner has changed.
.. versionadded:: NEXT.VERSION
sender_tag (:obj:`str`): Optional. Tag or custom title of the sender of the message; for
supergroups only
.. versionadded:: NEXT.VERSION
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
@@ -1193,6 +1201,7 @@ class Message(MaybeInaccessibleMessage):
"sender_boost_count",
"sender_business_bot",
"sender_chat",
"sender_tag",
"show_caption_above_media",
"sticker",
"story",
@@ -1327,6 +1336,7 @@ class Message(MaybeInaccessibleMessage):
gift_upgrade_sent: GiftInfo | None = None,
chat_owner_changed: ChatOwnerChanged | None = None,
chat_owner_left: ChatOwnerLeft | None = None,
sender_tag: str | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -1456,6 +1466,7 @@ class Message(MaybeInaccessibleMessage):
self.gift_upgrade_sent: GiftInfo | None = gift_upgrade_sent
self.chat_owner_changed: ChatOwnerChanged | None = chat_owner_changed
self.chat_owner_left: ChatOwnerLeft | None = chat_owner_left
self.sender_tag: str | None = sender_tag
self._effective_attachment = DEFAULT_NONE
@@ -5304,6 +5315,17 @@ class Message(MaybeInaccessibleMessage):
insert = f'<span class="tg-spoiler">{escaped_text}</span>'
elif entity.type == MessageEntity.CUSTOM_EMOJI:
insert = f'<tg-emoji emoji-id="{entity.custom_emoji_id}">{escaped_text}</tg-emoji>'
elif entity.type == MessageEntity.DATE_TIME:
if entity.date_time_format:
insert = (
f'<tg-time unix="{to_timestamp(entity.unix_time)}" '
f'format="{entity.date_time_format}">{escaped_text}</tg-time>'
)
else:
insert = (
f'<tg-time unix="{to_timestamp(entity.unix_time)}">'
f"{escaped_text}</tg-time>"
)
else:
insert = escaped_text
@@ -5443,6 +5465,7 @@ class Message(MaybeInaccessibleMessage):
MessageEntity.SPOILER,
MessageEntity.STRIKETHROUGH,
MessageEntity.UNDERLINE,
MessageEntity.DATE_TIME,
):
if any(entity.type == entity_type for entity in entities):
name = entity_type.name.title().replace("_", " ") # type:ignore[attr-defined]
@@ -5535,6 +5558,14 @@ class Message(MaybeInaccessibleMessage):
entity_type=MessageEntity.CUSTOM_EMOJI,
)
insert = f"![{escaped_text}](tg://emoji?id={custom_emoji_id})"
elif entity.type == MessageEntity.DATE_TIME:
if entity.date_time_format:
insert = (
f"![{escaped_text}](tg://time?unix={to_timestamp(entity.unix_time)}"
f"&format={entity.date_time_format})"
)
else:
insert = f"![{escaped_text}](tg://time?unix={to_timestamp(entity.unix_time)})"
else:
insert = escaped_text
+53 -2
View File
@@ -19,6 +19,7 @@
"""This module contains an object that represents a Telegram MessageEntity."""
import copy
import datetime as dtm
import itertools
from collections.abc import Sequence
from typing import TYPE_CHECKING, Final
@@ -28,6 +29,7 @@ from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import de_json_optional
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.strings import TextEncoding
from telegram._utils.types import JSONDict
@@ -55,13 +57,17 @@ class MessageEntity(TelegramObject):
(underlined text), :attr:`STRIKETHROUGH`, :attr:`SPOILER` (spoiler message),
:attr:`BLOCKQUOTE` (block quotation), :attr:`CODE` (monowidth string), :attr:`PRE`
(monowidth block), :attr:`TEXT_LINK` (for clickable text URLs), :attr:`TEXT_MENTION`
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers).
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers)
or :attr:`DATE_TIME`(for formatted date and time).
.. versionadded:: 20.0
Added inline custom emoji
.. versionadded:: 20.8
Added block quotation
.. versionadded:: NEXT.VERSION
Added date_time
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
length (:obj:`int`): Length of the entity in UTF-16 code units.
url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after
@@ -75,6 +81,17 @@ class MessageEntity(TelegramObject):
information about the sticker.
.. versionadded:: 20.0
date_time_format (:obj:`str`, optional): For :attr`DATE_TIME` only, the string that defines
the formatting of the date and time. See `date-time entity formatting
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details and
:tg-const:`telegram.constants.MessageEntityDateTimeFormats` for all possible formats.
.. versionadded:: NEXT.VERSION
unix_time (:class:`datetime.datetime`, optional): For :attr:`DATE_TIME` only, the time
associated with the entity.
|datetime_localization|
.. versionadded:: NEXT.VERSION
Attributes:
type (:obj:`str`): Type of the entity. Can be :attr:`MENTION` (``@username``),
:attr:`HASHTAG` (``#hashtag`` or ``#hashtag@chatusername``), :attr:`CASHTAG` (``$USD``
@@ -105,10 +122,31 @@ class MessageEntity(TelegramObject):
information about the sticker.
.. versionadded:: 20.0
date_time_format (:obj:`str`): Optional. For :attr`DATE_TIME` only, the string that defines
the formatting of the date and time. See `date-time entity formatting
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details and
:tg-const:`telegram.constants.MessageEntityDateTimeFormats` for all possible formats.
.. versionadded:: NEXT.VERSION
unix_time (:class:`datetime.datetime`): Optional. For :attr:`DATE_TIME` only, the time
associated with the entity.
|datetime_localization|
.. versionadded:: NEXT.VERSION
"""
__slots__ = ("custom_emoji_id", "language", "length", "offset", "type", "url", "user")
__slots__ = (
"custom_emoji_id",
"date_time_format",
"language",
"length",
"offset",
"type",
"unix_time",
"url",
"user",
)
def __init__(
self,
@@ -119,6 +157,8 @@ class MessageEntity(TelegramObject):
user: User | None = None,
language: str | None = None,
custom_emoji_id: str | None = None,
date_time_format: str | None = None,
unix_time: dtm.datetime | None = None,
*,
api_kwargs: JSONDict | None = None,
):
@@ -132,6 +172,8 @@ class MessageEntity(TelegramObject):
self.user: User | None = user
self.language: str | None = language
self.custom_emoji_id: str | None = custom_emoji_id
self.date_time_format: str | None = date_time_format
self.unix_time: dtm.datetime | None = unix_time
self._id_attrs = (self.type, self.offset, self.length)
@@ -144,6 +186,10 @@ class MessageEntity(TelegramObject):
data["user"] = de_json_optional(data.get("user"), User, bot)
# Get the local timezone from the bot if it has defaults
loc_tzinfo = extract_tzinfo_from_defaults(bot)
data["unix_time"] = from_timestamp(data.get("unix_time"), tzinfo=loc_tzinfo)
return super().de_json(data=data, bot=bot)
@staticmethod
@@ -376,6 +422,11 @@ class MessageEntity(TelegramObject):
.. versionadded:: 20.0
"""
DATE_TIME: Final[str] = constants.MessageEntityType.DATE_TIME
""":const:`telegram.constants.MessageEntityType.DATE_TIME`
.. versionadded:: NEXT.VERSION
"""
EMAIL: Final[str] = constants.MessageEntityType.EMAIL
""":const:`telegram.constants.MessageEntityType.EMAIL`"""
EXPANDABLE_BLOCKQUOTE: Final[str] = constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE
+35
View File
@@ -2677,3 +2677,38 @@ class User(TelegramObject):
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
async def set_chat_member_tag(
self,
chat_id: int | str,
tag: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
) -> bool:
"""
Shortcut for::
await bot.set_chat_member_tag(user_id=update.effective_user.id, *args, **kwargs)
For the documentation of the arguments, please see
:meth:`telegram.Bot.set_chat_member_tag`.
.. versionadded:: NEXT.VERSION
Returns:
:obj:`bool`: On success, :obj:`True` is returned.
"""
return await self.get_bot().set_chat_member_tag(
user_id=self.id,
chat_id=chat_id,
tag=tag,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
pool_timeout=pool_timeout,
api_kwargs=api_kwargs,
)
+80 -1
View File
@@ -91,6 +91,7 @@ __all__ = [
"MediaGroupLimit",
"MenuButtonType",
"MessageAttachmentType",
"MessageEntityDateTimeFormats",
"MessageEntityType",
"MessageLimit",
"MessageOriginType",
@@ -121,6 +122,7 @@ __all__ = [
"SuggestedPost",
"SuggestedPostInfoState",
"SuggestedPostRefunded",
"TagLimit",
"TransactionPartnerType",
"TransactionPartnerUser",
"UniqueGiftInfoOrigin",
@@ -179,7 +181,7 @@ class _AccentColor(NamedTuple):
#: :data:`telegram.__bot_api_version_info__`.
#:
#: .. versionadded:: 20.0
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=4)
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=5)
#: :obj:`str`: Telegram Bot API
#: version supported by this version of `python-telegram-bot`. Also available as
#: :data:`telegram.__bot_api_version__`.
@@ -2017,6 +2019,11 @@ class MessageEntityType(StringEnum):
.. versionadded:: 20.0
"""
DATE_TIME = "date_time"
""":obj:`str`: Message entities representing formatted date and time.
.. versionadded:: NEXT.VERSION
"""
EMAIL = "email"
""":obj:`str`: Message entities representing a email."""
EXPANDABLE_BLOCKQUOTE = "expandable_blockquote"
@@ -2048,6 +2055,63 @@ class MessageEntityType(StringEnum):
""":obj:`str`: Message entities representing a url."""
class MessageEntityDateTimeFormats(StringEnum):
"""This enum contains all possible formats for :attr:`telegram.MessageEntity.date_time_format`.
Please read `date-time entity formatting
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details. The enum
members of this enumeration are instances of :class:`str` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
RELATIVE = "r"
""":obj:`str`: Displays the time relative to the current time."""
LOCALIZED_WEEKDAY = "w"
""":obj:`str`: Displays the day of the week in the user's localized language."""
SHORT_DATE = "d"
""":obj:`str`: Displays the date in short form (e.g., "17.03.22")."""
LONG_DATE = "D"
""":obj:`str`: Displays the date in long form (e.g., "March 17, 2022")."""
SHORT_TIME = "t"
""":obj:`str`: Displays the time in short form (e.g., "22:45")."""
LONG_TIME = "T"
""":obj:`str`: Displays the time in long form (e.g., "22:45:00")."""
LOCALIZED_WEEKDAY_SHORT_DATE = "wd"
""":obj:`str`: Displays the day of the week in the user's localized language and the date in
short form."""
LOCALIZED_WEEKDAY_LONG_DATE = "wD"
""":obj:`str`: Displays the day of the week in the user's localized language and the date in
long form."""
LOCALIZED_WEEKDAY_SHORT_TIME = "wt"
""":obj:`str`: Displays the day of the week in the user's localized language and the time in
short form."""
LOCALIZED_WEEKDAY_LONG_TIME = "wT"
""":obj:`str`: Displays the day of the week in the user's localized language and the time in
long form."""
LOCALIZED_WEEKDAY_SHORT_DATE_SHORT_TIME = "wdt"
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
short form and the time in short form."""
LOCALIZED_WEEKDAY_SHORT_DATE_LONG_TIME = "wdT"
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
short form and the time in long form."""
LOCALIZED_WEEKDAY_LONG_DATE_SHORT_TIME = "wDt"
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
long form and the time in short form."""
LOCALIZED_WEEKDAY_LONG_DATE_LONG_TIME = "wDT"
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
long form and the time in long form."""
SHORT_DATE_SHORT_TIME = "dt"
""":obj:`str`: Displays the date in short form and the time in short form."""
SHORT_DATE_LONG_TIME = "dT"
""":obj:`str`: Displays the date in short form and the time in long form."""
LONG_DATE_SHORT_TIME = "Dt"
""":obj:`str`: Displays the date in long form and the time in short form."""
LONG_DATE_LONG_TIME = "DT"
""":obj:`str`: Displays the date in long form and the time in long form."""
class MessageLimit(IntEnum):
"""This enum contains limitations for :class:`telegram.Message`/
:class:`telegram.InputTextMessageContent`/
@@ -3964,3 +4028,18 @@ class VerifyLimit(IntEnum):
:paramref:`~telegram.Bot.verify_chat.custom_description` or
:paramref:`~telegram.Bot.verify_user.custom_description` parameter.
"""
class TagLimit(IntEnum):
"""This enum contains limitations for :meth:`~telegram.Bot.set_chat_member_tag`.
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
.. versionadded:: NEXT.VERSION
"""
__slots__ = ()
MAX_TAG_LENGTH = 16
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
:paramref:`~telegram.Bot.set_chat_member_tag.tag` parameter.
"""
+27
View File
@@ -2367,6 +2367,7 @@ class ExtBot(Bot, Generic[RLARGS]):
can_edit_stories: bool | None = None,
can_delete_stories: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
@@ -2394,6 +2395,7 @@ class ExtBot(Bot, Generic[RLARGS]):
can_edit_stories=can_edit_stories,
can_delete_stories=can_delete_stories,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
read_timeout=read_timeout,
write_timeout=write_timeout,
connect_timeout=connect_timeout,
@@ -5485,6 +5487,30 @@ class ExtBot(Bot, Generic[RLARGS]):
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
)
async def set_chat_member_tag(
self,
chat_id: int | str,
user_id: int,
tag: str | None = None,
*,
read_timeout: ODVInput[float] = DEFAULT_NONE,
write_timeout: ODVInput[float] = DEFAULT_NONE,
connect_timeout: ODVInput[float] = DEFAULT_NONE,
pool_timeout: ODVInput[float] = DEFAULT_NONE,
api_kwargs: JSONDict | None = None,
rate_limit_args: RLARGS | None = None,
) -> bool:
return await super().set_chat_member_tag(
chat_id=chat_id,
user_id=user_id,
tag=tag,
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),
)
# updated camelCase aliases
getMe = get_me
sendMessage = send_message
@@ -5650,3 +5676,4 @@ class ExtBot(Bot, Generic[RLARGS]):
setMyProfilePhoto = set_my_profile_photo
removeMyProfilePhoto = remove_my_profile_photo
getUserProfileAudios = get_user_profile_audios
setChatMemberTag = set_chat_member_tag
+15
View File
@@ -2835,6 +2835,18 @@ class TestBotWithoutRequest:
limit="limit",
)
# TODO if we have a group member id in every group we could test this
async def test_set_chat_member_tag(self, offline_bot, monkeypatch):
async def make_assertion(url, request_data: RequestData, *args, **kwargs):
assert request_data.parameters.get("chat_id") == 1234
assert request_data.parameters.get("user_id") == 5678
assert request_data.parameters.get("tag") == "This is a tag"
monkeypatch.setattr(offline_bot.request, "post", make_assertion)
await offline_bot.set_chat_member_tag(1234, 5678, "This is a tag")
class TestBotWithRequest:
"""
@@ -3808,6 +3820,7 @@ class TestBotWithRequest:
can_edit_stories=True,
can_delete_stories=True,
can_manage_direct_messages=True,
can_manage_tags=True,
)
# Test that we pass the correct params to TG
@@ -3832,6 +3845,7 @@ class TestBotWithRequest:
and data.get("can_edit_stories") == 14
and data.get("can_delete_stories") == 15
and data.get("can_manage_direct_messages") == 16
and data.get("can_manage_tags") == 17
)
monkeypatch.setattr(bot, "_post", make_assertion)
@@ -3854,6 +3868,7 @@ class TestBotWithRequest:
can_edit_stories=14,
can_delete_stories=15,
can_manage_direct_messages=16,
can_manage_tags=17,
)
async def test_export_chat_invite_link(self, bot, channel_id):
+19
View File
@@ -1548,6 +1548,25 @@ class TestChatWithoutRequest(ChatTestBase):
monkeypatch.setattr(chat.get_bot(), "get_chat_gifts", make_assertion)
assert await chat.get_gifts()
async def test_instance_method_set_chat_member_tag(self, monkeypatch, chat):
async def make_assertion(*_, **kwargs):
return (
kwargs["chat_id"] == chat.id
and kwargs["user_id"] == "user_id"
and kwargs["tag"] == "tag"
)
assert check_shortcut_signature(
Chat.set_chat_member_tag, Bot.set_chat_member_tag, ["chat_id"], []
)
assert await check_shortcut_call(
chat.set_chat_member_tag, chat.get_bot(), "set_chat_member_tag"
)
assert await check_defaults_handling(chat.set_chat_member_tag, chat.get_bot())
monkeypatch.setattr(chat.get_bot(), "set_chat_member_tag", make_assertion)
assert await chat.set_chat_member_tag(user_id="user_id", tag="tag")
def test_mention_html(self):
chat = Chat(id=1, type="foo")
with pytest.raises(TypeError, match="Can not create a mention to a private group chat"):
+5
View File
@@ -41,6 +41,7 @@ def chat_admin_rights():
can_edit_stories=True,
can_delete_stories=True,
can_manage_direct_messages=True,
can_manage_tags=True,
)
@@ -69,6 +70,7 @@ class TestChatAdministratorRightsWithoutRequest:
"can_edit_stories": True,
"can_delete_stories": True,
"can_manage_direct_messages": True,
"can_manage_tags": True,
}
chat_administrator_rights_de = ChatAdministratorRights.de_json(json_dict, offline_bot)
assert chat_administrator_rights_de.api_kwargs == {}
@@ -96,6 +98,7 @@ class TestChatAdministratorRightsWithoutRequest:
assert admin_rights_dict["can_edit_stories"] == car.can_edit_stories
assert admin_rights_dict["can_delete_stories"] == car.can_delete_stories
assert admin_rights_dict["can_manage_direct_messages"] == car.can_manage_direct_messages
assert admin_rights_dict["can_manage_tags"] == car.can_manage_tags
def test_equality(self):
a = ChatAdministratorRights(
@@ -147,6 +150,7 @@ class TestChatAdministratorRightsWithoutRequest:
True,
True,
True,
True,
)
t = ChatAdministratorRights.all_rights()
# if the dirs are the same, the attributes will all be there
@@ -173,6 +177,7 @@ class TestChatAdministratorRightsWithoutRequest:
False,
False,
False,
False,
)
t = ChatAdministratorRights.no_rights()
# if the dirs are the same, the attributes will all be there
+19
View File
@@ -76,6 +76,9 @@ class ChatMemberTestBase:
can_send_messages = True
is_member = True
can_manage_direct_messages = True
can_manage_tags = True
can_edit_tag = True
tag = "test_tag"
class TestChatMemberWithoutRequest(ChatMemberTestBase):
@@ -172,6 +175,7 @@ def chat_member_administrator():
TestChatMemberAdministratorWithoutRequest.custom_title,
TestChatMemberAdministratorWithoutRequest.is_anonymous,
TestChatMemberAdministratorWithoutRequest.can_manage_direct_messages,
TestChatMemberAdministratorWithoutRequest.can_manage_tags,
)
@@ -205,6 +209,7 @@ class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
"custom_title": self.custom_title,
"is_anonymous": self.is_anonymous,
"can_manage_direct_messages": self.can_manage_direct_messages,
"can_manage_tags": self.can_manage_tags,
}
chat_member = ChatMemberAdministrator.de_json(data, offline_bot)
@@ -230,6 +235,7 @@ class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
assert chat_member.custom_title == self.custom_title
assert chat_member.is_anonymous == self.is_anonymous
assert chat_member.can_manage_direct_messages == self.can_manage_direct_messages
assert chat_member.can_manage_tags == self.can_manage_tags
def test_to_dict(self, chat_member_administrator):
assert chat_member_administrator.to_dict() == {
@@ -253,6 +259,7 @@ class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
"custom_title": chat_member_administrator.custom_title,
"is_anonymous": chat_member_administrator.is_anonymous,
"can_manage_direct_messages": chat_member_administrator.can_manage_direct_messages,
"can_manage_tags": chat_member_administrator.can_manage_tags,
}
def test_equality(self, chat_member_administrator):
@@ -272,6 +279,7 @@ class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
True,
True,
True,
True,
)
c = ChatMemberAdministrator(
User(1, "test_user", is_bot=False),
@@ -288,6 +296,7 @@ class TestChatMemberAdministratorWithoutRequest(ChatMemberTestBase):
False,
False,
False,
False,
)
d = Dice(5, "test")
@@ -566,6 +575,8 @@ def chat_member_restricted():
can_send_voice_notes=TestChatMemberRestrictedWithoutRequest.can_send_voice_notes,
is_member=TestChatMemberRestrictedWithoutRequest.is_member,
until_date=TestChatMemberRestrictedWithoutRequest.until_date,
can_edit_tag=TestChatMemberRestrictedWithoutRequest.can_edit_tag,
tag=TestChatMemberRestrictedWithoutRequest.tag,
)
@@ -597,6 +608,8 @@ class TestChatMemberRestrictedWithoutRequest(ChatMemberTestBase):
"can_send_voice_notes": self.can_send_voice_notes,
"is_member": self.is_member,
"until_date": to_timestamp(self.until_date),
"can_edit_tag": self.can_edit_tag,
"tag": self.tag,
# legacy argument
"can_send_media_messages": False,
}
@@ -622,6 +635,8 @@ class TestChatMemberRestrictedWithoutRequest(ChatMemberTestBase):
assert chat_member.can_send_voice_notes == self.can_send_voice_notes
assert chat_member.is_member == self.is_member
assert chat_member.until_date == self.until_date
assert chat_member.can_edit_tag == self.can_edit_tag
assert chat_member.tag == self.tag
def test_de_json_localization(self, tz_bot, offline_bot, raw_bot, chat_member_restricted):
json_dict = chat_member_restricted.to_dict()
@@ -660,6 +675,8 @@ class TestChatMemberRestrictedWithoutRequest(ChatMemberTestBase):
"can_send_voice_notes": chat_member_restricted.can_send_voice_notes,
"is_member": chat_member_restricted.is_member,
"until_date": to_timestamp(chat_member_restricted.until_date),
"can_edit_tag": chat_member_restricted.can_edit_tag,
"tag": chat_member_restricted.tag,
}
def test_equality(self, chat_member_restricted):
@@ -683,6 +700,8 @@ class TestChatMemberRestrictedWithoutRequest(ChatMemberTestBase):
False,
False,
False,
False,
"tag",
)
d = Dice(5, "test")
+7
View File
@@ -40,6 +40,7 @@ def chat_permissions():
can_send_videos=True,
can_send_video_notes=True,
can_send_voice_notes=True,
can_edit_tag=True,
)
@@ -58,6 +59,7 @@ class ChatPermissionsTestBase:
can_send_videos = True
can_send_video_notes = False
can_send_voice_notes = None
can_edit_tag = None
class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
@@ -83,6 +85,7 @@ class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
"can_send_videos": self.can_send_videos,
"can_send_video_notes": self.can_send_video_notes,
"can_send_voice_notes": self.can_send_voice_notes,
"can_edit_tag": self.can_edit_tag,
}
permissions = ChatPermissions.de_json(json_dict, offline_bot)
assert permissions.api_kwargs == {"can_send_media_messages": "can_send_media_messages"}
@@ -101,6 +104,7 @@ class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
assert permissions.can_send_videos == self.can_send_videos
assert permissions.can_send_video_notes == self.can_send_video_notes
assert permissions.can_send_voice_notes == self.can_send_voice_notes
assert permissions.can_edit_tag == self.can_edit_tag
def test_to_dict(self, chat_permissions):
permissions_dict = chat_permissions.to_dict()
@@ -125,6 +129,7 @@ class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
assert permissions_dict["can_send_videos"] == chat_permissions.can_send_videos
assert permissions_dict["can_send_video_notes"] == chat_permissions.can_send_video_notes
assert permissions_dict["can_send_voice_notes"] == chat_permissions.can_send_voice_notes
assert permissions_dict["can_edit_tag"] == chat_permissions.can_edit_tag
def test_equality(self):
a = ChatPermissions(
@@ -153,6 +158,7 @@ class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
can_send_videos=True,
can_send_video_notes=True,
can_send_voice_notes=True,
can_edit_tag=True,
)
f = ChatPermissions(
can_send_messages=True,
@@ -164,6 +170,7 @@ class TestChatPermissionsWithoutRequest(ChatPermissionsTestBase):
can_send_videos=True,
can_send_video_notes=True,
can_send_voice_notes=True,
can_edit_tag=True,
)
assert a == b
+1
View File
@@ -198,6 +198,7 @@ class TestConstantsWithoutRequest:
"reply_markup",
"reply_to_message",
"sender_chat",
"sender_tag",
"is_accessible",
"quote",
"external_reply",
+38 -6
View File
@@ -19,6 +19,7 @@
import datetime as dtm
from copy import copy, deepcopy
from zoneinfo import ZoneInfo
import pytest
@@ -434,6 +435,7 @@ def message(bot):
},
{"chat_owner_changed": ChatOwnerChanged(new_owner=User(4, "Snow", False))},
{"chat_owner_left": ChatOwnerLeft(new_owner=User(5, "Crash", False))},
{"sender_tag": "This is a tag"},
],
ids=[
"reply",
@@ -527,6 +529,7 @@ def message(bot):
"gift_upgrade_sent",
"chat_owner_changed",
"chat_owner_left",
"sender_tag",
],
)
def message_params(bot, request):
@@ -585,11 +588,20 @@ class MessageTestBase:
{"length": 34, "offset": 154, "type": "blockquote"},
{"length": 6, "offset": 181, "type": "bold"},
{"length": 33, "offset": 190, "type": "expandable_blockquote"},
{"length": 4, "offset": 224, "type": "date_time", "unix_time": dtm.datetime(2000, 7, 28)},
{
"length": 14,
"offset": 229,
"type": "date_time",
"unix_time": dtm.datetime(2000, 7, 28, tzinfo=ZoneInfo("Europe/Berlin")),
"date_time_format": "r",
},
]
test_text_v2 = (
r"Test for <bold, ita_lic, \`code, links, text-mention and `\pre. "
"http://google.com and bold nested in strk>trgh nested in italic. Python pre. Spoiled. "
"👍.\nMultiline\nblock quote\nwith nested.\n\nMultiline\nexpandable\nblock quote."
"👍.\nMultiline\nblock quote\nwith nested.\n\nMultiline\nexpandable\nblock quote.\ntime"
"\ntime_formatted\n"
)
test_message = Message(
message_id=1,
@@ -957,7 +969,9 @@ class TestMessageWithoutRequest(MessageTestBase):
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>\n"
'<tg-time unix="964742400">time</tg-time>\n'
'<tg-time unix="964735200" format="r">time_formatted</tg-time>\n'
)
text_html = self.test_message_v2.text_html
assert text_html == test_html_string
@@ -979,7 +993,9 @@ class TestMessageWithoutRequest(MessageTestBase):
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>\n"
'<tg-time unix="964742400">time</tg-time>\n'
'<tg-time unix="964735200" format="r">time_formatted</tg-time>\n'
)
text_html = self.test_message_v2.text_html_urled
assert text_html == test_html_string
@@ -1007,6 +1023,8 @@ class TestMessageWithoutRequest(MessageTestBase):
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
"\n![time](tg://time?unix=964742400)\n"
"![time\\_formatted](tg://time?unix=964735200&format=r)\n"
)
text_markdown = self.test_message_v2.text_markdown_v2
assert text_markdown == test_md_string
@@ -1066,6 +1084,8 @@ class TestMessageWithoutRequest(MessageTestBase):
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
"\n![time](tg://time?unix=964742400)\n"
"![time\\_formatted](tg://time?unix=964735200&format=r)\n"
)
text_markdown = self.test_message_v2.text_markdown_v2_urled
assert text_markdown == test_md_string
@@ -1183,7 +1203,9 @@ class TestMessageWithoutRequest(MessageTestBase):
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>\n"
'<tg-time unix="964742400">time</tg-time>\n'
'<tg-time unix="964735200" format="r">time_formatted</tg-time>\n'
)
caption_html = self.test_message_v2.caption_html
assert caption_html == test_html_string
@@ -1205,7 +1227,9 @@ class TestMessageWithoutRequest(MessageTestBase):
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>\n"
'<tg-time unix="964742400">time</tg-time>\n'
'<tg-time unix="964735200" format="r">time_formatted</tg-time>\n'
)
caption_html = self.test_message_v2.caption_html_urled
assert caption_html == test_html_string
@@ -1233,6 +1257,8 @@ class TestMessageWithoutRequest(MessageTestBase):
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
"\n![time](tg://time?unix=964742400)\n"
"![time\\_formatted](tg://time?unix=964735200&format=r)\n"
)
caption_markdown = self.test_message_v2.caption_markdown_v2
assert caption_markdown == test_md_string
@@ -1267,6 +1293,8 @@ class TestMessageWithoutRequest(MessageTestBase):
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
"\n![time](tg://time?unix=964742400)\n"
"![time\\_formatted](tg://time?unix=964735200&format=r)\n"
)
caption_markdown = self.test_message_v2.caption_markdown_v2_urled
assert caption_markdown == test_md_string
@@ -1746,6 +1774,8 @@ class TestMessageWithoutRequest(MessageTestBase):
"\n\n>Multiline\n"
">expandable\n"
r">block quote\.||"
"\n![time](tg://time?unix=964742400)\n"
"![time\\_formatted](tg://time?unix=964735200&format=r)\n"
)
async def make_assertion(*_, **kwargs):
@@ -1803,7 +1833,9 @@ class TestMessageWithoutRequest(MessageTestBase):
'<span class="tg-spoiler">Spoiled</span>. '
'<tg-emoji emoji-id="1">👍</tg-emoji>.\n'
"<blockquote>Multiline\nblock quote\nwith <b>nested</b>.</blockquote>\n\n"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>"
"<blockquote expandable>Multiline\nexpandable\nblock quote.</blockquote>\n"
'<tg-time unix="964742400">time</tg-time>\n'
'<tg-time unix="964735200" format="r">time_formatted</tg-time>\n'
)
async def make_assertion(*_, **kwargs):
+26 -1
View File
@@ -16,11 +16,13 @@
#
# You should have received a copy of the GNU Lesser Public License
# along with this program. If not, see [http://www.gnu.org/licenses/].
import datetime as dtm
import random
import pytest
from telegram import MessageEntity, User
from telegram._utils.datetime import UTC, to_timestamp
from telegram.constants import MessageEntityType
from tests.auxil.slots import mro_slots
@@ -37,7 +39,25 @@ def message_entity(request):
language = None
if type_ == MessageEntity.PRE:
language = "python"
return MessageEntity(type_, 1, 3, url=url, user=user, language=language)
custom_emoji_id = None
if type_ == MessageEntity.CUSTOM_EMOJI:
custom_emoji_id = "emoji_id"
date_time_format = None
unix_time = None
if type_ == MessageEntity.DATE_TIME:
date_time_format = "wDT"
unix_time = dtm.datetime.now(tz=UTC)
return MessageEntity(
type_,
1,
3,
url=url,
user=user,
language=language,
custom_emoji_id=custom_emoji_id,
date_time_format=date_time_format,
unix_time=unix_time,
)
class MessageEntityTestBase:
@@ -76,6 +96,11 @@ class TestMessageEntityWithoutRequest(MessageEntityTestBase):
assert entity_dict["user"] == message_entity.user.to_dict()
if message_entity.language:
assert entity_dict["language"] == message_entity.language
if message_entity.custom_emoji_id:
assert entity_dict["custom_emoji_id"] == message_entity.custom_emoji_id
if message_entity.date_time_format:
assert entity_dict["date_time_format"] == message_entity.date_time_format
assert entity_dict["unix_time"] == to_timestamp(message_entity.unix_time)
def test_enum_init(self):
entity = MessageEntity(type="foo", offset=0, length=1)
+19
View File
@@ -887,3 +887,22 @@ class TestUserWithoutRequest(UserTestBase):
monkeypatch.setattr(user.get_bot(), "get_user_gifts", make_assertion)
assert await user.get_gifts()
async def test_instance_method_set_chat_member_tag(self, monkeypatch, user):
async def make_assertion(*_, **kwargs):
return (
kwargs["user_id"] == user.id
and kwargs["chat_id"] == "chat_id"
and kwargs["tag"] == "tag"
)
assert check_shortcut_signature(
user.set_chat_member_tag, Bot.set_chat_member_tag, ["user_id"], []
)
assert await check_shortcut_call(
user.set_chat_member_tag, user.get_bot(), "set_chat_member_tag"
)
assert await check_defaults_handling(user.set_chat_member_tag, user.get_bot())
monkeypatch.setattr(user.get_bot(), "set_chat_member_tag", make_assertion)
assert await user.set_chat_member_tag(chat_id="chat_id", tag="tag")