Added full support of the Bot API 9.4 (#1761)

* Bump API schema to version 9.4, add new object types, methods, and properties.

* Add tests for `ChatOwnerChanged` and `ChatOwnerLeft` message types

* Add tests for `GetUserProfileAudios`, `RemoveMyProfilePhoto`, and `SetMyProfilePhoto` methods

* Bump version

* Update Makefile variables and refactor `test_get_user_profile_audios.py`

* Document new features and updates from Bot API 9.4 in changelog

* Add `ButtonStyle` enum to represent button styles in the Telegram API

* Fix review issues from PR #1761

- Remove stray '-' artifact from GameHighScore docstring and butcher schema
- Fix Makefile reformat target scope inconsistency (ruff check --fix)
- Fix ButtonStyle enum source URL (#chat -> #inlinekeyboardbutton)
- Add User.get_profile_audios() shortcut method (parallel to get_profile_photos)
- Test ChatOwnerLeft with new_owner=None (edge case)
- Add VideoQuality type and Video.qualities nesting tests
- Add User.get_profile_audios() test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Revert "Fix review issues from PR #1761"

This reverts commit 2184e98988.

* Update source links for `ButtonStyle` documentation to reflect accurate API references

* Fix review issues from PR #1761 (#1762)

* Fix review issues from PR #1761

- Remove stray '-' artifact from GameHighScore docstring
- Fix Makefile reformat target scope inconsistency (ruff check --fix)
- Add User.get_profile_audios() shortcut method (parallel to get_profile_photos)
- Test ChatOwnerLeft with new_owner=None (edge case)
- Add VideoQuality type and Video.qualities nesting tests
- Add User.get_profile_audios() test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review comments: use fixture and variables in tests, add changelog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Address review follow-ups for PR #1762

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* Reformat code

* Shut up, ruff

---------

Co-authored-by: latand <latand666@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Kostiantyn Kriuchkov <36363097+Latand@users.noreply.github.com>
This commit is contained in:
Alex Root Junior 2026-02-10 23:43:52 +02:00 committed by GitHub
parent da7bfdca0c
commit 49d0784e33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 1457 additions and 61 deletions

View file

@ -395,6 +395,14 @@
"name": "rating",
"required": false
},
{
"type": "Audio",
"description": "For private chats, the first audio added to the profile of the user",
"html_description": "<td><em>Optional</em>. For private chats, the first audio added to the profile of the user</td>",
"rst_description": "*Optional*. For private chats, the first audio added to the profile of the user\n",
"name": "first_profile_audio",
"required": false
},
{
"type": "UniqueGiftColors",
"description": "The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews",

View file

@ -0,0 +1,25 @@
{
"meta": {},
"group": {
"title": "Available types",
"anchor": "available-types"
},
"object": {
"anchor": "chatownerchanged",
"name": "ChatOwnerChanged",
"description": "Describes a service message about an ownership change in the chat.",
"html_description": "<p>Describes a service message about an ownership change in the chat.</p>",
"rst_description": "Describes a service message about an ownership change in the chat.",
"annotations": [
{
"type": "User",
"description": "The new owner of the chat",
"html_description": "<td>The new owner of the chat</td>",
"rst_description": "The new owner of the chat\n",
"name": "new_owner",
"required": true
}
],
"category": "types"
}
}

25
.butcher/types/ChatOwnerLeft/entity.json generated Normal file
View file

@ -0,0 +1,25 @@
{
"meta": {},
"group": {
"title": "Available types",
"anchor": "available-types"
},
"object": {
"anchor": "chatownerleft",
"name": "ChatOwnerLeft",
"description": "Describes a service message about the chat owner leaving the chat.",
"html_description": "<p>Describes a service message about the chat owner leaving the chat.</p>",
"rst_description": "Describes a service message about the chat owner leaving the chat.",
"annotations": [
{
"type": "User",
"description": "The user which will be the new owner of the chat if the previous owner does not return to the chat",
"html_description": "<td><em>Optional</em>. The user which will be the new owner of the chat if the previous owner does not return to the chat</td>",
"rst_description": "*Optional*. The user which will be the new owner of the chat if the previous owner does not return to the chat\n",
"name": "new_owner",
"required": false
}
],
"category": "types"
}
}

View file

@ -7,9 +7,9 @@
"object": {
"anchor": "gamehighscore",
"name": "GameHighScore",
"description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ",
"html_description": "<p>This object represents one row of the high scores table for a game.</p><p>And that's about all we've got for now.<br/>\nIf you've got any questions, please check out our <a href=\"/bots/faq\"><strong>Bot FAQ &#187;</strong></a></p>",
"rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq <https://core.telegram.org/bots/faq>`_ **Bot FAQ »**",
"description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\nIf you've got any questions, please check out our Bot FAQ\n-",
"html_description": "<p>This object represents one row of the high scores table for a game.</p><p>And that's about all we've got for now.<br/>\nIf you've got any questions, please check out our <a href=\"/bots/faq\"><strong>Bot FAQ &#187;</strong></a><br/>\n-</p>",
"rst_description": "This object represents one row of the high scores table for a game.\nAnd that's about all we've got for now.\n\nIf you've got any questions, please check out our `https://core.telegram.org/bots/faq <https://core.telegram.org/bots/faq>`_ **Bot FAQ »**\n\n-",
"annotations": [
{
"type": "Integer",

View file

@ -7,9 +7,9 @@
"object": {
"anchor": "inlinekeyboardbutton",
"name": "InlineKeyboardButton",
"description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.",
"html_description": "<p>This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.</p>",
"rst_description": "This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.",
"description": "This object represents one button of an inline keyboard. Exactly one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button.",
"html_description": "<p>This object represents one button of an inline keyboard. Exactly one of the fields other than <em>text</em>, <em>icon_custom_emoji_id</em>, and <em>style</em> must be used to specify the type of the button.</p>",
"rst_description": "This object represents one button of an inline keyboard. Exactly one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button.",
"annotations": [
{
"type": "String",
@ -19,6 +19,22 @@
"name": "text",
"required": true
},
{
"type": "String",
"description": "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.",
"html_description": "<td><em>Optional</em>. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on <a href=\"https://fragment.com\">Fragment</a> 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.</td>",
"rst_description": "*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 <https://fragment.com>`_ 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.\n",
"name": "icon_custom_emoji_id",
"required": false
},
{
"type": "String",
"description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.",
"html_description": "<td><em>Optional</em>. Style of the button. Must be one of &#8220;danger&#8221; (red), &#8220;success&#8221; (green) or &#8220;primary&#8221; (blue). If omitted, then an app-specific style is used.</td>",
"rst_description": "*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.\n",
"name": "style",
"required": false
},
{
"type": "String",
"description": "HTTP or tg:// URL to be opened when the button is pressed. Links tg://user?id=<user_id> can be used to mention a user by their identifier without using a username, if this is allowed by their privacy settings.",

View file

@ -7,18 +7,34 @@
"object": {
"anchor": "keyboardbutton",
"name": "KeyboardButton",
"description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, String can be used instead of this object to specify the button text.\nNote: request_users and request_chat options will only work in Telegram versions released after 3 February, 2023. Older clients will display unsupported message.",
"html_description": "<p>This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, <em>String</em> can be used instead of this object to specify the button text.</p><p><strong>Note:</strong> <em>request_users</em> and <em>request_chat</em> options will only work in Telegram versions released after 3 February, 2023. Older clients will display <em>unsupported message</em>.</p>",
"rst_description": "This object represents one button of the reply keyboard. At most one of the optional fields must be used to specify type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.\n**Note:** *request_users* and *request_chat* options will only work in Telegram versions released after 3 February, 2023. Older clients will display *unsupported message*.",
"description": "This object represents one button of the reply keyboard. At most one of the fields other than text, icon_custom_emoji_id, and style must be used to specify the type of the button. For simple text buttons, String can be used instead of this object to specify the button text.",
"html_description": "<p>This object represents one button of the reply keyboard. At most one of the fields other than <em>text</em>, <em>icon_custom_emoji_id</em>, and <em>style</em> must be used to specify the type of the button. For simple text buttons, <em>String</em> can be used instead of this object to specify the button text.</p>",
"rst_description": "This object represents one button of the reply keyboard. At most one of the fields other than *text*, *icon_custom_emoji_id*, and *style* must be used to specify the type of the button. For simple text buttons, *String* can be used instead of this object to specify the button text.",
"annotations": [
{
"type": "String",
"description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed",
"html_description": "<td>Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed</td>",
"rst_description": "Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed\n",
"description": "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",
"html_description": "<td>Text of the button. If none of the fields other than <em>text</em>, <em>icon_custom_emoji_id</em>, and <em>style</em> are used, it will be sent as a message when the button is pressed</td>",
"rst_description": "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\n",
"name": "text",
"required": true
},
{
"type": "String",
"description": "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.",
"html_description": "<td><em>Optional</em>. Unique identifier of the custom emoji shown before the text of the button. Can only be used by bots that purchased additional usernames on <a href=\"https://fragment.com\">Fragment</a> 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.</td>",
"rst_description": "*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 <https://fragment.com>`_ 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.\n",
"name": "icon_custom_emoji_id",
"required": false
},
{
"type": "String",
"description": "Style of the button. Must be one of 'danger' (red), 'success' (green) or 'primary' (blue). If omitted, then an app-specific style is used.",
"html_description": "<td><em>Optional</em>. Style of the button. Must be one of &#8220;danger&#8221; (red), &#8220;success&#8221; (green) or &#8220;primary&#8221; (blue). If omitted, then an app-specific style is used.</td>",
"rst_description": "*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.\n",
"name": "style",
"required": false
},
{
"type": "KeyboardButtonRequestUsers",
"description": "If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a 'users_shared' service message. Available in private chats only.",

View file

@ -443,6 +443,22 @@
"name": "left_chat_member",
"required": false
},
{
"type": "ChatOwnerLeft",
"description": "Service message: chat owner has left",
"html_description": "<td><em>Optional</em>. Service message: chat owner has left</td>",
"rst_description": "*Optional*. Service message: chat owner has left\n",
"name": "chat_owner_left",
"required": false
},
{
"type": "ChatOwnerChanged",
"description": "Service message: chat owner has changed",
"html_description": "<td><em>Optional</em>. Service message: chat owner has changed</td>",
"rst_description": "*Optional*. Service message: chat owner has changed\n",
"name": "chat_owner_changed",
"required": false
},
{
"type": "String",
"description": "A chat title was changed to this value",

View file

@ -75,6 +75,14 @@
"name": "is_premium",
"required": false
},
{
"type": "True",
"description": "True, if the gift was used to craft another gift and isn't available anymore",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the gift was used to craft another gift and isn't available anymore</td>",
"rst_description": "*Optional*. :code:`True`, if the gift was used to craft another gift and isn't available anymore\n",
"name": "is_burned",
"required": false
},
{
"type": "True",
"description": "True, if the gift is assigned from the TON blockchain and can't be resold or transferred in Telegram",

View file

@ -29,11 +29,19 @@
},
{
"type": "Integer",
"description": "The number of unique gifts that receive this model for every 1000 gifts upgraded",
"html_description": "<td>The number of unique gifts that receive this model for every 1000 gifts upgraded</td>",
"rst_description": "The number of unique gifts that receive this model for every 1000 gifts upgraded\n",
"description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.",
"html_description": "<td>The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.</td>",
"rst_description": "The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts.\n",
"name": "rarity_per_mille",
"required": true
},
{
"type": "String",
"description": "Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.",
"html_description": "<td><em>Optional</em>. Rarity of the model if it is a crafted model. Currently, can be &#8220;uncommon&#8221;, &#8220;rare&#8221;, &#8220;epic&#8221;, or &#8220;legendary&#8221;.</td>",
"rst_description": "*Optional*. Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'.\n",
"name": "rarity",
"required": false
}
],
"category": "types"

View file

@ -2,3 +2,7 @@ get_profile_photos:
method: getUserProfilePhotos
fill:
user_id: self.id
get_profile_audios:
method: getUserProfileAudios
fill:
user_id: self.id

View file

@ -122,6 +122,14 @@
"rst_description": "*Optional*. :code:`True`, if the bot has forum topic mode enabled in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n",
"name": "has_topics_enabled",
"required": false
},
{
"type": "Boolean",
"description": "True, if the bot allows users to create and delete topics in private chats. Returned only in getMe.",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the bot allows users to create and delete topics in private chats. Returned only in <a href=\"#getme\">getMe</a>.</td>",
"rst_description": "*Optional*. :code:`True`, if the bot allows users to create and delete topics in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`.\n",
"name": "allows_users_to_create_topics",
"required": false
}
],
"category": "types"

View file

@ -0,0 +1,33 @@
{
"meta": {},
"group": {
"title": "Available types",
"anchor": "available-types"
},
"object": {
"anchor": "userprofileaudios",
"name": "UserProfileAudios",
"description": "This object represents the audios displayed on a user's profile.",
"html_description": "<p>This object represents the audios displayed on a user's profile.</p>",
"rst_description": "This object represents the audios displayed on a user's profile.",
"annotations": [
{
"type": "Integer",
"description": "Total number of profile audios for the target user",
"html_description": "<td>Total number of profile audios for the target user</td>",
"rst_description": "Total number of profile audios for the target user\n",
"name": "total_count",
"required": true
},
{
"type": "Array of Audio",
"description": "Requested profile audios",
"html_description": "<td>Requested profile audios</td>",
"rst_description": "Requested profile audios\n",
"name": "audios",
"required": true
}
],
"category": "types"
}
}

View file

@ -75,6 +75,14 @@
"name": "start_timestamp",
"required": false
},
{
"type": "Array of VideoQuality",
"description": "List of available qualities of the video",
"html_description": "<td><em>Optional</em>. List of available qualities of the video</td>",
"rst_description": "*Optional*. List of available qualities of the video\n",
"name": "qualities",
"required": false
},
{
"type": "String",
"description": "Original filename as defined by the sender",

65
.butcher/types/VideoQuality/entity.json generated Normal file
View file

@ -0,0 +1,65 @@
{
"meta": {},
"group": {
"title": "Available types",
"anchor": "available-types"
},
"object": {
"anchor": "videoquality",
"name": "VideoQuality",
"description": "This object represents a video file of a specific quality.",
"html_description": "<p>This object represents a video file of a specific quality.</p>",
"rst_description": "This object represents a video file of a specific quality.",
"annotations": [
{
"type": "String",
"description": "Identifier for this file, which can be used to download or reuse the file",
"html_description": "<td>Identifier for this file, which can be used to download or reuse the file</td>",
"rst_description": "Identifier for this file, which can be used to download or reuse the file\n",
"name": "file_id",
"required": true
},
{
"type": "String",
"description": "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.",
"html_description": "<td>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.</td>",
"rst_description": "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.\n",
"name": "file_unique_id",
"required": true
},
{
"type": "Integer",
"description": "Video width",
"html_description": "<td>Video width</td>",
"rst_description": "Video width\n",
"name": "width",
"required": true
},
{
"type": "Integer",
"description": "Video height",
"html_description": "<td>Video height</td>",
"rst_description": "Video height\n",
"name": "height",
"required": true
},
{
"type": "String",
"description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'",
"html_description": "<td>Codec that was used to encode the video, for example, &#8220;h264&#8221;, &#8220;h265&#8221;, or &#8220;av01&#8221;</td>",
"rst_description": "Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'\n",
"name": "codec",
"required": true
},
{
"type": "Integer",
"description": "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.",
"html_description": "<td><em>Optional</em>. 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.</td>",
"rst_description": "*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.\n",
"name": "file_size",
"required": false
}
],
"category": "types"
}
}