Merge branch 'dev-3.x'

This commit is contained in:
JRoot Junior 2026-02-10 23:47:33 +02:00
commit 860dd5cab8
No known key found for this signature in database
GPG key ID: 738964250D5FF6E2
281 changed files with 2741 additions and 1029 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"
}
}

View file

@ -72,7 +72,7 @@ jobs:
run: |
uv run ruff check --output-format=github aiogram examples
uv run mypy aiogram
uv run black --check --diff aiogram tests
uv run ruff format --check --diff aiogram tests scripts examples
- name: Setup redis
if: ${{ env.IS_WINDOWS == 'false' }}

View file

@ -13,13 +13,10 @@ repos:
- id: "check-toml"
- id: "check-json"
- repo: https://github.com/psf/black
rev: 25.9.0
hooks:
- id: black
files: &files '^(aiogram|tests|examples)'
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: 'v0.14.0'
hooks:
- id: ruff
files: &files '^(aiogram|tests|examples)'
- id: ruff-format
files: *files

View file

@ -16,6 +16,88 @@ Changelog
.. towncrier release notes start
3.25.0 (2026-02-10)
====================
Features
--------
- Add full_name property to Contact and corresponding tests
`#1758 <https://github.com/aiogram/aiogram/issues/1758>`_
- Updated to `Bot API 9.4 (February 9, 2026) <https://core.telegram.org/bots/api-changelog#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
`#1761 <https://github.com/aiogram/aiogram/issues/1761>`_
Bugfixes
--------
- Fixed scene handling for ``channel_post`` and ``edited_channel_post`` when Scenes are registered but FSM state is unavailable, and added channel-scoped FSM context support for ``CHAT``/``CHAT_TOPIC`` strategies.
`#1743 <https://github.com/aiogram/aiogram/issues/1743>`_
Misc
----
- Migrated from Black and isort to Ruff for code formatting and linting, a modern, blazingly fast formatter and linter written in Rust.
Enabled additional ruff rule sets.
**For end users:**
No changes required. This is purely a development tooling change that doesn't affect the library API or behavior.
**For contributors:**
- Use ``make reformat`` or ``uv run ruff format`` to format code (replaces ``black`` and ``isort``)
- Use ``make lint`` to check code quality (now includes formatting, linting, and type checking)
- Pre-commit hooks automatically updated to use ``ruff`` and ``ruff-format``
- CI/CD pipelines updated to use ruff in GitHub Actions workflows
**Benefits:**
- 10-100x faster formatting and linting compared to Black + isort + flake8
- Single tool for formatting, import sorting, and linting
- More comprehensive code quality checks out of the box
- Auto-fixes for many common issues (33 issues auto-fixed during migration)
- Better integration with modern Python development workflows
This change improves the developer experience and code quality while maintaining the same code style standards.
`#1750 <https://github.com/aiogram/aiogram/issues/1750>`_
3.24.0 (2026-01-02)
====================

View file

@ -37,15 +37,14 @@ install: clean
.PHONY: lint
lint:
uv run isort --check-only $(code_dir)
uv run black --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)
.PHONY: reformat
reformat:
uv run black $(code_dir)
uv run isort $(code_dir)
uv run ruff format $(code_dir)
uv run ruff check --fix $(code_dir)
# =================================================================================================
# Tests

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

@ -2,12 +2,11 @@ from __future__ import annotations
import io
import pathlib
from collections.abc import AsyncGenerator, AsyncIterator
from contextlib import asynccontextmanager
from types import TracebackType
from typing import (
Any,
AsyncGenerator,
AsyncIterator,
BinaryIO,
TypeVar,
cast,
@ -93,6 +92,7 @@ from ..methods import (
GetUpdates,
GetUserChatBoosts,
GetUserGifts,
GetUserProfileAudios,
GetUserProfilePhotos,
GetWebhookInfo,
GiftPremiumSubscription,
@ -106,6 +106,7 @@ from ..methods import (
RefundStarPayment,
RemoveBusinessAccountProfilePhoto,
RemoveChatVerification,
RemoveMyProfilePhoto,
RemoveUserVerification,
ReopenForumTopic,
ReopenGeneralForumTopic,
@ -155,6 +156,7 @@ from ..methods import (
SetMyDefaultAdministratorRights,
SetMyDescription,
SetMyName,
SetMyProfilePhoto,
SetMyShortDescription,
SetPassportDataErrors,
SetStickerEmojiList,
@ -242,6 +244,7 @@ from ..types import (
Update,
User,
UserChatBoosts,
UserProfileAudios,
UserProfilePhotos,
WebhookInfo,
)
@ -283,9 +286,9 @@ class Bot:
# Few arguments are completely removed in 3.7.0 version
# Temporary solution to raise an error if user passed these arguments
# with explanation how to fix it
parse_mode = kwargs.get("parse_mode", None)
link_preview_is_disabled = kwargs.get("disable_web_page_preview", None)
protect_content = kwargs.get("protect_content", None)
parse_mode = kwargs.get("parse_mode")
link_preview_is_disabled = kwargs.get("disable_web_page_preview")
protect_content = kwargs.get("protect_content")
if (
parse_mode is not None
or link_preview_is_disabled is not None
@ -310,7 +313,7 @@ class Bot:
self.__token = token
self._me: User | None = None
async def __aenter__(self) -> "Bot":
async def __aenter__(self) -> Bot:
return self
async def __aexit__(
@ -912,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
@ -5843,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

@ -58,7 +58,7 @@ def _prepare_connector(chain_or_plain: _ProxyType) -> tuple[type[TCPConnector],
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth
if isinstance(chain_or_plain, str) or (
isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2
isinstance(chain_or_plain, tuple) and len(chain_or_plain) == 2 # noqa: PLR2004
):
chain_or_plain = cast(_ProxyBasic, chain_or_plain)
return ProxyConnector, _retrieve_basic(chain_or_plain)
@ -170,10 +170,10 @@ class AiohttpSession(BaseSession):
timeout=self.timeout if timeout is None else timeout,
) as resp:
raw_result = await resp.text()
except asyncio.TimeoutError:
raise TelegramNetworkError(method=method, message="Request timeout error")
except asyncio.TimeoutError as e:
raise TelegramNetworkError(method=method, message="Request timeout error") from e
except ClientError as e:
raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}")
raise TelegramNetworkError(method=method, message=f"{type(e).__name__}: {e}") from e
response = self.check_response(
bot=bot,
method=method,

View file

@ -90,14 +90,14 @@ class BaseSession(abc.ABC):
# in due to decoder can be customized and raise any exception
msg = "Failed to decode object"
raise ClientDecodeError(msg, e, content)
raise ClientDecodeError(msg, e, content) from e
try:
response_type = Response[method.__returning__] # type: ignore
response = response_type.model_validate(json_data, context={"bot": bot})
except ValidationError as e:
msg = "Failed to deserialize object"
raise ClientDecodeError(msg, e, json_data)
raise ClientDecodeError(msg, e, json_data) from e
if HTTPStatus.OK <= status_code <= HTTPStatus.IM_USED and response.ok:
return response

View file

@ -21,10 +21,10 @@ class FlagDecorator:
flag: Flag
@classmethod
def _with_flag(cls, flag: Flag) -> "FlagDecorator":
def _with_flag(cls, flag: Flag) -> FlagDecorator:
return cls(flag)
def _with_value(self, value: Any) -> "FlagDecorator":
def _with_value(self, value: Any) -> FlagDecorator:
new_flag = Flag(self.flag.name, value)
return self._with_flag(new_flag)
@ -33,11 +33,11 @@ class FlagDecorator:
pass
@overload
def __call__(self, value: Any, /) -> "FlagDecorator":
def __call__(self, value: Any, /) -> FlagDecorator:
pass
@overload
def __call__(self, **kwargs: Any) -> "FlagDecorator":
def __call__(self, **kwargs: Any) -> FlagDecorator:
pass
def __call__(

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

@ -37,7 +37,7 @@ class _MemberStatusMarker:
def __or__(
self,
other: _MemberStatusMarker | _MemberStatusGroupMarker,
) -> "_MemberStatusGroupMarker":
) -> _MemberStatusGroupMarker:
if isinstance(other, _MemberStatusMarker):
return _MemberStatusGroupMarker(self, other)
if isinstance(other, _MemberStatusGroupMarker):
@ -53,7 +53,7 @@ class _MemberStatusMarker:
def __rshift__(
self,
other: _MemberStatusMarker | _MemberStatusGroupMarker,
) -> "_MemberStatusTransition":
) -> _MemberStatusTransition:
old = _MemberStatusGroupMarker(self)
if isinstance(other, _MemberStatusMarker):
return _MemberStatusTransition(old=old, new=_MemberStatusGroupMarker(other))
@ -68,7 +68,7 @@ class _MemberStatusMarker:
def __lshift__(
self,
other: _MemberStatusMarker | _MemberStatusGroupMarker,
) -> "_MemberStatusTransition":
) -> _MemberStatusTransition:
new = _MemberStatusGroupMarker(self)
if isinstance(other, _MemberStatusMarker):
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=new)
@ -118,7 +118,7 @@ class _MemberStatusGroupMarker:
def __rshift__(
self,
other: _MemberStatusMarker | _MemberStatusGroupMarker,
) -> "_MemberStatusTransition":
) -> _MemberStatusTransition:
if isinstance(other, _MemberStatusMarker):
return _MemberStatusTransition(old=self, new=_MemberStatusGroupMarker(other))
if isinstance(other, _MemberStatusGroupMarker):
@ -132,7 +132,7 @@ class _MemberStatusGroupMarker:
def __lshift__(
self,
other: _MemberStatusMarker | _MemberStatusGroupMarker,
) -> "_MemberStatusTransition":
) -> _MemberStatusTransition:
if isinstance(other, _MemberStatusMarker):
return _MemberStatusTransition(old=_MemberStatusGroupMarker(other), new=self)
if isinstance(other, _MemberStatusGroupMarker):

View file

@ -123,14 +123,15 @@ class Command(Filter):
result.update(command.magic_result)
return result
def extract_command(self, text: str) -> CommandObject:
@classmethod
def extract_command(cls, text: str) -> CommandObject:
# First step: separate command with arguments
# "/command@mention arg1 arg2" -> "/command@mention", ["arg1 arg2"]
try:
full_command, *args = text.split(maxsplit=1)
except ValueError:
except ValueError as e:
msg = "not enough values to unpack"
raise CommandException(msg)
raise CommandException(msg) from e
# Separate command into valuable parts
# "/command@mention" -> "/", ("command", "@", "mention")
@ -292,6 +293,6 @@ class CommandStart(Command):
args = decode_payload(args)
except UnicodeDecodeError as e:
msg = f"Failed to decode Base64: {e}"
raise CommandException(msg)
raise CommandException(msg) from e
return replace(command, args=args)
return command

View file

@ -70,6 +70,9 @@ class FSMContextMiddleware(BaseMiddleware):
) -> FSMContext | None:
if chat_id is None:
chat_id = user_id
elif user_id is None and self.strategy in {FSMStrategy.CHAT, FSMStrategy.CHAT_TOPIC}:
# CHAT/CHAT_TOPIC are chat-scoped, so missing user_id can fallback to chat_id.
user_id = chat_id
if chat_id is not None and user_id is not None:
chat_id, user_id, thread_id = apply_strategy(

View file

@ -120,7 +120,7 @@ class ObserverDecorator:
handlers = getattr(target, "__aiogram_handler__", None)
if not handlers:
handlers = []
setattr(target, "__aiogram_handler__", handlers)
target.__aiogram_handler__ = handlers # type: ignore[union-attr]
handlers.append(
HandlerContainer(
@ -137,7 +137,7 @@ class ObserverDecorator:
action = getattr(target, "__aiogram_action__", None)
if action is None:
action = defaultdict(dict)
setattr(target, "__aiogram_action__", action)
target.__aiogram_action__ = action # type: ignore[attr-defined]
action[self.action][self.name] = CallableObject(target)
def __call__(self, target: CallbackType) -> CallbackType:
@ -248,8 +248,16 @@ class SceneHandlerWrapper:
event: TelegramObject,
**kwargs: Any,
) -> Any:
state: FSMContext = kwargs["state"]
scenes: ScenesManager = kwargs["scenes"]
try:
state: FSMContext = kwargs["state"]
scenes: ScenesManager = kwargs["scenes"]
except KeyError as error:
missing_key = error.args[0]
msg = (
f"Scene context key {missing_key!r} is not available. "
"Ensure FSM is enabled and pipeline is intact."
)
raise SceneException(msg) from None
event_update: Update = kwargs["event_update"]
scene = self.scene(
wizard=SceneWizard(
@ -768,12 +776,15 @@ class SceneRegistry:
data: dict[str, Any],
) -> Any:
assert isinstance(event, Update), "Event must be an Update instance"
state = data.get("state")
if state is None:
return await handler(event, data)
data["scenes"] = ScenesManager(
registry=self,
update_type=event.event_type,
event=event.event,
state=data["state"],
state=state,
data=data,
)
return await handler(event, data)
@ -784,12 +795,16 @@ class SceneRegistry:
event: TelegramObject,
data: dict[str, Any],
) -> Any:
state = data.get("state")
if state is None:
return await handler(event, data)
update: Update = data["event_update"]
data["scenes"] = ScenesManager(
registry=self,
update_type=update.event_type,
event=event,
state=data["state"],
state=state,
data=data,
)
return await handler(event, data)
@ -865,7 +880,7 @@ class SceneRegistry:
return self._scenes[scene]
except KeyError:
msg = f"Scene {scene!r} is not registered"
raise SceneException(msg)
raise SceneException(msg) from None
@dataclass

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

@ -1,11 +1,11 @@
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Generator
from typing import (
TYPE_CHECKING,
Any,
ClassVar,
Generator,
Generic,
TypeVar,
)

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

@ -1,4 +1,4 @@
from typing import List, Literal, Optional, Union
from typing import Literal, Optional, Union
from .accepted_gift_types import AcceptedGiftTypes
from .affiliate_info import AffiliateInfo
@ -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__:
@ -654,7 +662,7 @@ for _entity_name in __all__:
continue
_entity.model_rebuild(
_types_namespace={
"List": List,
"List": list,
"Optional": Optional,
"Union": Union,
"Literal": Literal,

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

@ -49,3 +49,9 @@ class Contact(TelegramObject):
vcard=vcard,
**__pydantic_kwargs,
)
@property
def full_name(self) -> str:
if self.last_name:
return f"{self.first_name} {self.last_name}"
return self.first_name

View file

@ -1,8 +1,8 @@
import sys
from datetime import datetime, timezone
from typing import Annotated
from pydantic import PlainSerializer
from typing_extensions import Annotated
if sys.platform == "win32": # pragma: no cover

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

@ -130,9 +130,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMessage(
chat_id=self.chat.id,
@ -208,9 +208,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMessage(
chat_id=self.chat.id,
@ -298,9 +298,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendAnimation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAnimation(
chat_id=self.chat.id,
@ -391,9 +391,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendAnimation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAnimation(
chat_id=self.chat.id,
@ -483,9 +483,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendAudio
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAudio(
chat_id=self.chat.id,
@ -571,9 +571,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendAudio
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAudio(
chat_id=self.chat.id,
@ -652,9 +652,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendContact
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendContact(
chat_id=self.chat.id,
@ -727,9 +727,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendContact
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendContact(
chat_id=self.chat.id,
@ -808,9 +808,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendDocument
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDocument(
chat_id=self.chat.id,
@ -889,9 +889,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendDocument
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDocument(
chat_id=self.chat.id,
@ -958,9 +958,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendGame
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendGame(
chat_id=self.chat.id,
@ -1018,9 +1018,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendGame
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendGame(
chat_id=self.chat.id,
@ -1122,9 +1122,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendInvoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendInvoice(
chat_id=self.chat.id,
@ -1245,9 +1245,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendInvoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendInvoice(
chat_id=self.chat.id,
@ -1342,9 +1342,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendLocation(
chat_id=self.chat.id,
@ -1423,9 +1423,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendLocation(
chat_id=self.chat.id,
@ -1492,9 +1492,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendMediaGroup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMediaGroup(
chat_id=self.chat.id,
@ -1552,9 +1552,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendMediaGroup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMediaGroup(
chat_id=self.chat.id,
@ -1628,9 +1628,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPhoto
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPhoto(
chat_id=self.chat.id,
@ -1709,9 +1709,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPhoto
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPhoto(
chat_id=self.chat.id,
@ -1804,9 +1804,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPoll
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPoll(
chat_id=self.chat.id,
@ -1903,9 +1903,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPoll
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPoll(
chat_id=self.chat.id,
@ -1982,9 +1982,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendDice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDice(
chat_id=self.chat.id,
@ -2048,9 +2048,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendDice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDice(
chat_id=self.chat.id,
@ -2118,9 +2118,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendSticker
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendSticker(
chat_id=self.chat.id,
@ -2187,9 +2187,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendSticker
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendSticker(
chat_id=self.chat.id,
@ -2270,9 +2270,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVenue
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVenue(
chat_id=self.chat.id,
@ -2357,9 +2357,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVenue
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVenue(
chat_id=self.chat.id,
@ -2456,9 +2456,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVideo
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideo(
chat_id=self.chat.id,
@ -2558,9 +2558,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVideo
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideo(
chat_id=self.chat.id,
@ -2644,9 +2644,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVideoNote
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideoNote(
chat_id=self.chat.id,
@ -2719,9 +2719,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVideoNote
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideoNote(
chat_id=self.chat.id,
@ -2798,9 +2798,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVoice(
chat_id=self.chat.id,
@ -2876,9 +2876,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendVoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVoice(
chat_id=self.chat.id,
@ -2954,9 +2954,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPaidMedia
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPaidMedia(
chat_id=self.chat.id,
@ -3031,9 +3031,9 @@ class InaccessibleMessage(MaybeInaccessibleMessage):
from aiogram.methods import SendPaidMedia
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPaidMedia(
chat_id=self.chat.id,

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

@ -3,8 +3,9 @@ from __future__ import annotations
import io
import os
from abc import ABC, abstractmethod
from collections.abc import AsyncGenerator
from pathlib import Path
from typing import TYPE_CHECKING, Any, AsyncGenerator
from typing import TYPE_CHECKING, Any
import aiofiles
@ -33,7 +34,7 @@ class InputFile(ABC):
self.chunk_size = chunk_size
@abstractmethod
async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]: # pragma: no cover
async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]: # pragma: no cover
yield b""
@ -72,7 +73,7 @@ class BufferedInputFile(InputFile):
data = f.read()
return cls(data, filename=filename, chunk_size=chunk_size)
async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]:
async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]:
buffer = io.BytesIO(self.data)
while chunk := buffer.read(self.chunk_size):
yield chunk
@ -99,7 +100,7 @@ class FSInputFile(InputFile):
self.path = path
async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]:
async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]:
async with aiofiles.open(self.path, "rb") as f:
while chunk := await f.read(self.chunk_size):
yield chunk
@ -135,7 +136,7 @@ class URLInputFile(InputFile):
self.timeout = timeout
self.bot = bot
async def read(self, bot: "Bot") -> AsyncGenerator[bytes, None]:
async def read(self, bot: Bot) -> AsyncGenerator[bytes, None]:
bot = self.bot or bot
stream = bot.session.stream_content(
url=self.url,

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:
@ -851,9 +865,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendAnimation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAnimation(
chat_id=self.chat.id,
@ -944,9 +958,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendAnimation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAnimation(
chat_id=self.chat.id,
@ -1032,9 +1046,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendAudio
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAudio(
chat_id=self.chat.id,
@ -1120,9 +1134,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendAudio
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendAudio(
chat_id=self.chat.id,
@ -1197,9 +1211,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendContact
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendContact(
chat_id=self.chat.id,
@ -1272,9 +1286,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendContact
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendContact(
chat_id=self.chat.id,
@ -1349,9 +1363,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendDocument
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDocument(
chat_id=self.chat.id,
@ -1430,9 +1444,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendDocument
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDocument(
chat_id=self.chat.id,
@ -1495,9 +1509,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendGame
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendGame(
chat_id=self.chat.id,
@ -1555,9 +1569,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendGame
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendGame(
chat_id=self.chat.id,
@ -1657,9 +1671,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendInvoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendInvoice(
chat_id=self.chat.id,
@ -1783,9 +1797,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendInvoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendInvoice(
chat_id=self.chat.id,
@ -1877,9 +1891,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendLocation(
chat_id=self.chat.id,
@ -1958,9 +1972,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendLocation(
chat_id=self.chat.id,
@ -2023,9 +2037,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendMediaGroup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMediaGroup(
chat_id=self.chat.id,
@ -2083,9 +2097,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendMediaGroup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMediaGroup(
chat_id=self.chat.id,
@ -2153,9 +2167,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMessage(
chat_id=self.chat.id,
@ -2231,9 +2245,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendMessage(
chat_id=self.chat.id,
@ -2309,9 +2323,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPhoto
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPhoto(
chat_id=self.chat.id,
@ -2390,9 +2404,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPhoto
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPhoto(
chat_id=self.chat.id,
@ -2481,9 +2495,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPoll
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPoll(
chat_id=self.chat.id,
@ -2580,9 +2594,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPoll
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPoll(
chat_id=self.chat.id,
@ -2655,9 +2669,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendDice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDice(
chat_id=self.chat.id,
@ -2721,9 +2735,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendDice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendDice(
chat_id=self.chat.id,
@ -2787,9 +2801,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendSticker
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendSticker(
chat_id=self.chat.id,
@ -2856,9 +2870,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendSticker
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendSticker(
chat_id=self.chat.id,
@ -2935,9 +2949,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVenue
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVenue(
chat_id=self.chat.id,
@ -3022,9 +3036,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVenue
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVenue(
chat_id=self.chat.id,
@ -3117,9 +3131,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVideo
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideo(
chat_id=self.chat.id,
@ -3219,9 +3233,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVideo
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideo(
chat_id=self.chat.id,
@ -3301,9 +3315,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVideoNote
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideoNote(
chat_id=self.chat.id,
@ -3376,9 +3390,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVideoNote
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVideoNote(
chat_id=self.chat.id,
@ -3451,9 +3465,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVoice(
chat_id=self.chat.id,
@ -3529,9 +3543,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendVoice
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendVoice(
chat_id=self.chat.id,
@ -3808,9 +3822,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import CopyMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return CopyMessage(
from_chat_id=self.chat.id,
@ -3872,9 +3886,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageText
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageText(
chat_id=self.chat.id,
@ -3928,9 +3942,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import ForwardMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return ForwardMessage(
from_chat_id=self.chat.id,
@ -3975,9 +3989,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageMedia
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageMedia(
chat_id=self.chat.id,
@ -4016,9 +4030,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageReplyMarkup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageReplyMarkup(
chat_id=self.chat.id,
@ -4055,9 +4069,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageReplyMarkup
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageReplyMarkup(
chat_id=self.chat.id,
@ -4107,9 +4121,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageLiveLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageLiveLocation(
chat_id=self.chat.id,
@ -4153,9 +4167,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import StopMessageLiveLocation
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return StopMessageLiveLocation(
chat_id=self.chat.id,
@ -4201,9 +4215,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import EditMessageCaption
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return EditMessageCaption(
chat_id=self.chat.id,
@ -4261,9 +4275,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import DeleteMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return DeleteMessage(
chat_id=self.chat.id,
@ -4297,9 +4311,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import PinChatMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return PinChatMessage(
chat_id=self.chat.id,
@ -4332,9 +4346,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import UnpinChatMessage
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return UnpinChatMessage(
chat_id=self.chat.id,
@ -4353,7 +4367,7 @@ class Message(MaybeInaccessibleMessage):
:param include_thread_id: if set, adds chat thread id to URL and returns like https://t.me/username/thread_id/message_id
:return: string with full message URL
"""
if self.chat.type in ("private", "group"):
if self.chat.type in {"private", "group"}:
return None
chat_value = (
@ -4397,9 +4411,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SetMessageReaction
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SetMessageReaction(
chat_id=self.chat.id,
@ -4461,9 +4475,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPaidMedia
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPaidMedia(
chat_id=self.chat.id,
@ -4536,9 +4550,9 @@ class Message(MaybeInaccessibleMessage):
from aiogram.methods import SendPaidMedia
assert (
self.chat is not None
), "This method can be used only if chat is present in the message."
assert self.chat is not None, (
"This method can be used only if chat is present in the message."
)
return SendPaidMedia(
chat_id=self.chat.id,

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

@ -22,6 +22,7 @@ if TYPE_CHECKING:
from aiogram import Bot
BAD_PATTERN = re.compile(r"[^a-zA-Z0-9-_]")
DEEPLINK_PAYLOAD_LENGTH = 64
async def create_start_link(
@ -145,8 +146,8 @@ def create_deep_link(
)
raise ValueError(msg)
if len(payload) > 64:
msg = "Payload must be up to 64 characters long."
if len(payload) > DEEPLINK_PAYLOAD_LENGTH:
msg = f"Payload must be up to {DEEPLINK_PAYLOAD_LENGTH} characters long."
raise ValueError(msg)
if not app_name:

View file

@ -15,7 +15,7 @@ class DataMixin:
data: dict[str, Any] | None = getattr(self, "_data", None)
if data is None:
data = {}
setattr(self, "_data", data)
self._data = data
return data
def __getitem__(self, key: str) -> Any:

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

@ -148,8 +148,8 @@ When using :code:`uv`, prefix commands with :code:`uv run` to execute them in th
.. code-block:: bash
# Format code
uv run black aiogram tests examples
uv run isort aiogram tests examples
uv run ruff format aiogram tests scripts examples
uv run ruff check --fix aiogram tests scripts examples
# Run tests
uv run pytest tests
@ -180,22 +180,22 @@ implementing new features or experimenting.
Format the code (code-style)
----------------------------
Note that this project is Black-formatted, so you should follow that code-style,
too be sure You're correctly doing this let's reformat the code automatically:
Note that this project uses Ruff for formatting and linting, so you should follow that code-style.
To be sure you're correctly doing this, let's reformat the code automatically:
Using traditional approach:
.. code-block:: bash
black aiogram tests examples
isort aiogram tests examples
ruff format aiogram tests scripts examples
ruff check --fix aiogram tests scripts examples
Or with uv:
.. code-block:: bash
uv run black aiogram tests examples
uv run isort aiogram tests examples
uv run ruff format aiogram tests scripts examples
uv run ruff check --fix aiogram tests scripts examples
Or simply use Makefile:

View file

@ -94,9 +94,61 @@ Bot API
However, all errors are classified by HTTP status codes, and for each method,
only one type of error can be associated with a given code.
Therefore, in most cases, you should check only the error type (by status code)
without inspecting the error message.
without inspecting the error message. More details can be found in the
:ref:`exceptions section » <exceptions>`.
Exceptions
==========
Mapping (v2 -> v3)
-------------------
- RetryAfter -> :class:`TelegramRetryAfter` (:mod:`aiogram.exceptions`)
- Important attribute in v3: ``retry_after`` (int).
- ChatMigrated / MigrateToChat -> :class:`TelegramMigrateToChat`
- Important attribute in v3: ``migrate_to_chat_id`` (int).
- ClientDecodeError -> :class:`ClientDecodeError`
- Important attributes in v3: ``original`` (Exception) and ``data`` (response body).
- BadRequest -> :class:`TelegramBadRequest`
- Unauthorized -> :class:`TelegramUnauthorizedError`
- Forbidden -> :class:`TelegramForbiddenError`
- NotFound -> :class:`TelegramNotFound`
- Conflict -> :class:`TelegramConflictError`
- ServerError -> :class:`TelegramServerError`
- NetworkError -> :class:`TelegramNetworkError`
- EntityTooLarge -> :class:`TelegramEntityTooLarge`
Exceptions removed in v3 (from v2)
----------------------------------
The list below contains common exception names that appeared in aiogram v2 but
are not defined as separate classes in the v3 codebase. For each v2 name, a
recommended v3 replacement (or handling) is provided — keep your migration
logic simple and rely on the v3 exception classes and their attributes.
- MessageNotModified -> :class:`TelegramBadRequest`
- MessageToEditNotFound -> :class:`TelegramNotFound`
- MessageToDeleteNotFound -> :class:`TelegramNotFound`
- MessageCantBeDeleted -> :class:`TelegramForbiddenError` / :class:`TelegramBadRequest`
- CantParseEntities -> :class:`TelegramBadRequest`
- MessageIsTooLong -> :class:`TelegramEntityTooLarge`
- MessageIdentifierNotFound -> :class:`TelegramNotFound`
- UserDeactivated -> :class:`TelegramForbiddenError`
- CantInitiateConversation -> :class:`TelegramBadRequest`
- StickerSetNameInvalid -> :class:`TelegramBadRequest`
- ChatAdminRequired -> :class:`TelegramForbiddenError`
Use these replacements when migrating exception handling from v2 to v3. If
you relied on catching very specific v2 exception classes, replace those
handlers with the corresponding v3 class above (or catch a broader v3 class
such as :class:`TelegramBadRequest` / :class:`TelegramAPIError`) and inspect
available attributes (see "Mapping (v2 -> v3)") for any required details.
Middlewares
===========

View file

@ -2,12 +2,11 @@ import asyncio
import logging
from os import getenv
from handlers.echo import echo_router
from handlers.start import start_router
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from handlers.echo import echo_router
from handlers.start import start_router
# Bot token can be obtained via https://t.me/BotFather
TOKEN = getenv("BOT_TOKEN")

View file

@ -4,7 +4,6 @@ from os import getenv
from typing import Any
from aiohttp import web
from finite_state_machine import form_router
from aiogram import Bot, Dispatcher, F, Router
from aiogram.client.session.aiohttp import AiohttpSession
@ -19,6 +18,7 @@ from aiogram.webhook.aiohttp_server import (
TokenBasedRequestHandler,
setup_application,
)
from finite_state_machine import form_router
main_router = Router()

View file

@ -4,14 +4,14 @@ from os import getenv
from aiohttp.web import run_app
from aiohttp.web_app import Application
from handlers import my_router
from routes import check_data_handler, demo_handler, send_message_handler
from aiogram import Bot, Dispatcher
from aiogram.client.default import DefaultBotProperties
from aiogram.enums.parse_mode import ParseMode
from aiogram.types import MenuButtonWebApp, WebAppInfo
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
from handlers import my_router
from routes import check_data_handler, demo_handler, send_message_handler
TOKEN = getenv("BOT_TOKEN")

View file

@ -94,8 +94,6 @@ docs = [
[dependency-groups]
dev = [
"black~=25.9",
"isort~=7.0",
"ruff~=0.14",
"mypy==1.10.1",
"toml~=0.10.2",
@ -139,16 +137,38 @@ exclude = [
[tool.ruff.lint]
select = [
# "C", # TODO: mccabe - code complecity
"C4",
"E",
"F",
"Q",
"RET",
"T10",
"T20",
"A", # flake8-annotations
"B", # flake8-bugbear
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"E", # pycodestyle errors
"F", # pyflakes
"I", # isort
"PERF", # perflint
"PL", # pylint
"Q", # flake8-quotes
"RET", # flake8-return
"SIM", # flake8-simplify
"T10", # flake8-debugger
"T20", # flake8-print
"UP", # pyupgrade
]
ignore = [
"F401",
"F401", # unused-import (handled by other tools)
"A002", # builtin-argument-shadowing (common in API: type, id, format, etc.)
"A005", # stdlib-module-shadowing (Module `types` shadows a Python standard-library module)
"B008", # function-call-in-default-argument (Pydantic Field() defaults)
"PLC0415", # import-outside-top-level (needed for circular imports)
"PLC1901", # compare-to-empty-string (sometimes more explicit is better)
"PLR0904", # too-many-public-methods (Bot class has many API methods)
"PLR0911", # too-many-return-statements
"PLR0912", # too-many-branches
"PLR0913", # too-many-arguments (Telegram API methods have many params)
"PLR0915", # too-many-statements
"PLR0917", # too-many-positional-arguments (Telegram API design)
"PLR6301", # no-self-use (sometimes methods are intentionally not static)
"PLW2901", # redefined-loop-name (intentional pattern in this codebase)
"PLW3201", # bad-dunder-method-name (custom dunders for framework design)
]
[tool.ruff.lint.isort]
@ -164,6 +184,20 @@ known-first-party = [
"aiogram/types/*" = ["E501"]
"aiogram/methods/*" = ["E501"]
"aiogram/enums/*" = ["E501"]
"tests/**" = [
"PLR0124",
"PLR2004",
"DTZ005",
"DTZ006",
"A001",
"A004",
"B018",
"B020",
"B904",
"E501",
"F821",
"UP",
]
[tool.pytest.ini_options]
@ -174,7 +208,6 @@ testpaths = [
filterwarnings = [
"error",
"ignore::pytest.PytestUnraisableExceptionWarning",
# Remove when uvloop fixes the issue
# https://github.com/MagicStack/uvloop/issues/703
# https://github.com/MagicStack/uvloop/pull/705
@ -235,23 +268,10 @@ module = [
ignore_missing_imports = true
disallow_untyped_defs = true
[tool.black]
line-length = 99
target-version = ['py310', 'py311', 'py312', 'py313']
exclude = '''
(
\.eggs
| \.git
| \.tox
| build
| dist
| venv
| docs
)
'''
[tool.isort]
profile = "black"
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "auto"
[tool.towncrier]
package = "aiogram"

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python3
"""Version bumping script for aiogram (replaces hatch version)."""
import re
import sys
from pathlib import Path
@ -44,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

@ -144,8 +144,7 @@ async def memory_storage():
@pytest.fixture()
async def redis_isolation(redis_storage):
isolation = redis_storage.create_isolation()
return isolation
return redis_storage.create_isolation()
@pytest.fixture()

View file

@ -1,5 +1,4 @@
from contextlib import contextmanager
from typing import Type
import pytest
from packaging import version
@ -10,8 +9,8 @@ import aiogram
@contextmanager
def check_deprecated(
max_version: str,
exception: Type[Exception],
warning: Type[Warning] = DeprecationWarning,
exception: type[Exception],
warning: type[Warning] = DeprecationWarning,
) -> None:
"""
Should be used for modules that are being deprecated or already removed from aiogram

View file

@ -1,5 +1,6 @@
from collections import deque
from typing import TYPE_CHECKING, Any, AsyncGenerator, Deque, Dict, Optional, Type
from collections.abc import AsyncGenerator
from typing import TYPE_CHECKING, Any
from aiogram import Bot
from aiogram.client.session.base import BaseSession
@ -10,9 +11,9 @@ from aiogram.types import UNSET_PARSE_MODE, ResponseParameters, User
class MockedSession(BaseSession):
def __init__(self):
super(MockedSession, self).__init__()
self.responses: Deque[Response[TelegramType]] = deque()
self.requests: Deque[TelegramMethod[TelegramType]] = deque()
super().__init__()
self.responses: deque[Response[TelegramType]] = deque()
self.requests: deque[TelegramMethod[TelegramType]] = deque()
self.closed = True
def add_result(self, response: Response[TelegramType]) -> Response[TelegramType]:
@ -29,7 +30,7 @@ class MockedSession(BaseSession):
self,
bot: Bot,
method: TelegramMethod[TelegramType],
timeout: Optional[int] = UNSET_PARSE_MODE,
timeout: int | None = UNSET_PARSE_MODE,
) -> TelegramType:
self.closed = False
self.requests.append(method)
@ -45,7 +46,7 @@ class MockedSession(BaseSession):
async def stream_content(
self,
url: str,
headers: Optional[Dict[str, Any]] = None,
headers: dict[str, Any] | None = None,
timeout: int = 30,
chunk_size: int = 65536,
raise_for_status: bool = True,
@ -58,9 +59,7 @@ class MockedBot(Bot):
session: MockedSession
def __init__(self, **kwargs):
super(MockedBot, self).__init__(
kwargs.pop("token", "42:TEST"), session=MockedSession(), **kwargs
)
super().__init__(kwargs.pop("token", "42:TEST"), session=MockedSession(), **kwargs)
self._me = User(
id=self.id,
is_bot=True,
@ -72,13 +71,13 @@ class MockedBot(Bot):
def add_result_for(
self,
method: Type[TelegramMethod[TelegramType]],
method: type[TelegramMethod[TelegramType]],
ok: bool,
result: TelegramType = None,
description: Optional[str] = None,
description: str | None = None,
error_code: int = 200,
migrate_to_chat_id: Optional[int] = None,
retry_after: Optional[int] = None,
migrate_to_chat_id: int | None = None,
retry_after: int | None = None,
) -> Response[TelegramType]:
response = Response[method.__returning__]( # type: ignore
ok=ok,

View file

@ -1,12 +1,8 @@
import asyncio
from collections.abc import AsyncGenerator, AsyncIterable
from typing import (
Any,
AsyncContextManager,
AsyncGenerator,
AsyncIterable,
Dict,
List,
Union,
)
from unittest.mock import AsyncMock, patch
@ -115,10 +111,10 @@ class TestAiohttpSession:
str_: str
int_: int
bool_: bool
unset_: Union[str, Default] = Default("parse_mode")
unset_: str | Default = Default("parse_mode")
null_: None
list_: List[str]
dict_: Dict[str, Any]
list_: list[str]
dict_: dict[str, Any]
session = AiohttpSession()
form = session.build_form_data(

View file

@ -1,6 +1,7 @@
import datetime
import json
from typing import Any, AsyncContextManager, AsyncGenerator, Dict, Optional
from collections.abc import AsyncGenerator
from typing import Any, AsyncContextManager
from unittest.mock import AsyncMock, patch
import pytest
@ -39,7 +40,7 @@ class CustomSession(BaseSession):
self,
token: str,
method: TelegramMethod[TelegramType],
timeout: Optional[int] = UNSET_PARSE_MODE,
timeout: int | None = UNSET_PARSE_MODE,
) -> None: # type: ignore
assert isinstance(token, str)
assert isinstance(method, TelegramMethod)
@ -47,7 +48,7 @@ class CustomSession(BaseSession):
async def stream_content(
self,
url: str,
headers: Optional[Dict[str, Any]] = None,
headers: dict[str, Any] | None = None,
timeout: int = 30,
chunk_size: int = 65536,
raise_for_status: bool = True,

View file

@ -15,5 +15,5 @@ class TestAddStickerToSet:
sticker="file id", format=StickerFormat.STATIC, emoji_list=[":)"]
),
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -7,5 +7,5 @@ class TestAnswerCallbackQuery:
prepare_result = bot.add_result_for(AnswerCallbackQuery, ok=True, result=True)
response: bool = await bot.answer_callback_query(callback_query_id="cq id", text="OK")
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -17,5 +17,5 @@ class TestAnswerInlineQuery:
)
],
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -9,5 +9,5 @@ class TestAnswerPreCheckoutQuery:
response: bool = await bot.answer_pre_checkout_query(
pre_checkout_query_id="query id", ok=True
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -7,5 +7,5 @@ class TestAnswerShippingQuery:
prepare_result = bot.add_result_for(AnswerShippingQuery, ok=True, result=True)
response: bool = await bot.answer_shipping_query(shipping_query_id="query id", ok=True)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -15,5 +15,5 @@ class TestAnswerWebAppQuery:
thumbnail_url="test",
),
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -10,5 +10,5 @@ class TestApproveChatJoinRequest:
chat_id=-42,
user_id=42,
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -10,5 +10,5 @@ class TestApproveSuggestedPost:
chat_id=-42,
message_id=42,
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -7,5 +7,5 @@ class TestKickChatMember:
prepare_result = bot.add_result_for(BanChatMember, ok=True, result=True)
response: bool = await bot.ban_chat_member(chat_id=-42, user_id=42)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -10,5 +10,5 @@ class TestBanChatSenderChat:
chat_id=-42,
sender_chat_id=-1337,
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -7,5 +7,5 @@ class TestClose:
prepare_result = bot.add_result_for(Close, ok=True, result=True)
response: bool = await bot.close()
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

View file

@ -10,5 +10,5 @@ class TestCloseForumTopic:
chat_id=42,
message_thread_id=42,
)
request = bot.get_request()
bot.get_request()
assert response == prepare_result.result

Some files were not shown because too many files have changed in this diff Show more