From 74f0b2b4398d35adb65ca158c8ac1724879616f3 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:38:06 +0400 Subject: [PATCH 01/12] Added the field allows_users_to_create_topics to the class User. --- telebot/types.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 73dba2015..3e1971084 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -515,6 +515,9 @@ class User(JsonDeserializable, Dictionaryable, JsonSerializable): :param has_topics_enabled: Optional. True, if the bot has forum topic mode enabled in private chats. Returned only in getMe. :type has_topics_enabled: :obj:`bool` + :param allows_users_to_create_topics: Optional. True, if the bot allows users to create and delete topics in private chats. Returned only in getMe. + :type allows_users_to_create_topics: :obj:`bool` + :return: Instance of the class :rtype: :class:`telebot.types.User` """ @@ -528,7 +531,7 @@ def de_json(cls, json_string): def __init__(self, id, is_bot, first_name, last_name=None, username=None, language_code=None, can_join_groups=None, can_read_all_group_messages=None, supports_inline_queries=None, is_premium=None, added_to_attachment_menu=None, can_connect_to_business=None, - has_main_web_app=None, has_topics_enabled=None, **kwargs): + has_main_web_app=None, has_topics_enabled=None, allows_users_to_create_topics=None, **kwargs): self.id: int = id self.is_bot: bool = is_bot self.first_name: str = first_name @@ -543,6 +546,7 @@ def __init__(self, id, is_bot, first_name, last_name=None, username=None, langua self.can_connect_to_business: Optional[bool] = can_connect_to_business self.has_main_web_app: Optional[bool] = has_main_web_app self.has_topics_enabled: Optional[bool] = has_topics_enabled + self.allows_users_to_create_topics: Optional[bool] = allows_users_to_create_topics @property def full_name(self) -> str: @@ -570,7 +574,10 @@ def to_dict(self): 'is_premium': self.is_premium, 'added_to_attachment_menu': self.added_to_attachment_menu, 'can_connect_to_business': self.can_connect_to_business, - 'has_main_web_app': self.has_main_web_app} + 'has_main_web_app': self.has_main_web_app, + 'has_topics_enabled': self.has_topics_enabled, + 'allows_users_to_create_topics': self.allows_users_to_create_topics + } # noinspection PyShadowingBuiltins From 5898931491cfbc9b43874a8aac8340820287fa33 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:41:11 +0400 Subject: [PATCH 02/12] Added the field icon_custom_emoji_id to the classes KeyboardButton and InlineKeyboardButton, allowing bots to show a custom emoji on buttons if they are able to use custom emoji in the message. --- telebot/types.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 3e1971084..db552a80a 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2905,10 +2905,15 @@ class KeyboardButton(Dictionaryable, JsonSerializable): Telegram Documentation: https://core.telegram.org/bots/api#keyboardbutton - :param text: Text of the button. If none of the optional fields are used, it will be sent as a message when the button is - pressed + :param text: Text of the button. If none of the fields other than text, icon_custom_emoji_id, and style are used, + it will be sent as a message when the button is pressed :type text: :obj:`str` + :param icon_custom_emoji_id: Optional. Unique identifier of the custom emoji shown before the text of the button. + Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, group and supergroup chats + if the owner of the bot has a Telegram Premium subscription. + :type icon_custom_emoji_id: :obj:`str` + :param request_contact: Optional. If True, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only. :type request_contact: :obj:`bool` @@ -2942,7 +2947,8 @@ class KeyboardButton(Dictionaryable, JsonSerializable): def __init__(self, text: str, request_contact: Optional[bool]=None, request_location: Optional[bool]=None, request_poll: Optional[KeyboardButtonPollType]=None, web_app: Optional[WebAppInfo]=None, request_user: Optional[KeyboardButtonRequestUser]=None, - request_chat: Optional[KeyboardButtonRequestChat]=None, request_users: Optional[KeyboardButtonRequestUsers]=None): + request_chat: Optional[KeyboardButtonRequestChat]=None, request_users: Optional[KeyboardButtonRequestUsers]=None, + icon_custom_emoji_id: Optional[str]=None, **kwargs): self.text: str = text self.request_contact: Optional[bool] = request_contact self.request_location: Optional[bool] = request_location @@ -2950,6 +2956,7 @@ def __init__(self, text: str, request_contact: Optional[bool]=None, self.web_app: Optional[WebAppInfo] = web_app self.request_chat: Optional[KeyboardButtonRequestChat] = request_chat self.request_users: Optional[KeyboardButtonRequestUsers] = request_users + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id if request_user is not None: log_deprecation_warning('The parameter "request_user" is deprecated, use "request_users" instead') if self.request_users is None: @@ -2974,6 +2981,8 @@ def to_dict(self): json_dict['request_users'] = self.request_users.to_dict() if self.request_chat is not None: json_dict['request_chat'] = self.request_chat.to_dict() + if self.icon_custom_emoji_id is not None: + json_dict['icon_custom_emoji_id'] = self.icon_custom_emoji_id return json_dict @@ -3097,6 +3106,11 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable) :param text: Label text on the button :type text: :obj:`str` + :param icon_custom_emoji_id: Optional. Unique identifier of the custom emoji shown before the text of the button. + Can only be used by bots that purchased additional usernames on Fragment or in the messages directly sent by the bot to private, + group and supergroup chats if the owner of the bot has a Telegram Premium subscription. + :type icon_custom_emoji_id: :obj:`str` + :param url: Optional. HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id= can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings. :type url: :obj:`str` @@ -3162,7 +3176,7 @@ def de_json(cls, json_string): def __init__(self, text: str, url: Optional[str]=None, callback_data: Optional[str]=None, web_app: Optional[WebAppInfo]=None, switch_inline_query: Optional[str]=None, switch_inline_query_current_chat: Optional[str]=None, switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat]=None, callback_game=None, pay: Optional[bool]=None, - login_url: Optional[LoginUrl]=None, copy_text: Optional[CopyTextButton]=None, **kwargs): + login_url: Optional[LoginUrl]=None, copy_text: Optional[CopyTextButton]=None, icon_custom_emoji_id: Optional[str]=None, **kwargs): self.text: str = text self.url: Optional[str] = url self.callback_data: Optional[str] = callback_data @@ -3174,6 +3188,7 @@ def __init__(self, text: str, url: Optional[str]=None, callback_data: Optional[s self.pay: Optional[bool] = pay self.login_url: Optional[LoginUrl] = login_url self.copy_text: Optional[CopyTextButton] = copy_text + self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id def to_json(self): return json.dumps(self.to_dict()) @@ -3200,6 +3215,8 @@ def to_dict(self): json_dict['switch_inline_query_chosen_chat'] = self.switch_inline_query_chosen_chat.to_dict() if self.copy_text is not None: json_dict['copy_text'] = self.copy_text.to_dict() + if self.icon_custom_emoji_id is not None: + json_dict['icon_custom_emoji_id'] = self.icon_custom_emoji_id return json_dict From fb3405e769447c1cfed0237308b8059f4a5ff1ea Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:43:25 +0400 Subject: [PATCH 03/12] Added the field style to the classes KeyboardButton and InlineKeyboardButton, allowing bots to change the color of buttons. --- telebot/types.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index db552a80a..e82406b1e 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2914,6 +2914,9 @@ class KeyboardButton(Dictionaryable, JsonSerializable): if the owner of the bot has a Telegram Premium subscription. :type icon_custom_emoji_id: :obj:`str` + :param style: Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, then an app-specific style is used. + :type style: :obj:`str` + :param request_contact: Optional. If True, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only. :type request_contact: :obj:`bool` @@ -2948,7 +2951,7 @@ def __init__(self, text: str, request_contact: Optional[bool]=None, request_location: Optional[bool]=None, request_poll: Optional[KeyboardButtonPollType]=None, web_app: Optional[WebAppInfo]=None, request_user: Optional[KeyboardButtonRequestUser]=None, request_chat: Optional[KeyboardButtonRequestChat]=None, request_users: Optional[KeyboardButtonRequestUsers]=None, - icon_custom_emoji_id: Optional[str]=None, **kwargs): + icon_custom_emoji_id: Optional[str]=None, style: Optional[str]=None, **kwargs): self.text: str = text self.request_contact: Optional[bool] = request_contact self.request_location: Optional[bool] = request_location @@ -2957,6 +2960,7 @@ def __init__(self, text: str, request_contact: Optional[bool]=None, self.request_chat: Optional[KeyboardButtonRequestChat] = request_chat self.request_users: Optional[KeyboardButtonRequestUsers] = request_users self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id + self.style: Optional[str] = style if request_user is not None: log_deprecation_warning('The parameter "request_user" is deprecated, use "request_users" instead') if self.request_users is None: @@ -2983,6 +2987,8 @@ def to_dict(self): json_dict['request_chat'] = self.request_chat.to_dict() if self.icon_custom_emoji_id is not None: json_dict['icon_custom_emoji_id'] = self.icon_custom_emoji_id + if self.style is not None: + json_dict['style'] = self.style return json_dict @@ -3111,6 +3117,10 @@ class InlineKeyboardButton(Dictionaryable, JsonSerializable, JsonDeserializable) group and supergroup chats if the owner of the bot has a Telegram Premium subscription. :type icon_custom_emoji_id: :obj:`str` + :param style: Optional. Style of the button. Must be one of “danger” (red), “success” (green) or “primary” (blue). If omitted, + then an app-specific style is used. + :type style: :obj:`str` + :param url: Optional. HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id= can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings. :type url: :obj:`str` @@ -3176,7 +3186,7 @@ def de_json(cls, json_string): def __init__(self, text: str, url: Optional[str]=None, callback_data: Optional[str]=None, web_app: Optional[WebAppInfo]=None, switch_inline_query: Optional[str]=None, switch_inline_query_current_chat: Optional[str]=None, switch_inline_query_chosen_chat: Optional[SwitchInlineQueryChosenChat]=None, callback_game=None, pay: Optional[bool]=None, - login_url: Optional[LoginUrl]=None, copy_text: Optional[CopyTextButton]=None, icon_custom_emoji_id: Optional[str]=None, **kwargs): + login_url: Optional[LoginUrl]=None, copy_text: Optional[CopyTextButton]=None, icon_custom_emoji_id: Optional[str]=None, style: Optional[str]=None, **kwargs): self.text: str = text self.url: Optional[str] = url self.callback_data: Optional[str] = callback_data @@ -3189,6 +3199,7 @@ def __init__(self, text: str, url: Optional[str]=None, callback_data: Optional[s self.login_url: Optional[LoginUrl] = login_url self.copy_text: Optional[CopyTextButton] = copy_text self.icon_custom_emoji_id: Optional[str] = icon_custom_emoji_id + self.style: Optional[str] = style def to_json(self): return json.dumps(self.to_dict()) @@ -3217,6 +3228,8 @@ def to_dict(self): json_dict['copy_text'] = self.copy_text.to_dict() if self.icon_custom_emoji_id is not None: json_dict['icon_custom_emoji_id'] = self.icon_custom_emoji_id + if self.style is not None: + json_dict['style'] = self.style return json_dict From 742c0ae418475f60989890164c2a15c36a2b43b5 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:49:40 +0400 Subject: [PATCH 04/12] Added the class ChatOwnerLeft and the field chat_owner_left to the class Message. Added the class ChatOwnerChanged and the field chat_owner_changed to the class Message. --- telebot/types.py | 61 +++++++++++++++++++++++++++++++++++++++++++++++- telebot/util.py | 2 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index e82406b1e..1fbe0c5ac 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -1133,6 +1133,12 @@ class Message(JsonDeserializable): the bot itself) :type left_chat_member: :class:`telebot.types.User` + :param chat_owner_left: Optional. Service message: chat owner has left + :type chat_owner_left: :class:`telebot.types.ChatOwnerLeft` + + :param chat_owner_changed: Optional. Service message: chat owner has changed + :type chat_owner_changed: :class:`telebot.types.ChatOwnerChanged` + :param new_chat_title: Optional. A chat title was changed to this value :type new_chat_title: :obj:`str` @@ -1584,6 +1590,12 @@ def de_json(cls, json_string): if 'suggested_post_refunded' in obj: opts['suggested_post_refunded'] = SuggestedPostRefunded.de_json(obj['suggested_post_refunded']) content_type = 'suggested_post_refunded' + if 'chat_owner_changed' in obj: + opts['chat_owner_changed'] = ChatOwnerChanged.de_json(obj['chat_owner_changed']) + content_type = 'chat_owner_changed' + if 'chat_owner_left' in obj: + opts['chat_owner_left'] = ChatOwnerLeft.de_json(obj['chat_owner_left']) + content_type = 'chat_owner_left' return cls(message_id, from_user, date, chat, content_type, opts, json_string) @@ -1720,6 +1732,8 @@ def __init__(self, message_id, from_user, date, chat, content_type, options, jso self.suggested_post_declined: Optional[SuggestedPostDeclined] = None self.suggested_post_paid: Optional[SuggestedPostPaid] = None self.suggested_post_refunded: Optional[SuggestedPostRefunded] = None + self.chat_owner_left: Optional[ChatOwnerLeft] = None + self.chat_owner_changed: Optional[ChatOwnerChanged] = None for key in options: setattr(self, key, options[key]) @@ -13505,4 +13519,49 @@ def de_json(cls, json_string): obj = cls.check_json(json_string) return cls(**obj) - \ No newline at end of file + +class ChatOwnerLeft(JsonDeserializable): + """ + Describes a service message about the chat owner leaving the chat. + + Telegram documentation: https://core.telegram.org/bots/api#chatownerleft + + :param new_owner: Optional. The user which will be the new owner of the chat if the previous owner does not return to the chat + :type new_owner: :class:`User` + + :return: Instance of the class + :rtype: :class:`ChatOwnerLeft` + """ + def __init__(self, new_owner: Optional[User] = None, **kwargs): + self.new_owner: Optional[User] = new_owner + + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + if 'new_owner' in obj: + obj['new_owner'] = User.de_json(obj['new_owner']) + return cls(**obj) + +class ChatOwnerChanged(JsonDeserializable): + """ + Describes a service message about an ownership change in the chat. + + Telegram documentation: https://core.telegram.org/bots/api#chatownerchanged + + :param new_owner: The new owner of the chat + :type new_owner: :class:`User` + + :return: Instance of the class + :rtype: :class:`ChatOwnerChanged` + """ + def __init__(self, new_owner: User, **kwargs): + self.new_owner: User = new_owner + + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + obj['new_owner'] = User.de_json(obj['new_owner']) + return cls(**obj) + diff --git a/telebot/util.py b/telebot/util.py index 9d16a0dca..71117c679 100644 --- a/telebot/util.py +++ b/telebot/util.py @@ -43,7 +43,7 @@ 'giveaway_created', 'giveaway_winners', 'giveaway_completed', 'boost_added', 'paid_message_price_changed', 'checklist_tasks_done', 'checklist_tasks_added', 'direct_message_price_changed', 'suggested_post_refunded', 'suggested_post_info', 'suggested_post_approved', 'suggested_post_approval_failed', 'suggested_post_declined', - 'suggested_post_paid', 'gift_upgrade_sent' + 'suggested_post_paid', 'gift_upgrade_sent', 'chat_owner_left', 'chat_owner_changed' ] #: All update types, should be used for allowed_updates parameter in polling. From 78158bab090798aeb05c8a1c5910f34441b34c37 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:53:39 +0400 Subject: [PATCH 05/12] Added the methods setMyProfilePhoto and removeMyProfilePhoto, allowing bots to manage their profile picture. --- telebot/__init__.py | 25 +++++++++++++++++++++++++ telebot/apihelper.py | 16 ++++++++++++++++ telebot/async_telebot.py | 25 +++++++++++++++++++++++++ telebot/asyncio_helper.py | 16 ++++++++++++++++ 4 files changed, 82 insertions(+) diff --git a/telebot/__init__.py b/telebot/__init__.py index 3e1973eb5..9f3ea6f53 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -5048,6 +5048,31 @@ def get_my_short_description(self, language_code: Optional[str]=None): return types.BotShortDescription.de_json( apihelper.get_my_short_description(self.token, language_code=language_code)) + def set_my_profile_photo(self, photo: Any) -> bool: + """ + Use this method to change the profile photo of the bot. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#setmyprofilephoto + + :param photo: New profile photo for the bot, uploaded using multipart/form-data + :type photo: :obj:`typing.Union[file_like, str]` + + :return: True on success. + :rtype: :obj:`bool` + """ + return apihelper.set_my_profile_photo(self.token, photo) + + def remove_my_profile_photo(self) -> bool: + """ + Use this method to remove the profile photo of the bot. Requires no parameters. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#removemyprofilephoto + + :return: True on success. + :rtype: :obj:`bool` + """ + return apihelper.remove_my_profile_photo(self.token) + def set_chat_menu_button(self, chat_id: Union[int, str]=None, menu_button: types.MenuButton=None) -> bool: """ diff --git a/telebot/apihelper.py b/telebot/apihelper.py index 649b85f77..b5c5a8612 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1526,6 +1526,22 @@ def get_my_name(token, language_code=None): payload['language_code'] = language_code return _make_request(token, method_url, params=payload) +def set_my_profile_photo(token, photo): + method_url = r'setMyProfilePhoto' + files = None + payload = {} + if util.is_string(photo): + payload['photo'] = photo + elif util.is_pil_image(photo): + files = {'photo': util.pil_image_to_file(photo)} + else: + files = {'photo': photo} + return _make_request(token, method_url, params=payload, files=files, method='post') + +def delete_my_profile_photo(token): + method_url = r'deleteMyProfilePhoto' + return _make_request(token, method_url, method='post') + def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index e1a2685c1..b60979841 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -6533,6 +6533,31 @@ async def get_my_name(self, language_code: Optional[str]=None): result = await asyncio_helper.get_my_name(self.token, language_code) return types.BotName.de_json(result) + async def set_my_profile_photo(self, photo: Any) -> bool: + """ + Use this method to change the profile photo of the bot. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#setmyprofilephoto + + :param photo: InputProfilePhoto: The new profile photo to set + :type photo: :obj:`typing.Union[file_like, str]` + + :return: True on success. + :rtype: :obj:`bool` + """ + return await asyncio_helper.set_my_profile_photo(self.token, photo) + + async def remove_my_profile_photo(self) -> bool: + """ + Use this method to remove the profile photo of the bot. Requires no parameters. Returns True on success. + + Telegram documentation: https://core.telegram.org/bots/api#removemyprofilephoto + + :return: True on success. + :rtype: :obj:`bool` + """ + return await asyncio_helper.remove_my_profile_photo(self.token) + async def set_chat_menu_button(self, chat_id: Union[int, str]=None, menu_button: types.MenuButton=None) -> bool: """ diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index e5e815c09..6086afb15 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1517,6 +1517,22 @@ async def get_my_name(token, language_code=None): payload['language_code'] = language_code return await _process_request(token, method_url, params=payload) +async def set_my_profile_photo(token, photo): + method_url = r'setMyProfilePhoto' + payload = {} + files = None + if util.is_string(photo): + payload['photo'] = photo + elif util.is_pil_image(photo): + files = {'photo': util.pil_image_to_file(photo)} + else: + files = {'photo': photo} + return await _process_request(token, method_url, params=payload, files=files, method='post') + +async def remove_my_profile_photo(token): + method_url = r'removeMyProfilePhoto' + return await _process_request(token, method_url, method='post') + async def set_chat_menu_button(token, chat_id=None, menu_button=None): method_url = r'setChatMenuButton' payload = {} From 283278a1a5c5b374e40ac0d35ebceea34beb0a0b Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:57:07 +0400 Subject: [PATCH 06/12] Added the class VideoQuality and the field qualities to the class Video allowing bots to get information about other available qualities of a video. --- telebot/types.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 1fbe0c5ac..db2cc603f 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -2185,6 +2185,9 @@ class Video(JsonDeserializable): :param start_timestamp: Optional. Timestamp in seconds from which the video will play in the message :type start_timestamp: :obj:`int` + :param qualities: Optional. List of available qualities of the video + :type qualities: List[:class:`telebot.types.VideoQuality`] + :param file_name: Optional. Original filename as defined by sender :type file_name: :obj:`str` @@ -2207,10 +2210,12 @@ def de_json(cls, json_string): obj['thumbnail'] = PhotoSize.de_json(obj['thumbnail']) if 'cover' in obj: obj['cover'] = [PhotoSize.de_json(c) for c in obj['cover']] + if 'qualities' in obj: + obj['qualities'] = [VideoQuality.de_json(q) for q in obj['qualities']] return cls(**obj) def __init__(self, file_id, file_unique_id, width, height, duration, thumbnail=None, file_name=None, mime_type=None, file_size=None, - cover=None, start_timestamp=None, **kwargs): + cover=None, start_timestamp=None, qualities=None, **kwargs): self.file_id: str = file_id self.file_unique_id: str = file_unique_id self.width: int = width @@ -2222,7 +2227,8 @@ def __init__(self, file_id, file_unique_id, width, height, duration, thumbnail=N self.file_size: Optional[int] = file_size self.cover: Optional[List[PhotoSize]] = cover self.start_timestamp: Optional[int] = start_timestamp - + self.qualities: Optional[List[VideoQuality]] = qualities + @property def thumb(self) -> Optional[PhotoSize]: log_deprecation_warning('The parameter "thumb" is deprecated, use "thumbnail" instead') @@ -13565,3 +13571,45 @@ def de_json(cls, json_string): obj['new_owner'] = User.de_json(obj['new_owner']) return cls(**obj) +class VideoQuality(JsonDeserializable): + """ + This object represents a video file of a specific quality. + + Telegram documentation: https://core.telegram.org/bots/api#videoquality + + :param file_id: Identifier for this file, which can be used to download or reuse the file + :type file_id: :obj:`str` + + :param file_unique_id: Unique identifier for this file, which is supposed to be the same over time and for different bots. Can't be used to download or reuse the file. + :type file_unique_id: :obj:`str` + + :param width: Video width + :type width: :obj:`int` + + :param height: Video height + :type height: :obj:`int` + + :param codec: Codec that was used to encode the video, for example, “h264”, “h265”, or “av01” + :type codec: :obj:`str` + + :param file_size: Optional. File size in bytes. It can be bigger than 2^31 and some programming languages may have difficulty/silent defects in interpreting it. + But it has at most 52 significant bits, so a signed 64-bit integer or double-precision float type are safe for storing this value. + :type file_size: :obj:`int` + + :return: Instance of the class + :rtype: :class:`VideoQuality` + """ + def __init__(self, file_id: str, file_unique_id: str, width: int, height: int, codec: str, file_size: Optional[int] = None, **kwargs): + self.file_id: str = file_id + self.file_unique_id: str = file_unique_id + self.width: int = width + self.height: int = height + self.codec: str = codec + self.file_size: Optional[int] = file_size + + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + return cls(**obj) + \ No newline at end of file From 84012c4d097386b97e5c5aba697e36ad1b861397 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 16:58:40 +0400 Subject: [PATCH 07/12] Added the field first_profile_audio to the class ChatFullInfo. --- telebot/types.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index db2cc603f..c7f12c1c2 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -760,6 +760,9 @@ class ChatFullInfo(JsonDeserializable): :param paid_message_star_count: Optional. The number of Telegram Stars a general user have to pay to send a message to the chat :type paid_message_star_count: :obj:`int` + + :param first_profile_audio: Optional. For private chats, the first audio added to the profile of the user + :type first_profile_audio: :class:`telebot.types.Audio` :param unique_gift_colors: Optional. The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews :type unique_gift_colors: :class:`telebot.types.UniqueGiftColors` @@ -799,6 +802,8 @@ def de_json(cls, json_string): obj['rating'] = UserRating.de_json(obj['rating']) if 'unique_gift_colors' in obj: obj['unique_gift_colors'] = UniqueGiftColors.de_json(obj['unique_gift_colors']) + if 'first_profile_audio' in obj: + obj['first_profile_audio'] = Audio.de_json(obj['first_profile_audio']) return cls(**obj) def __init__(self, id, type, title=None, username=None, first_name=None, @@ -816,7 +821,7 @@ def __init__(self, id, type, title=None, username=None, first_name=None, business_opening_hours=None, personal_chat=None, birthdate=None, can_send_paid_media=None, accepted_gift_types=None, is_direct_messages=None, parent_chat=None, rating=None, paid_message_star_count=None, - unique_gift_colors=None, **kwargs): + unique_gift_colors=None, first_profile_audio=None, **kwargs): self.id: int = id self.type: str = type self.title: Optional[str] = title @@ -867,6 +872,7 @@ def __init__(self, id, type, title=None, username=None, first_name=None, self.rating: Optional[UserRating] = rating self.paid_message_star_count: Optional[int] = paid_message_star_count self.unique_gift_colors: Optional[UniqueGiftColors] = unique_gift_colors + self.first_profile_audio: Optional[Audio] = first_profile_audio @property @@ -2228,7 +2234,7 @@ def __init__(self, file_id, file_unique_id, width, height, duration, thumbnail=N self.cover: Optional[List[PhotoSize]] = cover self.start_timestamp: Optional[int] = start_timestamp self.qualities: Optional[List[VideoQuality]] = qualities - + @property def thumb(self) -> Optional[PhotoSize]: log_deprecation_warning('The parameter "thumb" is deprecated, use "thumbnail" instead') From 0249aefc13a37d329296833e8d3562862c99ed17 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 17:04:32 +0400 Subject: [PATCH 08/12] Added the class UserProfileAudios and the method getUserProfileAudios, allowing bots to fetch a list of audios added to the profile of a user. --- telebot/__init__.py | 23 +++++++++++++++++++++++ telebot/apihelper.py | 10 ++++++++++ telebot/async_telebot.py | 20 ++++++++++++++++++++ telebot/asyncio_helper.py | 10 ++++++++++ telebot/types.py | 29 ++++++++++++++++++++++++++++- 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 9f3ea6f53..826a3627e 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -1512,6 +1512,29 @@ def get_user_profile_photos(self, user_id: int, offset: Optional[int]=None, apihelper.get_user_profile_photos(self.token, user_id, offset=offset, limit=limit) ) + def get_user_profile_audios(self, user_id: int, offset: Optional[int]=None, + limit: Optional[int]=None) -> types.UserProfileAudios: + """ + Use this method to get a list of profile audios for a user. Returns a :class:`telebot.types.UserProfileAudios` object. + + Telegram documentation: https://core.telegram.org/bots/api#getuserprofileaudios + + :param user_id: Unique identifier of the target user + :type user_id: :obj:`int` + + :param offset: Sequential number of the first audio to be returned. By default, all audios are returned. + :type offset: :obj:`int` + + :param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100. + :type limit: :obj:`int` + + :return: If the request is successful, a UserProfileAudios object is returned. + :rtype: :class:`telebot.types.UserProfileAudios` + """ + return types.UserProfileAudios.de_json( + apihelper.get_user_profile_audios(self.token, user_id, offset=offset, limit=limit) + ) + def set_user_emoji_status(self, user_id: int, emoji_status_custom_emoji_id: Optional[str]=None, emoji_status_expiration_date: Optional[int]=None) -> bool: """ Changes the emoji status for a given user that previously allowed the bot to manage their emoji status via the Mini App method requestEmojiStatusAccess. Returns True on success. diff --git a/telebot/apihelper.py b/telebot/apihelper.py index b5c5a8612..cfa241b29 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -349,6 +349,16 @@ def get_user_profile_photos(token, user_id, offset=None, limit=None): return _make_request(token, method_url, params=payload) +def get_user_profile_audios(token, user_id, offset=None, limit=None): + method_url = r'getUserProfileAudios' + payload = {'user_id': user_id} + if offset: + payload['offset'] = offset + if limit: + payload['limit'] = limit + return _make_request(token, method_url, params=payload) + + def set_user_emoji_status(token, user_id, emoji_status_custom_emoji_id=None, emoji_status_expiration_date=None): method_url = r'setUserEmojiStatus' payload = {'user_id': user_id} diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index b60979841..604f3d815 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -2989,6 +2989,26 @@ async def get_user_profile_photos(self, user_id: int, offset: Optional[int]=None result = await asyncio_helper.get_user_profile_photos(self.token, user_id, offset, limit) return types.UserProfilePhotos.de_json(result) + async def get_user_profile_audios(self, user_id: int, offset: Optional[int]=None, limit: Optional[int]=None) -> types.UserProfileAudios: + """ + Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object. + + Telegram documentation: https://core.telegram.org/bots/api#getuserprofileaudios + + :param user_id: Unique identifier of the target user + :type user_id: :obj:`int` + + :param offset: Sequential number of the first audio to be returned. By default, all audios are returned. + :type offset: :obj:`int` + + :param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100. + :type limit: :obj:`int` + + :return: If successful, returns a UserProfileAudios object. + :rtype: :class:`telebot.types.UserProfileAudios` + """ + return types.UserProfileAudios.de_json(await asyncio_helper.get_user_profile_audios(self.token, user_id, offset, limit)) + async def set_user_emoji_status(self, user_id: int, emoji_status_custom_emoji_id: Optional[str]=None, emoji_status_expiration_date: Optional[int]=None) -> bool: """ Use this method to change the emoji status for a given user that previously allowed the bot to manage their emoji status via the Mini App method requestEmojiStatusAccess. diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 6086afb15..6543c7c7a 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -330,6 +330,16 @@ async def get_user_profile_photos(token, user_id, offset=None, limit=None): return await _process_request(token, method_url, params=payload) +async def get_user_profile_audios(token, user_id, offset=None, limit=None): + method_url = r'getUserProfileAudios' + payload = {'user_id': user_id} + if offset: + payload['offset'] = offset + if limit: + payload['limit'] = limit + return await _process_request(token, method_url, params=payload) + + async def set_user_emoji_status(token, user_id, emoji_status_custom_emoji_id=None, emoji_status_expiration_date=None): method_url = r'setUserEmojiStatus' payload = {'user_id': user_id} diff --git a/telebot/types.py b/telebot/types.py index c7f12c1c2..1eac92e89 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -13618,4 +13618,31 @@ def de_json(cls, json_string): if json_string is None: return None obj = cls.check_json(json_string) return cls(**obj) - \ No newline at end of file + + +class UserProfileAudios(JsonDeserializable): + """ + This object represents the audios displayed on a user's profile. + + Telegram documentation: https://core.telegram.org/bots/api#userprofileaudios + + :param total_count: Total number of profile audios for the target user + :type total_count: :obj:`int` + + :param audios: Requested profile audios + :type audios: :obj:`list` of :class:`Audio` + + :return: Instance of the class + :rtype: :class:`UserProfileAudios` + """ + def __init__(self, total_count: int, audios: List[Audio], **kwargs): + self.total_count: int = total_count + self.audios: List[Audio] = audios + + @classmethod + def de_json(cls, json_string): + if json_string is None: return None + obj = cls.check_json(json_string) + obj['audios'] = [Audio.de_json(audio) for audio in obj['audios']] + return cls(**obj) + From 9d72040b8ae494c3874114c10e8c3a06422e95f7 Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 17:06:27 +0400 Subject: [PATCH 09/12] Added the field rarity to the class UniqueGiftModel. Added the field is_burned to the class UniqueGift. --- telebot/types.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/telebot/types.py b/telebot/types.py index 1eac92e89..13811ffe7 100644 --- a/telebot/types.py +++ b/telebot/types.py @@ -12032,6 +12032,9 @@ class UniqueGift(JsonDeserializable): :param is_premium: Optional. True, if the gift can only be purchased by Telegram Premium subscribers :type is_premium: :obj:`bool` + :param is_burned: Optional. True, if the gift was used to craft another gift and isn't available anymore + :type is_burned: :obj:`bool` + :param colors: Optional. The color scheme that can be used by the gift's owner for the chat's name, replies to messages and link previews; for business account gifts and gifts that are currently on sale only :type colors: :class:`UniqueGiftColors` :param publisher_chat: Optional. Information about the chat that published the gift @@ -12040,7 +12043,8 @@ class UniqueGift(JsonDeserializable): :return: Instance of the class :rtype: :class:`UniqueGift` """ - def __init__(self, base_name, name, number, model, symbol, backdrop, gift_id, publisher_chat=None, is_from_blockchain=None, is_premium=None, colors=None, **kwargs): + def __init__(self, base_name, name, number, model, symbol, backdrop, gift_id, publisher_chat=None, is_from_blockchain=None, is_premium=None, colors=None, + is_burned=None, **kwargs): self.base_name: str = base_name self.name: str = name self.number: int = number @@ -12052,6 +12056,7 @@ def __init__(self, base_name, name, number, model, symbol, backdrop, gift_id, pu self.is_premium: Optional[bool] = is_premium self.colors: Optional[UniqueGiftColors] = colors self.publisher_chat: Optional[Chat] = publisher_chat + self.is_burned: Optional[bool] = is_burned @classmethod def de_json(cls, json_string): @@ -12082,14 +12087,18 @@ class UniqueGiftModel(JsonDeserializable): :param rarity_per_mille: The number of unique gifts that receive this model for every 1000 gifts upgraded :type rarity_per_mille: :obj:`int` + :param rarity: Optional. Rarity of the model if it is a crafted model. Currently, can be “uncommon”, “rare”, “epic”, or “legendary”. + :type rarity: :obj:`str` + :return: Instance of the class :rtype: :class:`UniqueGiftModel` """ - def __init__(self, name, sticker, rarity_per_mille, **kwargs): + def __init__(self, name, sticker, rarity_per_mille, rarity=None, **kwargs): self.name: str = name self.sticker: Sticker = sticker self.rarity_per_mille: int = rarity_per_mille + self.rarity: Optional[str] = rarity @classmethod def de_json(cls, json_string): From 092640eeb603048879e4fd83df41fa0e22dcce3a Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 17:07:35 +0400 Subject: [PATCH 10/12] Allowed bots to create topics in private chats using the method createForumTopic. --- telebot/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 826a3627e..6caa64c62 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -7815,8 +7815,8 @@ def create_forum_topic(self, chat_id: int, name: str, icon_color: Optional[int]=None, icon_custom_emoji_id: Optional[str]=None) -> types.ForumTopic: """ - Use this method to create a topic in a forum supergroup chat. The bot must be an administrator - in the chat for this to work and must have the can_manage_topics administrator rights. + Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot + must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object. Telegram documentation: https://core.telegram.org/bots/api#createforumtopic From 64e34792b96203e5a6677eb8dfa68b216a2e00cf Mon Sep 17 00:00:00 2001 From: _run Date: Tue, 10 Feb 2026 17:08:11 +0400 Subject: [PATCH 11/12] Update version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a6c7fd96..3d3c4ad82 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

A simple, but extensible Python implementation for the Telegram Bot API.

Both synchronous and asynchronous.

-##

Supported Bot API version: Supported Bot API version +##

Supported Bot API version: Supported Bot API version

Official documentation

Official ru documentation

From bec87fa5ef6a7e31fea46cb2e84e964deec14504 Mon Sep 17 00:00:00 2001 From: _run Date: Sat, 14 Feb 2026 17:57:41 +0400 Subject: [PATCH 12/12] Fix setmyprofilephoto --- telebot/__init__.py | 8 ++++---- telebot/apihelper.py | 9 ++------- telebot/async_telebot.py | 10 +++++----- telebot/asyncio_helper.py | 10 +++------- 4 files changed, 14 insertions(+), 23 deletions(-) diff --git a/telebot/__init__.py b/telebot/__init__.py index 6caa64c62..9a8904a18 100644 --- a/telebot/__init__.py +++ b/telebot/__init__.py @@ -5071,14 +5071,14 @@ def get_my_short_description(self, language_code: Optional[str]=None): return types.BotShortDescription.de_json( apihelper.get_my_short_description(self.token, language_code=language_code)) - def set_my_profile_photo(self, photo: Any) -> bool: + def set_my_profile_photo(self, photo: types.InputProfilePhoto) -> bool: """ Use this method to change the profile photo of the bot. Returns True on success. Telegram documentation: https://core.telegram.org/bots/api#setmyprofilephoto - :param photo: New profile photo for the bot, uploaded using multipart/form-data - :type photo: :obj:`typing.Union[file_like, str]` + :param photo: The new profile photo to set + :type photo: :class:`telebot.types.InputProfilePhoto` :return: True on success. :rtype: :obj:`bool` @@ -7806,7 +7806,7 @@ def delete_sticker_from_set(self, sticker: str) -> bool: :param sticker: File identifier of the sticker :return: On success, True is returned. - :rtype: :obj:`bool` + :rtype: :obj:`bool` """ return apihelper.delete_sticker_from_set(self.token, sticker) diff --git a/telebot/apihelper.py b/telebot/apihelper.py index cfa241b29..ea189b26b 100644 --- a/telebot/apihelper.py +++ b/telebot/apihelper.py @@ -1538,14 +1538,9 @@ def get_my_name(token, language_code=None): def set_my_profile_photo(token, photo): method_url = r'setMyProfilePhoto' - files = None payload = {} - if util.is_string(photo): - payload['photo'] = photo - elif util.is_pil_image(photo): - files = {'photo': util.pil_image_to_file(photo)} - else: - files = {'photo': photo} + photo_json, files = photo.convert_input_profile_photo() + payload['photo'] = photo_json return _make_request(token, method_url, params=payload, files=files, method='post') def delete_my_profile_photo(token): diff --git a/telebot/async_telebot.py b/telebot/async_telebot.py index 604f3d815..1bdece11b 100644 --- a/telebot/async_telebot.py +++ b/telebot/async_telebot.py @@ -3007,7 +3007,7 @@ async def get_user_profile_audios(self, user_id: int, offset: Optional[int]=None :return: If successful, returns a UserProfileAudios object. :rtype: :class:`telebot.types.UserProfileAudios` """ - return types.UserProfileAudios.de_json(await asyncio_helper.get_user_profile_audios(self.token, user_id, offset, limit)) + return types.UserProfileAudios.de_json(await asyncio_helper.get_user_profile_audios(self.token, user_id, offset=offset, limit=limit)) async def set_user_emoji_status(self, user_id: int, emoji_status_custom_emoji_id: Optional[str]=None, emoji_status_expiration_date: Optional[int]=None) -> bool: """ @@ -6553,14 +6553,14 @@ async def get_my_name(self, language_code: Optional[str]=None): result = await asyncio_helper.get_my_name(self.token, language_code) return types.BotName.de_json(result) - async def set_my_profile_photo(self, photo: Any) -> bool: + async def set_my_profile_photo(self, photo: types.InputProfilePhoto) -> bool: """ Use this method to change the profile photo of the bot. Returns True on success. Telegram documentation: https://core.telegram.org/bots/api#setmyprofilephoto :param photo: InputProfilePhoto: The new profile photo to set - :type photo: :obj:`typing.Union[file_like, str]` + :type photo: :class:`telebot.types.InputProfilePhoto` :return: True on success. :rtype: :obj:`bool` @@ -9238,8 +9238,8 @@ async def create_forum_topic(self, chat_id: int, name: str, icon_color: Optional[int]=None, icon_custom_emoji_id: Optional[str]=None) -> types.ForumTopic: """ - Use this method to create a topic in a forum supergroup chat. The bot must be an administrator - in the chat for this to work and must have the can_manage_topics administrator rights. + Use this method to create a topic in a forum supergroup chat or a private chat with a user. In the case of a supergroup chat the bot + must be an administrator in the chat for this to work and must have the can_manage_topics administrator right. Returns information about the created topic as a ForumTopic object. Telegram documentation: https://core.telegram.org/bots/api#createforumtopic diff --git a/telebot/asyncio_helper.py b/telebot/asyncio_helper.py index 6543c7c7a..20e9127d2 100644 --- a/telebot/asyncio_helper.py +++ b/telebot/asyncio_helper.py @@ -1530,13 +1530,9 @@ async def get_my_name(token, language_code=None): async def set_my_profile_photo(token, photo): method_url = r'setMyProfilePhoto' payload = {} - files = None - if util.is_string(photo): - payload['photo'] = photo - elif util.is_pil_image(photo): - files = {'photo': util.pil_image_to_file(photo)} - else: - files = {'photo': photo} + photo_json, files = photo.convert_input_profile_photo() + payload['photo'] = photo_json + return await _process_request(token, method_url, params=payload, files=files, method='post') async def remove_my_profile_photo(token):