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

@ -1 +1 @@
9.3
9.4

View file

@ -0,0 +1,12 @@
name: ButtonStyle
description: |
This object represents a button style (inline- or reply-keyboard).
Sources:
* https://core.telegram.org/bots/api#inlinekeyboardbutton
* https://core.telegram.org/bots/api#keyboardbutton
parse:
entity: InlineKeyboardButton
attribute: style
regexp: "'([a-z]+)'"

View file

@ -7,9 +7,9 @@
"object": {
"anchor": "createforumtopic",
"name": "createForumTopic",
"description": "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. Returns information about the created topic as a ForumTopic object.",
"html_description": "<p>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 <em>can_manage_topics</em> administrator rights. Returns information about the created topic as a <a href=\"#forumtopic\">ForumTopic</a> object.</p>",
"rst_description": "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. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.",
"description": "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.",
"html_description": "<p>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 <em>can_manage_topics</em> administrator right. Returns information about the created topic as a <a href=\"#forumtopic\">ForumTopic</a> object.</p>",
"rst_description": "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 :class:`aiogram.types.forum_topic.ForumTopic` object.",
"annotations": [
{
"type": "Integer or String",

View file

@ -0,0 +1,41 @@
{
"meta": {},
"group": {
"title": "Available methods",
"anchor": "available-methods"
},
"object": {
"anchor": "getuserprofileaudios",
"name": "getUserProfileAudios",
"description": "Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.",
"html_description": "<p>Use this method to get a list of profile audios for a user. Returns a <a href=\"#userprofileaudios\">UserProfileAudios</a> object.</p>",
"rst_description": "Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.",
"annotations": [
{
"type": "Integer",
"required": true,
"description": "Unique identifier of the target user",
"html_description": "<td>Unique identifier of the target user</td>",
"rst_description": "Unique identifier of the target user\n",
"name": "user_id"
},
{
"type": "Integer",
"required": false,
"description": "Sequential number of the first audio to be returned. By default, all audios are returned.",
"html_description": "<td>Sequential number of the first audio to be returned. By default, all audios are returned.</td>",
"rst_description": "Sequential number of the first audio to be returned. By default, all audios are returned.\n",
"name": "offset"
},
{
"type": "Integer",
"required": false,
"description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.",
"html_description": "<td>Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.</td>",
"rst_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.\n",
"name": "limit"
}
],
"category": "methods"
}
}

View file

@ -0,0 +1,16 @@
{
"meta": {},
"group": {
"title": "Available methods",
"anchor": "available-methods"
},
"object": {
"anchor": "removemyprofilephoto",
"name": "removeMyProfilePhoto",
"description": "Removes the profile photo of the bot. Requires no parameters. Returns True on success.",
"html_description": "<p>Removes the profile photo of the bot. Requires no parameters. Returns <em>True</em> on success.</p>",
"rst_description": "Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.",
"annotations": [],
"category": "methods"
}
}

View file

@ -0,0 +1,25 @@
{
"meta": {},
"group": {
"title": "Available methods",
"anchor": "available-methods"
},
"object": {
"anchor": "setmyprofilephoto",
"name": "setMyProfilePhoto",
"description": "Changes the profile photo of the bot. Returns True on success.",
"html_description": "<p>Changes the profile photo of the bot. Returns <em>True</em> on success.</p>",
"rst_description": "Changes the profile photo of the bot. Returns :code:`True` on success.",
"annotations": [
{
"type": "InputProfilePhoto",
"required": true,
"description": "The new profile photo to set",
"html_description": "<td>The new profile photo to set</td>",
"rst_description": "The new profile photo to set\n",
"name": "photo"
}
],
"category": "methods"
}
}

View file

@ -1,7 +1,7 @@
{
"api": {
"version": "9.3",
"release_date": "2025-12-31"
"version": "9.4",
"release_date": "2026-02-09"
},
"items": [
{
@ -552,6 +552,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"
@ -1021,6 +1029,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",
@ -1479,6 +1495,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",
@ -2794,6 +2826,64 @@
],
"category": "types"
},
{
"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"
},
{
"anchor": "video",
"name": "Video",
@ -2865,6 +2955,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",
@ -5184,6 +5282,32 @@
],
"category": "types"
},
{
"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"
},
{
"anchor": "file",
"name": "File",
@ -5305,18 +5429,34 @@
{
"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.",
@ -5597,9 +5737,9 @@
{
"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",
@ -5609,6 +5749,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.",
@ -7884,11 +8040,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"
@ -8132,6 +8296,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",
@ -9193,6 +9365,42 @@
],
"category": "types"
},
{
"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"
},
{
"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"
},
{
"anchor": "userchatboosts",
"name": "UserChatBoosts",
@ -13040,6 +13248,40 @@
],
"category": "methods"
},
{
"anchor": "getuserprofileaudios",
"name": "getUserProfileAudios",
"description": "Use this method to get a list of profile audios for a user. Returns a UserProfileAudios object.",
"html_description": "<p>Use this method to get a list of profile audios for a user. Returns a <a href=\"#userprofileaudios\">UserProfileAudios</a> object.</p>",
"rst_description": "Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.",
"annotations": [
{
"type": "Integer",
"required": true,
"description": "Unique identifier of the target user",
"html_description": "<td>Unique identifier of the target user</td>",
"rst_description": "Unique identifier of the target user\n",
"name": "user_id"
},
{
"type": "Integer",
"required": false,
"description": "Sequential number of the first audio to be returned. By default, all audios are returned.",
"html_description": "<td>Sequential number of the first audio to be returned. By default, all audios are returned.</td>",
"rst_description": "Sequential number of the first audio to be returned. By default, all audios are returned.\n",
"name": "offset"
},
{
"type": "Integer",
"required": false,
"description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.",
"html_description": "<td>Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.</td>",
"rst_description": "Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.\n",
"name": "limit"
}
],
"category": "methods"
},
{
"anchor": "setuseremojistatus",
"name": "setUserEmojiStatus",
@ -14116,9 +14358,9 @@
{
"anchor": "createforumtopic",
"name": "createForumTopic",
"description": "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. Returns information about the created topic as a ForumTopic object.",
"html_description": "<p>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 <em>can_manage_topics</em> administrator rights. Returns information about the created topic as a <a href=\"#forumtopic\">ForumTopic</a> object.</p>",
"rst_description": "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. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.",
"description": "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.",
"html_description": "<p>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 <em>can_manage_topics</em> administrator right. Returns information about the created topic as a <a href=\"#forumtopic\">ForumTopic</a> object.</p>",
"rst_description": "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 :class:`aiogram.types.forum_topic.ForumTopic` object.",
"annotations": [
{
"type": "Integer or String",
@ -14729,6 +14971,33 @@
],
"category": "methods"
},
{
"anchor": "setmyprofilephoto",
"name": "setMyProfilePhoto",
"description": "Changes the profile photo of the bot. Returns True on success.",
"html_description": "<p>Changes the profile photo of the bot. Returns <em>True</em> on success.</p>",
"rst_description": "Changes the profile photo of the bot. Returns :code:`True` on success.",
"annotations": [
{
"type": "InputProfilePhoto",
"required": true,
"description": "The new profile photo to set",
"html_description": "<td>The new profile photo to set</td>",
"rst_description": "The new profile photo to set\n",
"name": "photo"
}
],
"category": "methods"
},
{
"anchor": "removemyprofilephoto",
"name": "removeMyProfilePhoto",
"description": "Removes the profile photo of the bot. Requires no parameters. Returns True on success.",
"html_description": "<p>Removes the profile photo of the bot. Requires no parameters. Returns <em>True</em> on success.</p>",
"rst_description": "Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.",
"annotations": [],
"category": "methods"
},
{
"anchor": "setchatmenubutton",
"name": "setChatMenuButton",
@ -22596,9 +22865,9 @@
{
"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

@ -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"
}
}

35
CHANGES/1761.feature.rst Normal file
View file

@ -0,0 +1,35 @@
Updated to Bot API 9.4 (February 9, 2026)
**New Features:**
- Bots with Premium subscriptions can now use custom emoji directly in messages to private, group, and supergroup chats
- Bots can create topics in private chats via the :class:`aiogram.methods.create_forum_topic.CreateForumTopic` method
- Bots can prevent users from creating/deleting topics in private chats through BotFather settings
**New Fields:**
- Added :code:`allows_users_to_create_topics` field to :class:`aiogram.types.user.User` class - indicates whether the user allows others to create topics in chats with them
- Added :code:`icon_custom_emoji_id` field to :class:`aiogram.types.keyboard_button.KeyboardButton` and :class:`aiogram.types.inline_keyboard_button.InlineKeyboardButton` classes - allows displaying custom emoji icons on buttons
- Added :code:`style` field to :class:`aiogram.types.keyboard_button.KeyboardButton` and :class:`aiogram.types.inline_keyboard_button.InlineKeyboardButton` classes - changes button color/style
- Added :code:`chat_owner_left` field to :class:`aiogram.types.message.Message` class - service message indicating chat owner has left (type: :class:`aiogram.types.chat_owner_left.ChatOwnerLeft`)
- Added :code:`chat_owner_changed` field to :class:`aiogram.types.message.Message` class - service message indicating chat ownership has transferred (type: :class:`aiogram.types.chat_owner_changed.ChatOwnerChanged`)
- Added :code:`qualities` field to :class:`aiogram.types.video.Video` class - list of available video quality options (type: :code:`list[`:class:`aiogram.types.video_quality.VideoQuality`:code:`]`)
- Added :code:`first_profile_audio` field to :class:`aiogram.types.chat_full_info.ChatFullInfo` class - user's first profile audio
- Added :code:`rarity` field to :class:`aiogram.types.unique_gift_model.UniqueGiftModel` class
- Added :code:`is_burned` field to :class:`aiogram.types.unique_gift.UniqueGift` class
**New Methods:**
- Added :class:`aiogram.methods.set_my_profile_photo.SetMyProfilePhoto` method - allows bots to set their profile photo
- Added :class:`aiogram.methods.remove_my_profile_photo.RemoveMyProfilePhoto` method - allows bots to remove their profile photo
- Added :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` method - retrieves a user's profile audio list
- Added :meth:`aiogram.types.user.User.get_profile_audios` shortcut - creates a prefilled :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios` request with :code:`user_id`
**New Types:**
- Added :class:`aiogram.types.chat_owner_left.ChatOwnerLeft` type - describes a service message about the chat owner leaving the chat
- Added :class:`aiogram.types.chat_owner_changed.ChatOwnerChanged` type - describes a service message about an ownership change in the chat
- Added :class:`aiogram.types.video_quality.VideoQuality` type - describes available video quality options
- Added :class:`aiogram.types.user_profile_audios.UserProfileAudios` type - represents the collection of audios displayed on a user's profile
Source: https://core.telegram.org/bots/api-changelog#february-9-2026

View file

@ -37,7 +37,7 @@ install: clean
.PHONY: lint
lint:
uv run ruff format --check --diff $(code_dir)
uv run ruff format --check --diff $(package_dir)
uv run ruff check --show-fixes --preview $(package_dir) $(examples_dir)
uv run mypy $(package_dir)

View file

@ -52,7 +52,7 @@ Features
- Asynchronous (`asyncio docs <https://docs.python.org/3/library/asyncio.html>`_, :pep:`492`)
- Has type hints (:pep:`484`) and can be used with `mypy <http://mypy-lang.org/>`_
- Supports `PyPy <https://www.pypy.org/>`_
- Supports `Telegram Bot API 9.3 <https://core.telegram.org/bots/api>`_ and gets fast updates to the latest versions of the Bot API
- Supports `Telegram Bot API 9.4 <https://core.telegram.org/bots/api>`_ and gets fast updates to the latest versions of the Bot API
- Telegram Bot API integration code was `autogenerated <https://github.com/aiogram/tg-codegen>`_ and can be easily re-generated when API gets updated
- Updates router (Blueprints)
- Has Finite State Machine

View file

@ -1,2 +1,2 @@
__version__ = "3.24.0"
__api_version__ = "9.3"
__version__ = "3.25.0"
__api_version__ = "9.4"

View file

@ -92,6 +92,7 @@ from ..methods import (
GetUpdates,
GetUserChatBoosts,
GetUserGifts,
GetUserProfileAudios,
GetUserProfilePhotos,
GetWebhookInfo,
GiftPremiumSubscription,
@ -105,6 +106,7 @@ from ..methods import (
RefundStarPayment,
RemoveBusinessAccountProfilePhoto,
RemoveChatVerification,
RemoveMyProfilePhoto,
RemoveUserVerification,
ReopenForumTopic,
ReopenGeneralForumTopic,
@ -154,6 +156,7 @@ from ..methods import (
SetMyDefaultAdministratorRights,
SetMyDescription,
SetMyName,
SetMyProfilePhoto,
SetMyShortDescription,
SetPassportDataErrors,
SetStickerEmojiList,
@ -241,6 +244,7 @@ from ..types import (
Update,
User,
UserChatBoosts,
UserProfileAudios,
UserProfilePhotos,
WebhookInfo,
)
@ -911,7 +915,7 @@ class Bot:
request_timeout: int | None = None,
) -> 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. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.
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 :class:`aiogram.types.forum_topic.ForumTopic` object.
Source: https://core.telegram.org/bots/api#createforumtopic
@ -5842,3 +5846,65 @@ class Bot:
entities=entities,
)
return await self(call, request_timeout=request_timeout)
async def get_user_profile_audios(
self,
user_id: int,
offset: int | None = None,
limit: int | None = None,
request_timeout: int | None = None,
) -> UserProfileAudios:
"""
Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.
Source: https://core.telegram.org/bots/api#getuserprofileaudios
:param user_id: Unique identifier of the target user
:param offset: Sequential number of the first audio to be returned. By default, all audios are returned.
:param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.
:param request_timeout: Request timeout
:return: Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.
"""
call = GetUserProfileAudios(
user_id=user_id,
offset=offset,
limit=limit,
)
return await self(call, request_timeout=request_timeout)
async def remove_my_profile_photo(
self,
request_timeout: int | None = None,
) -> bool:
"""
Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#removemyprofilephoto
:param request_timeout: Request timeout
:return: Returns :code:`True` on success.
"""
call = RemoveMyProfilePhoto()
return await self(call, request_timeout=request_timeout)
async def set_my_profile_photo(
self,
photo: InputProfilePhotoUnion,
request_timeout: int | None = None,
) -> bool:
"""
Changes the profile photo of the bot. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#setmyprofilephoto
:param photo: The new profile photo to set
:param request_timeout: Request timeout
:return: Returns :code:`True` on success.
"""
call = SetMyProfilePhoto(
photo=photo,
)
return await self(call, request_timeout=request_timeout)

View file

@ -1,4 +1,5 @@
from .bot_command_scope_type import BotCommandScopeType
from .button_style import ButtonStyle
from .chat_action import ChatAction
from .chat_boost_source_type import ChatBoostSourceType
from .chat_member_status import ChatMemberStatus
@ -36,6 +37,7 @@ from .update_type import UpdateType
__all__ = (
"BotCommandScopeType",
"ButtonStyle",
"ChatAction",
"ChatBoostSourceType",
"ChatMemberStatus",

View file

@ -0,0 +1,15 @@
from enum import Enum
class ButtonStyle(str, Enum):
"""
This object represents a button style (inline- or reply-keyboard).
Sources:
* https://core.telegram.org/bots/api#inlinekeyboardbutton
* https://core.telegram.org/bots/api#keyboardbutton
"""
DANGER = "danger"
SUCCESS = "success"
PRIMARY = "primary"

View file

@ -28,6 +28,8 @@ class ContentType(str, Enum):
LOCATION = "location"
NEW_CHAT_MEMBERS = "new_chat_members"
LEFT_CHAT_MEMBER = "left_chat_member"
CHAT_OWNER_LEFT = "chat_owner_left"
CHAT_OWNER_CHANGED = "chat_owner_changed"
NEW_CHAT_TITLE = "new_chat_title"
NEW_CHAT_PHOTO = "new_chat_photo"
DELETE_CHAT_PHOTO = "delete_chat_photo"

View file

@ -74,6 +74,7 @@ from .get_sticker_set import GetStickerSet
from .get_updates import GetUpdates
from .get_user_chat_boosts import GetUserChatBoosts
from .get_user_gifts import GetUserGifts
from .get_user_profile_audios import GetUserProfileAudios
from .get_user_profile_photos import GetUserProfilePhotos
from .get_webhook_info import GetWebhookInfo
from .gift_premium_subscription import GiftPremiumSubscription
@ -87,6 +88,7 @@ from .read_business_message import ReadBusinessMessage
from .refund_star_payment import RefundStarPayment
from .remove_business_account_profile_photo import RemoveBusinessAccountProfilePhoto
from .remove_chat_verification import RemoveChatVerification
from .remove_my_profile_photo import RemoveMyProfilePhoto
from .remove_user_verification import RemoveUserVerification
from .reopen_forum_topic import ReopenForumTopic
from .reopen_general_forum_topic import ReopenGeneralForumTopic
@ -136,6 +138,7 @@ from .set_my_commands import SetMyCommands
from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights
from .set_my_description import SetMyDescription
from .set_my_name import SetMyName
from .set_my_profile_photo import SetMyProfilePhoto
from .set_my_short_description import SetMyShortDescription
from .set_passport_data_errors import SetPassportDataErrors
from .set_sticker_emoji_list import SetStickerEmojiList
@ -238,6 +241,7 @@ __all__ = (
"GetUpdates",
"GetUserChatBoosts",
"GetUserGifts",
"GetUserProfileAudios",
"GetUserProfilePhotos",
"GetWebhookInfo",
"GiftPremiumSubscription",
@ -251,6 +255,7 @@ __all__ = (
"RefundStarPayment",
"RemoveBusinessAccountProfilePhoto",
"RemoveChatVerification",
"RemoveMyProfilePhoto",
"RemoveUserVerification",
"ReopenForumTopic",
"ReopenGeneralForumTopic",
@ -302,6 +307,7 @@ __all__ = (
"SetMyDefaultAdministratorRights",
"SetMyDescription",
"SetMyName",
"SetMyProfilePhoto",
"SetMyShortDescription",
"SetPassportDataErrors",
"SetStickerEmojiList",

View file

@ -8,7 +8,7 @@ from .base import TelegramMethod
class CreateForumTopic(TelegramMethod[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. Returns information about the created topic as a :class:`aiogram.types.forum_topic.ForumTopic` object.
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 :class:`aiogram.types.forum_topic.ForumTopic` object.
Source: https://core.telegram.org/bots/api#createforumtopic
"""

View file

@ -0,0 +1,42 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..types import UserProfileAudios
from .base import TelegramMethod
class GetUserProfileAudios(TelegramMethod[UserProfileAudios]):
"""
Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.
Source: https://core.telegram.org/bots/api#getuserprofileaudios
"""
__returning__ = UserProfileAudios
__api_method__ = "getUserProfileAudios"
user_id: int
"""Unique identifier of the target user"""
offset: int | None = None
"""Sequential number of the first audio to be returned. By default, all audios are returned."""
limit: int | None = None
"""Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__,
*,
user_id: int,
offset: int | None = None,
limit: int | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(user_id=user_id, offset=offset, limit=limit, **__pydantic_kwargs)

View file

@ -0,0 +1,14 @@
from __future__ import annotations
from .base import TelegramMethod
class RemoveMyProfilePhoto(TelegramMethod[bool]):
"""
Removes the profile photo of the bot. Requires no parameters. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#removemyprofilephoto
"""
__returning__ = bool
__api_method__ = "removeMyProfilePhoto"

View file

@ -0,0 +1,33 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from ..types import InputProfilePhotoUnion
from .base import TelegramMethod
class SetMyProfilePhoto(TelegramMethod[bool]):
"""
Changes the profile photo of the bot. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#setmyprofilephoto
"""
__returning__ = bool
__api_method__ = "setMyProfilePhoto"
photo: InputProfilePhotoUnion
"""The new profile photo to set"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__, *, photo: InputProfilePhotoUnion, **__pydantic_kwargs: Any
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(photo=photo, **__pydantic_kwargs)

View file

@ -387,6 +387,8 @@ __all__ = (
"ChatMemberRestricted",
"ChatMemberUnion",
"ChatMemberUpdated",
"ChatOwnerChanged",
"ChatOwnerLeft",
"ChatPermissions",
"ChatPhoto",
"ChatShared",
@ -627,6 +629,7 @@ __all__ = (
"Update",
"User",
"UserChatBoosts",
"UserProfileAudios",
"UserProfilePhotos",
"UserRating",
"UserShared",
@ -638,6 +641,7 @@ __all__ = (
"VideoChatScheduled",
"VideoChatStarted",
"VideoNote",
"VideoQuality",
"Voice",
"WebAppData",
"WebAppInfo",
@ -646,6 +650,10 @@ __all__ = (
)
from ..client.default import Default as _Default
from .chat_owner_changed import ChatOwnerChanged
from .chat_owner_left import ChatOwnerLeft
from .user_profile_audios import UserProfileAudios
from .video_quality import VideoQuality
# Load typing forward refs for every TelegramObject
for _entity_name in __all__:

View file

@ -9,6 +9,7 @@ from .custom import DateTime
if TYPE_CHECKING:
from .accepted_gift_types import AcceptedGiftTypes
from .audio import Audio
from .birthdate import Birthdate
from .business_intro import BusinessIntro
from .business_location import BusinessLocation
@ -125,6 +126,8 @@ class ChatFullInfo(Chat):
"""*Optional*. For supergroups, the location to which the supergroup is connected"""
rating: UserRating | None = None
"""*Optional*. For private chats, the rating of the user if any"""
first_profile_audio: Audio | None = None
"""*Optional*. For private chats, the first audio added to the profile of the user"""
unique_gift_colors: UniqueGiftColors | None = None
"""*Optional*. The color scheme based on a unique gift that must be used for the chat's name, message replies and link previews"""
paid_message_star_count: int | None = None
@ -190,6 +193,7 @@ class ChatFullInfo(Chat):
linked_chat_id: int | None = None,
location: ChatLocation | None = None,
rating: UserRating | None = None,
first_profile_audio: Audio | None = None,
unique_gift_colors: UniqueGiftColors | None = None,
paid_message_star_count: int | None = None,
can_send_gift: bool | None = None,
@ -248,6 +252,7 @@ class ChatFullInfo(Chat):
linked_chat_id=linked_chat_id,
location=location,
rating=rating,
first_profile_audio=first_profile_audio,
unique_gift_colors=unique_gift_colors,
paid_message_star_count=paid_message_star_count,
can_send_gift=can_send_gift,

View file

@ -0,0 +1,30 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .base import TelegramObject
if TYPE_CHECKING:
from .user import User
class ChatOwnerChanged(TelegramObject):
"""
Describes a service message about an ownership change in the chat.
Source: https://core.telegram.org/bots/api#chatownerchanged
"""
new_owner: User
"""The new owner of the chat"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(__pydantic__self__, *, new_owner: User, **__pydantic_kwargs: Any) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(new_owner=new_owner, **__pydantic_kwargs)

View file

@ -0,0 +1,32 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .base import TelegramObject
if TYPE_CHECKING:
from .user import User
class ChatOwnerLeft(TelegramObject):
"""
Describes a service message about the chat owner leaving the chat.
Source: https://core.telegram.org/bots/api#chatownerleft
"""
new_owner: User | None = None
"""*Optional*. The user which will be the new owner of the chat if the previous owner does not return to the chat"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__, *, new_owner: User | None = None, **__pydantic_kwargs: Any
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(new_owner=new_owner, **__pydantic_kwargs)

View file

@ -15,6 +15,8 @@ class GameHighScore(TelegramObject):
If you've got any questions, please check out our `https://core.telegram.org/bots/faq <https://core.telegram.org/bots/faq>`_ **Bot FAQ »**
-
Source: https://core.telegram.org/bots/api#gamehighscore
"""

View file

@ -14,13 +14,17 @@ if TYPE_CHECKING:
class InlineKeyboardButton(MutableTelegramObject):
"""
This object represents one button of an inline keyboard. Exactly one of the optional fields must be used to specify type of the button.
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.
Source: https://core.telegram.org/bots/api#inlinekeyboardbutton
"""
text: str
"""Label text on the button"""
icon_custom_emoji_id: str | None = None
"""*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."""
style: str | None = None
"""*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."""
url: str | None = None
"""*Optional*. HTTP or tg:// URL to be opened when the button is pressed. Links :code:`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."""
callback_data: str | None = None
@ -50,6 +54,8 @@ class InlineKeyboardButton(MutableTelegramObject):
__pydantic__self__,
*,
text: str,
icon_custom_emoji_id: str | None = None,
style: str | None = None,
url: str | None = None,
callback_data: str | None = None,
web_app: WebAppInfo | None = None,
@ -68,6 +74,8 @@ class InlineKeyboardButton(MutableTelegramObject):
super().__init__(
text=text,
icon_custom_emoji_id=icon_custom_emoji_id,
style=style,
url=url,
callback_data=callback_data,
web_app=web_app,

View file

@ -16,14 +16,17 @@ if TYPE_CHECKING:
class KeyboardButton(MutableTelegramObject):
"""
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.
**Note:** *request_users* and *request_chat* options will only work in Telegram versions released after 3 February, 2023. Older clients will display *unsupported message*.
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.
Source: https://core.telegram.org/bots/api#keyboardbutton
"""
text: str
"""Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed"""
"""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"""
icon_custom_emoji_id: str | None = None
"""*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."""
style: str | None = None
"""*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."""
request_users: KeyboardButtonRequestUsers | None = None
"""*Optional*. 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."""
request_chat: KeyboardButtonRequestChat | None = None
@ -52,6 +55,8 @@ class KeyboardButton(MutableTelegramObject):
__pydantic__self__,
*,
text: str,
icon_custom_emoji_id: str | None = None,
style: str | None = None,
request_users: KeyboardButtonRequestUsers | None = None,
request_chat: KeyboardButtonRequestChat | None = None,
request_contact: bool | None = None,
@ -67,6 +72,8 @@ class KeyboardButton(MutableTelegramObject):
super().__init__(
text=text,
icon_custom_emoji_id=icon_custom_emoji_id,
style=style,
request_users=request_users,
request_chat=request_chat,
request_contact=request_contact,

View file

@ -55,6 +55,8 @@ if TYPE_CHECKING:
from .chat_background import ChatBackground
from .chat_boost_added import ChatBoostAdded
from .chat_id_union import ChatIdUnion
from .chat_owner_changed import ChatOwnerChanged
from .chat_owner_left import ChatOwnerLeft
from .chat_shared import ChatShared
from .checklist import Checklist
from .checklist_tasks_added import ChecklistTasksAdded
@ -245,6 +247,10 @@ class Message(MaybeInaccessibleMessage):
"""*Optional*. New members that were added to the group or supergroup and information about them (the bot itself may be one of these members)"""
left_chat_member: User | None = None
"""*Optional*. A member was removed from the group, information about them (this member may be the bot itself)"""
chat_owner_left: ChatOwnerLeft | None = None
"""*Optional*. Service message: chat owner has left"""
chat_owner_changed: ChatOwnerChanged | None = None
"""*Optional*. Service message: chat owner has changed"""
new_chat_title: str | None = None
"""*Optional*. A chat title was changed to this value"""
new_chat_photo: list[PhotoSize] | None = None
@ -440,6 +446,8 @@ class Message(MaybeInaccessibleMessage):
location: Location | None = None,
new_chat_members: list[User] | None = None,
left_chat_member: User | None = None,
chat_owner_left: ChatOwnerLeft | None = None,
chat_owner_changed: ChatOwnerChanged | None = None,
new_chat_title: str | None = None,
new_chat_photo: list[PhotoSize] | None = None,
delete_chat_photo: bool | None = None,
@ -557,6 +565,8 @@ class Message(MaybeInaccessibleMessage):
location=location,
new_chat_members=new_chat_members,
left_chat_member=left_chat_member,
chat_owner_left=chat_owner_left,
chat_owner_changed=chat_owner_changed,
new_chat_title=new_chat_title,
new_chat_photo=new_chat_photo,
delete_chat_photo=delete_chat_photo,
@ -650,6 +660,10 @@ class Message(MaybeInaccessibleMessage):
return ContentType.NEW_CHAT_MEMBERS
if self.left_chat_member:
return ContentType.LEFT_CHAT_MEMBER
if self.chat_owner_left:
return ContentType.CHAT_OWNER_LEFT
if self.chat_owner_changed:
return ContentType.CHAT_OWNER_CHANGED
if self.invoice:
return ContentType.INVOICE
if self.successful_payment:

View file

@ -35,6 +35,8 @@ class UniqueGift(TelegramObject):
"""Backdrop of the gift"""
is_premium: bool | None = None
"""*Optional*. :code:`True`, if the original regular gift was exclusively purchaseable by Telegram Premium subscribers"""
is_burned: bool | None = None
"""*Optional*. :code:`True`, if the gift was used to craft another gift and isn't available anymore"""
is_from_blockchain: bool | None = None
"""*Optional*. :code:`True`, if the gift is assigned from the TON blockchain and can't be resold or transferred in Telegram"""
colors: UniqueGiftColors | None = None
@ -57,6 +59,7 @@ class UniqueGift(TelegramObject):
symbol: UniqueGiftSymbol,
backdrop: UniqueGiftBackdrop,
is_premium: bool | None = None,
is_burned: bool | None = None,
is_from_blockchain: bool | None = None,
colors: UniqueGiftColors | None = None,
publisher_chat: Chat | None = None,
@ -75,6 +78,7 @@ class UniqueGift(TelegramObject):
symbol=symbol,
backdrop=backdrop,
is_premium=is_premium,
is_burned=is_burned,
is_from_blockchain=is_from_blockchain,
colors=colors,
publisher_chat=publisher_chat,

View file

@ -20,7 +20,9 @@ class UniqueGiftModel(TelegramObject):
sticker: Sticker
"""The sticker that represents the unique gift"""
rarity_per_mille: int
"""The number of unique gifts that receive this model for every 1000 gifts upgraded"""
"""The number of unique gifts that receive this model for every 1000 gift upgrades. Always 0 for crafted gifts."""
rarity: str | None = None
"""*Optional*. Rarity of the model if it is a crafted model. Currently, can be 'uncommon', 'rare', 'epic', or 'legendary'."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -32,6 +34,7 @@ class UniqueGiftModel(TelegramObject):
name: str,
sticker: Sticker,
rarity_per_mille: int,
rarity: str | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -39,5 +42,9 @@ class UniqueGiftModel(TelegramObject):
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(
name=name, sticker=sticker, rarity_per_mille=rarity_per_mille, **__pydantic_kwargs
name=name,
sticker=sticker,
rarity_per_mille=rarity_per_mille,
rarity=rarity,
**__pydantic_kwargs,
)

View file

@ -7,7 +7,7 @@ from ..utils.link import create_tg_link
from .base import TelegramObject
if TYPE_CHECKING:
from ..methods import GetUserProfilePhotos
from ..methods import GetUserProfileAudios, GetUserProfilePhotos
class User(TelegramObject):
@ -45,6 +45,8 @@ class User(TelegramObject):
"""*Optional*. :code:`True`, if the bot has a main Web App. Returned only in :class:`aiogram.methods.get_me.GetMe`."""
has_topics_enabled: bool | None = None
"""*Optional*. :code:`True`, if the bot has forum topic mode enabled in private chats. Returned only in :class:`aiogram.methods.get_me.GetMe`."""
allows_users_to_create_topics: bool | None = None
"""*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`."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -67,6 +69,7 @@ class User(TelegramObject):
can_connect_to_business: bool | None = None,
has_main_web_app: bool | None = None,
has_topics_enabled: bool | None = None,
allows_users_to_create_topics: bool | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -88,6 +91,7 @@ class User(TelegramObject):
can_connect_to_business=can_connect_to_business,
has_main_web_app=has_main_web_app,
has_topics_enabled=has_topics_enabled,
allows_users_to_create_topics=allows_users_to_create_topics,
**__pydantic_kwargs,
)
@ -142,3 +146,35 @@ class User(TelegramObject):
limit=limit,
**kwargs,
).as_(self._bot)
def get_profile_audios(
self,
offset: int | None = None,
limit: int | None = None,
**kwargs: Any,
) -> GetUserProfileAudios:
"""
Shortcut for method :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios`
will automatically fill method attributes:
- :code:`user_id`
Use this method to get a list of profile audios for a user. Returns a :class:`aiogram.types.user_profile_audios.UserProfileAudios` object.
Source: https://core.telegram.org/bots/api#getuserprofileaudios
:param offset: Sequential number of the first audio to be returned. By default, all audios are returned.
:param limit: Limits the number of audios to be retrieved. Values between 1-100 are accepted. Defaults to 100.
:return: instance of method :class:`aiogram.methods.get_user_profile_audios.GetUserProfileAudios`
"""
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
from aiogram.methods import GetUserProfileAudios
return GetUserProfileAudios(
user_id=self.id,
offset=offset,
limit=limit,
**kwargs,
).as_(self._bot)

View file

@ -0,0 +1,34 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .base import TelegramObject
if TYPE_CHECKING:
from .audio import Audio
class UserProfileAudios(TelegramObject):
"""
This object represents the audios displayed on a user's profile.
Source: https://core.telegram.org/bots/api#userprofileaudios
"""
total_count: int
"""Total number of profile audios for the target user"""
audios: list[Audio]
"""Requested profile audios"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__, *, total_count: int, audios: list[Audio], **__pydantic_kwargs: Any
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(total_count=total_count, audios=audios, **__pydantic_kwargs)

View file

@ -7,6 +7,7 @@ from .base import TelegramObject
if TYPE_CHECKING:
from .photo_size import PhotoSize
from .video_quality import VideoQuality
class Video(TelegramObject):
@ -32,6 +33,8 @@ class Video(TelegramObject):
"""*Optional*. Available sizes of the cover of the video in the message"""
start_timestamp: datetime.datetime | None = None
"""*Optional*. Timestamp in seconds from which the video will play in the message"""
qualities: list[VideoQuality] | None = None
"""*Optional*. List of available qualities of the video"""
file_name: str | None = None
"""*Optional*. Original filename as defined by the sender"""
mime_type: str | None = None
@ -54,6 +57,7 @@ class Video(TelegramObject):
thumbnail: PhotoSize | None = None,
cover: list[PhotoSize] | None = None,
start_timestamp: datetime.datetime | None = None,
qualities: list[VideoQuality] | None = None,
file_name: str | None = None,
mime_type: str | None = None,
file_size: int | None = None,
@ -72,6 +76,7 @@ class Video(TelegramObject):
thumbnail=thumbnail,
cover=cover,
start_timestamp=start_timestamp,
qualities=qualities,
file_name=file_name,
mime_type=mime_type,
file_size=file_size,

View file

@ -0,0 +1,55 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any
from .base import TelegramObject
class VideoQuality(TelegramObject):
"""
This object represents a video file of a specific quality.
Source: https://core.telegram.org/bots/api#videoquality
"""
file_id: str
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""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."""
width: int
"""Video width"""
height: int
"""Video height"""
codec: str
"""Codec that was used to encode the video, for example, 'h264', 'h265', or 'av01'"""
file_size: int | None = None
"""*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."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__,
*,
file_id: str,
file_unique_id: str,
width: int,
height: int,
codec: str,
file_size: int | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(
file_id=file_id,
file_unique_id=file_unique_id,
width=width,
height=height,
codec=codec,
file_size=file_size,
**__pydantic_kwargs,
)

View file

@ -0,0 +1,9 @@
###########
ButtonStyle
###########
.. automodule:: aiogram.enums.button_style
:members:
:member-order: bysource
:undoc-members: True

View file

@ -11,6 +11,7 @@ Here is list of all available enums:
:maxdepth: 1
bot_command_scope_type
button_style
chat_action
chat_boost_source_type
chat_member_status

View file

@ -0,0 +1,38 @@
####################
getUserProfileAudios
####################
Returns: :obj:`UserProfileAudios`
.. automodule:: aiogram.methods.get_user_profile_audios
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields
Usage
=====
As bot method
-------------
.. code-block::
result: UserProfileAudios = await bot.get_user_profile_audios(...)
Method as object
----------------
Imports:
- :code:`from aiogram.methods.get_user_profile_audios import GetUserProfileAudios`
- alias: :code:`from aiogram.methods import GetUserProfileAudios`
With specific bot
~~~~~~~~~~~~~~~~~
.. code-block:: python
result: UserProfileAudios = await bot(GetUserProfileAudios(...))

View file

@ -82,6 +82,7 @@ Available methods
get_my_short_description
get_user_chat_boosts
get_user_gifts
get_user_profile_audios
get_user_profile_photos
gift_premium_subscription
hide_general_forum_topic
@ -93,6 +94,7 @@ Available methods
read_business_message
remove_business_account_profile_photo
remove_chat_verification
remove_my_profile_photo
remove_user_verification
reopen_forum_topic
reopen_general_forum_topic
@ -135,6 +137,7 @@ Available methods
set_my_default_administrator_rights
set_my_description
set_my_name
set_my_profile_photo
set_my_short_description
set_user_emoji_status
transfer_business_account_stars

View file

@ -0,0 +1,45 @@
####################
removeMyProfilePhoto
####################
Returns: :obj:`bool`
.. automodule:: aiogram.methods.remove_my_profile_photo
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields
Usage
=====
As bot method
-------------
.. code-block::
result: bool = await bot.remove_my_profile_photo(...)
Method as object
----------------
Imports:
- :code:`from aiogram.methods.remove_my_profile_photo import RemoveMyProfilePhoto`
- alias: :code:`from aiogram.methods import RemoveMyProfilePhoto`
With specific bot
~~~~~~~~~~~~~~~~~
.. code-block:: python
result: bool = await bot(RemoveMyProfilePhoto(...))
As reply into Webhook in handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
return RemoveMyProfilePhoto(...)

View file

@ -0,0 +1,45 @@
#################
setMyProfilePhoto
#################
Returns: :obj:`bool`
.. automodule:: aiogram.methods.set_my_profile_photo
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields
Usage
=====
As bot method
-------------
.. code-block::
result: bool = await bot.set_my_profile_photo(...)
Method as object
----------------
Imports:
- :code:`from aiogram.methods.set_my_profile_photo import SetMyProfilePhoto`
- alias: :code:`from aiogram.methods import SetMyProfilePhoto`
With specific bot
~~~~~~~~~~~~~~~~~
.. code-block:: python
result: bool = await bot(SetMyProfilePhoto(...))
As reply into Webhook in handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code-block:: python
return SetMyProfilePhoto(...)

View file

@ -0,0 +1,10 @@
################
ChatOwnerChanged
################
.. automodule:: aiogram.types.chat_owner_changed
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields

View file

@ -0,0 +1,10 @@
#############
ChatOwnerLeft
#############
.. automodule:: aiogram.types.chat_owner_left
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields

View file

@ -67,6 +67,8 @@ Available types
chat_member_owner
chat_member_restricted
chat_member_updated
chat_owner_changed
chat_owner_left
chat_permissions
chat_photo
chat_shared
@ -199,6 +201,7 @@ Available types
unique_gift_symbol
user
user_chat_boosts
user_profile_audios
user_profile_photos
user_rating
user_shared
@ -210,6 +213,7 @@ Available types
video_chat_scheduled
video_chat_started
video_note
video_quality
voice
web_app_data
web_app_info

View file

@ -0,0 +1,10 @@
#################
UserProfileAudios
#################
.. automodule:: aiogram.types.user_profile_audios
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields

View file

@ -0,0 +1,10 @@
############
VideoQuality
############
.. automodule:: aiogram.types.video_quality
:members:
:member-order: bysource
:undoc-members: True
:exclude-members: model_config,model_fields

View file

@ -45,9 +45,9 @@ def bump_version(part: str) -> str:
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python scripts/bump_version.py [major|minor|patch|to:X.Y.Z]")
if len(sys.argv) != 2: # noqa: PLR2004
print("Usage: python scripts/bump_version.py [major|minor|patch|to:X.Y.Z]") # noqa: T201
sys.exit(1)
new_version = bump_version(sys.argv[1])
print(f"Bumped version to {new_version}")
print(f"Bumped version to {new_version}") # noqa: T201

View file

@ -45,8 +45,7 @@ def get_telegram_api_version() -> str:
def replace_line(content: str, pattern: re.Pattern, new_value: str) -> str:
result = pattern.sub(f"\\g<1>{new_value}\\g<2>", content)
return result
return pattern.sub(f"\\g<1>{new_value}\\g<2>", content)
def write_package_meta(api_version: str) -> None:
@ -55,7 +54,7 @@ def write_package_meta(api_version: str) -> None:
content = replace_line(content, API_VERSION, api_version)
print(f"Write {path}")
print(f"Write {path}") # noqa: T201
path.write_text(content)
@ -64,7 +63,7 @@ def write_readme(api_version: str) -> None:
content = path.read_text()
content = replace_line(content, API_VERSION_BADGE, api_version)
content = replace_line(content, API_VERSION_LINE, api_version)
print(f"Write {path}")
print(f"Write {path}") # noqa: T201
path.write_text(content)
@ -72,14 +71,14 @@ def write_docs_index(api_version: str) -> None:
path = Path.cwd() / "docs" / "index.rst"
content = path.read_text()
content = replace_line(content, API_VERSION_BADGE, api_version)
print(f"Write {path}")
print(f"Write {path}") # noqa: T201
path.write_text(content)
def main():
api_version = get_telegram_api_version()
print(f"Telegram Bot API version: {api_version}")
print(f"Telegram Bot API version: {api_version}") # noqa: T201
write_package_meta(api_version=api_version)
write_readme(api_version=api_version)
write_docs_index(api_version=api_version)

View file

@ -0,0 +1,19 @@
from aiogram.methods import GetUserProfileAudios
from aiogram.types import Audio, UserProfileAudios
from tests.mocked_bot import MockedBot
class TestGetUserProfileAudios:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(
GetUserProfileAudios,
ok=True,
result=UserProfileAudios(
total_count=1,
audios=[Audio(file_id="file_id", file_unique_id="file_unique_id", duration=120)],
),
)
response: UserProfileAudios = await bot.get_user_profile_audios(user_id=42)
bot.get_request()
assert response == prepare_result.result

View file

@ -0,0 +1,11 @@
from aiogram.methods import RemoveMyProfilePhoto
from tests.mocked_bot import MockedBot
class TestRemoveMyProfilePhoto:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(RemoveMyProfilePhoto, ok=True, result=True)
response: bool = await bot.remove_my_profile_photo()
bot.get_request()
assert response == prepare_result.result

View file

@ -0,0 +1,14 @@
from aiogram.methods import SetMyProfilePhoto
from aiogram.types import InputProfilePhotoStatic
from tests.mocked_bot import MockedBot
class TestSetMyProfilePhoto:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(SetMyProfilePhoto, ok=True, result=True)
response: bool = await bot.set_my_profile_photo(
photo=InputProfilePhotoStatic(photo="file_id")
)
bot.get_request()
assert response == prepare_result.result

View file

@ -46,6 +46,8 @@ from aiogram.types import (
Chat,
ChatBackground,
ChatBoostAdded,
ChatOwnerChanged,
ChatOwnerLeft,
ChatShared,
Checklist,
ChecklistTask,
@ -253,6 +255,31 @@ TEST_MESSAGE_LEFT_CHAT_MEMBER = Message(
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
)
TEST_MESSAGE_CHAT_OWNER_LEFT = Message(
message_id=42,
date=datetime.datetime.now(),
chat_owner_left=ChatOwnerLeft(
new_owner=User(id=43, is_bot=False, first_name="NewOwner"),
),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
)
TEST_MESSAGE_CHAT_OWNER_LEFT_NO_SUCCESSOR = Message(
message_id=42,
date=datetime.datetime.now(),
chat_owner_left=ChatOwnerLeft(),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
)
TEST_MESSAGE_CHAT_OWNER_CHANGED = Message(
message_id=42,
date=datetime.datetime.now(),
chat_owner_changed=ChatOwnerChanged(
new_owner=User(id=43, is_bot=False, first_name="NewOwner"),
),
chat=Chat(id=42, type="private"),
from_user=User(id=42, is_bot=False, first_name="Test"),
)
TEST_MESSAGE_INVOICE = Message(
message_id=42,
date=datetime.datetime.now(),
@ -849,6 +876,8 @@ MESSAGES_AND_CONTENT_TYPES = [
[TEST_MESSAGE_LOCATION, ContentType.LOCATION],
[TEST_MESSAGE_NEW_CHAT_MEMBERS, ContentType.NEW_CHAT_MEMBERS],
[TEST_MESSAGE_LEFT_CHAT_MEMBER, ContentType.LEFT_CHAT_MEMBER],
[TEST_MESSAGE_CHAT_OWNER_LEFT, ContentType.CHAT_OWNER_LEFT],
[TEST_MESSAGE_CHAT_OWNER_CHANGED, ContentType.CHAT_OWNER_CHANGED],
[TEST_MESSAGE_INVOICE, ContentType.INVOICE],
[TEST_MESSAGE_SUCCESSFUL_PAYMENT, ContentType.SUCCESSFUL_PAYMENT],
[TEST_MESSAGE_CONNECTED_WEBSITE, ContentType.CONNECTED_WEBSITE],
@ -930,6 +959,8 @@ MESSAGES_AND_COPY_METHODS = [
[TEST_MESSAGE_STORY, ForwardMessage],
[TEST_MESSAGE_NEW_CHAT_MEMBERS, None],
[TEST_MESSAGE_LEFT_CHAT_MEMBER, None],
[TEST_MESSAGE_CHAT_OWNER_LEFT, None],
[TEST_MESSAGE_CHAT_OWNER_CHANGED, None],
[TEST_MESSAGE_INVOICE, None],
[TEST_MESSAGE_SUCCESSFUL_PAYMENT, None],
[TEST_MESSAGE_CONNECTED_WEBSITE, None],
@ -1023,6 +1054,11 @@ class TestMessage:
def test_content_type(self, message: Message, content_type: str):
assert message.content_type == content_type
def test_chat_owner_left_no_successor(self):
assert (
TEST_MESSAGE_CHAT_OWNER_LEFT_NO_SUCCESSOR.content_type == ContentType.CHAT_OWNER_LEFT
)
def test_as_reply_parameters(self):
message = Message(
message_id=42, chat=Chat(id=42, type="private"), date=datetime.datetime.now()

View file

@ -56,3 +56,9 @@ class TestUser:
method = user.get_profile_photos(description="test")
assert method.user_id == user.id
def test_get_profile_audios(self):
user = User(id=42, is_bot=False, first_name="Test", last_name="User")
method = user.get_profile_audios(description="test")
assert method.user_id == user.id

View file

@ -0,0 +1,61 @@
import pytest
from aiogram.types import Video, VideoQuality
@pytest.fixture()
def video_quality():
return VideoQuality(
file_id="abc123",
file_unique_id="unique123",
width=1920,
height=1080,
codec="h264",
)
class TestVideoQuality:
def test_instantiation(self, video_quality: VideoQuality):
assert video_quality.file_id == "abc123"
assert video_quality.file_unique_id == "unique123"
assert video_quality.width == 1920
assert video_quality.height == 1080
assert video_quality.codec == "h264"
assert video_quality.file_size is None
def test_instantiation_with_file_size(self):
file_size = 1048576
vq = VideoQuality(
file_id="abc123",
file_unique_id="unique123",
width=1920,
height=1080,
codec="h265",
file_size=file_size,
)
assert vq.file_size == file_size
def test_video_with_qualities(self, video_quality: VideoQuality):
file_size = 524288
video = Video(
file_id="video123",
file_unique_id="unique_video123",
width=1920,
height=1080,
duration=120,
qualities=[
video_quality,
VideoQuality(
file_id="q2",
file_unique_id="uq2",
width=1280,
height=720,
codec="h264",
file_size=file_size,
),
],
)
assert video.qualities is not None
assert len(video.qualities) == 2
assert video.qualities[0].width == 1920
assert video.qualities[1].file_size == file_size