Merge branch 'dev-3.x'

This commit is contained in:
JRoot Junior 2026-03-03 01:26:29 +02:00
commit 4f435ae39b
No known key found for this signature in database
GPG key ID: 738964250D5FF6E2
62 changed files with 1296 additions and 83 deletions

View file

@ -1 +1 @@
9.4
9.5

View file

@ -39,6 +39,7 @@ extract:
- reply_to_story
- business_connection_id
- sender_business_bot
- sender_tag
- is_from_offline
- has_media_spoiler
- effect_id

View file

@ -47,8 +47,8 @@
"type": "InlineKeyboardMarkup",
"required": false,
"description": "A JSON-serialized object for the new inline keyboard for the message",
"html_description": "<td>A JSON-serialized object for the new inline keyboard for the message</td>",
"rst_description": "A JSON-serialized object for the new inline keyboard for the message\n",
"html_description": "<td>A JSON-serialized object for the new <a href=\"/bots/features#inline-keyboards\">inline keyboard</a> for the message</td>",
"rst_description": "A JSON-serialized object for the new `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ for the message\n",
"name": "reply_markup"
}
],

View file

@ -154,6 +154,14 @@
"html_description": "<td>Pass <em>True</em> if the administrator can manage direct messages within the channel and decline suggested posts; for channels only</td>",
"rst_description": "Pass :code:`True` if the administrator can manage direct messages within the channel and decline suggested posts; for channels only\n",
"name": "can_manage_direct_messages"
},
{
"type": "Boolean",
"required": false,
"description": "Pass True if the administrator can edit the tags of regular members; for groups and supergroups only",
"html_description": "<td>Pass <em>True</em> if the administrator can edit the tags of regular members; for groups and supergroups only</td>",
"rst_description": "Pass :code:`True` if the administrator can edit the tags of regular members; for groups and supergroups only\n",
"name": "can_manage_tags"
}
],
"category": "methods"

View file

@ -71,8 +71,8 @@
"type": "InlineKeyboardMarkup",
"required": false,
"description": "A JSON-serialized object for an inline keyboard",
"html_description": "<td>A JSON-serialized object for an inline keyboard</td>",
"rst_description": "A JSON-serialized object for an inline keyboard\n",
"html_description": "<td>A JSON-serialized object for an <a href=\"/bots/features#inline-keyboards\">inline keyboard</a></td>",
"rst_description": "A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_\n",
"name": "reply_markup"
}
],

View file

@ -7,9 +7,9 @@
"object": {
"anchor": "sendmessagedraft",
"name": "sendMessageDraft",
"description": "Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns True on success.",
"html_description": "<p>Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns :code:`True` on success.",
"description": "Use this method to stream a partial message to a user while the message is being generated. Returns True on success.",
"html_description": "<p>Use this method to stream a partial message to a user while the message is being generated. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to stream a partial message to a user while the message is being generated. Returns :code:`True` on success.",
"annotations": [
{
"type": "Integer",

View file

@ -0,0 +1,41 @@
{
"meta": {},
"group": {
"title": "Available methods",
"anchor": "available-methods"
},
"object": {
"anchor": "setchatmembertag",
"name": "setChatMemberTag",
"description": "Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the can_manage_tags administrator right. Returns True on success.",
"html_description": "<p>Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the <em>can_manage_tags</em> administrator right. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the *can_manage_tags* administrator right. Returns :code:`True` on success.",
"annotations": [
{
"type": "Integer or String",
"required": true,
"description": "Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)",
"html_description": "<td>Unique identifier for the target chat or username of the target supergroup (in the format <code>@supergroupusername</code>)</td>",
"rst_description": "Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)\n",
"name": "chat_id"
},
{
"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": "String",
"required": false,
"description": "New tag for the member; 0-16 characters, emoji are not allowed",
"html_description": "<td>New tag for the member; 0-16 characters, emoji are not allowed</td>",
"rst_description": "New tag for the member; 0-16 characters, emoji are not allowed\n",
"name": "tag"
}
],
"category": "methods"
}
}

View file

@ -1,7 +1,7 @@
{
"api": {
"version": "9.4",
"release_date": "2026-02-09"
"version": "9.5",
"release_date": "2026-03-01"
},
"items": [
{
@ -1119,6 +1119,14 @@
"name": "sender_business_bot",
"required": false
},
{
"type": "String",
"description": "Tag or custom title of the sender of the message; for supergroups only",
"html_description": "<td><em>Optional</em>. Tag or custom title of the sender of the message; for supergroups only</td>",
"rst_description": "*Optional*. Tag or custom title of the sender of the message; for supergroups only\n",
"name": "sender_tag",
"required": false
},
{
"type": "Integer",
"description": "Date the message was sent in Unix time. It is always a positive number, representing a valid date.",
@ -1249,9 +1257,9 @@
},
{
"type": "String",
"description": "The unique identifier of a media message group this message belongs to",
"html_description": "<td><em>Optional</em>. The unique identifier of a media message group this message belongs to</td>",
"rst_description": "*Optional*. The unique identifier of a media message group this message belongs to\n",
"description": "The unique identifier inside this chat of a media message group this message belongs to",
"html_description": "<td><em>Optional</em>. The unique identifier inside this chat of a media message group this message belongs to</td>",
"rst_description": "*Optional*. The unique identifier inside this chat of a media message group this message belongs to\n",
"name": "media_group_id",
"required": false
},
@ -1898,8 +1906,8 @@
{
"type": "InlineKeyboardMarkup",
"description": "Inline keyboard attached to the message. login_url buttons are represented as ordinary url buttons.",
"html_description": "<td><em>Optional</em>. Inline keyboard attached to the message. <code>login_url</code> buttons are represented as ordinary <code>url</code> buttons.</td>",
"rst_description": "*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.\n",
"html_description": "<td><em>Optional</em>. <a href=\"/bots/features#inline-keyboards\">Inline keyboard</a> attached to the message. <code>login_url</code> buttons are represented as ordinary <code>url</code> buttons.</td>",
"rst_description": "*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.\n",
"name": "reply_markup",
"required": false
}
@ -1976,9 +1984,9 @@
"annotations": [
{
"type": "String",
"description": "Type of the entity. Currently, can be 'mention' (@username), 'hashtag' (#hashtag or #hashtag@chatusername), 'cashtag' ($USD or $USD@chatusername), 'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email' (do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic' (italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users without usernames), 'custom_emoji' (for inline custom emoji stickers)",
"html_description": "<td>Type of the entity. Currently, can be &#8220;mention&#8221; (<code>@username</code>), &#8220;hashtag&#8221; (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), &#8220;cashtag&#8221; (<code>$USD</code> or <code>$USD@chatusername</code>), &#8220;bot_command&#8221; (<code>/start@jobs_bot</code>), &#8220;url&#8221; (<code>https://telegram.org</code>), &#8220;email&#8221; (<code>do-not-reply@telegram.org</code>), &#8220;phone_number&#8221; (<code>+1-212-555-0123</code>), &#8220;bold&#8221; (<strong>bold text</strong>), &#8220;italic&#8221; (<em>italic text</em>), &#8220;underline&#8221; (underlined text), &#8220;strikethrough&#8221; (strikethrough text), &#8220;spoiler&#8221; (spoiler message), &#8220;blockquote&#8221; (block quotation), &#8220;expandable_blockquote&#8221; (collapsed-by-default block quotation), &#8220;code&#8221; (monowidth string), &#8220;pre&#8221; (monowidth block), &#8220;text_link&#8221; (for clickable text URLs), &#8220;text_mention&#8221; (for users <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), &#8220;custom_emoji&#8221; (for inline custom emoji stickers)</td>",
"rst_description": "Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers)\n",
"description": "Type of the entity. Currently, can be 'mention' (@username), 'hashtag' (#hashtag or #hashtag@chatusername), 'cashtag' ($USD or $USD@chatusername), 'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email' (do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic' (italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users without usernames), 'custom_emoji' (for inline custom emoji stickers), or 'date_time' (for formatted date and time)",
"html_description": "<td>Type of the entity. Currently, can be &#8220;mention&#8221; (<code>@username</code>), &#8220;hashtag&#8221; (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), &#8220;cashtag&#8221; (<code>$USD</code> or <code>$USD@chatusername</code>), &#8220;bot_command&#8221; (<code>/start@jobs_bot</code>), &#8220;url&#8221; (<code>https://telegram.org</code>), &#8220;email&#8221; (<code>do-not-reply@telegram.org</code>), &#8220;phone_number&#8221; (<code>+1-212-555-0123</code>), &#8220;bold&#8221; (<strong>bold text</strong>), &#8220;italic&#8221; (<em>italic text</em>), &#8220;underline&#8221; (underlined text), &#8220;strikethrough&#8221; (strikethrough text), &#8220;spoiler&#8221; (spoiler message), &#8220;blockquote&#8221; (block quotation), &#8220;expandable_blockquote&#8221; (collapsed-by-default block quotation), &#8220;code&#8221; (monowidth string), &#8220;pre&#8221; (monowidth block), &#8220;text_link&#8221; (for clickable text URLs), &#8220;text_mention&#8221; (for users <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), &#8220;custom_emoji&#8221; (for inline custom emoji stickers), or &#8220;date_time&#8221; (for formatted date and time)</td>",
"rst_description": "Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers), or 'date_time' (for formatted date and time)\n",
"name": "type",
"required": true
},
@ -2029,6 +2037,22 @@
"rst_description": "*Optional*. For 'custom_emoji' only, unique identifier of the custom emoji. Use :class:`aiogram.methods.get_custom_emoji_stickers.GetCustomEmojiStickers` to get full information about the sticker\n",
"name": "custom_emoji_id",
"required": false
},
{
"type": "Integer",
"description": "For 'date_time' only, the Unix time associated with the entity",
"html_description": "<td><em>Optional</em>. For &#8220;date_time&#8221; only, the Unix time associated with the entity</td>",
"rst_description": "*Optional*. For 'date_time' only, the Unix time associated with the entity\n",
"name": "unix_time",
"required": false
},
{
"type": "String",
"description": "For 'date_time' only, the string that defines the formatting of the date and time. See date-time entity formatting for more details.",
"html_description": "<td><em>Optional</em>. For &#8220;date_time&#8221; only, the string that defines the formatting of the date and time. See <a href=\"#date-time-entity-formatting\">date-time entity formatting</a> for more details.</td>",
"rst_description": "*Optional*. For 'date_time' only, the string that defines the formatting of the date and time. See `date-time entity formatting <https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details.\n",
"name": "date_time_format",
"required": false
}
],
"category": "types"
@ -6332,6 +6356,14 @@
"rst_description": "*Optional*. :code:`True`, if the administrator can manage direct messages of the channel and decline suggested posts; for channels only\n",
"name": "can_manage_direct_messages",
"required": false
},
{
"type": "Boolean",
"description": "True, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.</td>",
"rst_description": "*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.\n",
"name": "can_manage_tags",
"required": false
}
],
"category": "types"
@ -6620,6 +6652,14 @@
"name": "can_manage_direct_messages",
"required": false
},
{
"type": "Boolean",
"description": "True, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.</td>",
"rst_description": "*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.\n",
"name": "can_manage_tags",
"required": false
},
{
"type": "String",
"description": "Custom title for this user",
@ -6646,6 +6686,14 @@
"name": "status",
"required": true
},
{
"type": "String",
"description": "Tag of the member",
"html_description": "<td><em>Optional</em>. Tag of the member</td>",
"rst_description": "*Optional*. Tag of the member\n",
"name": "tag",
"required": false
},
{
"type": "User",
"description": "Information about the user",
@ -6680,6 +6728,14 @@
"name": "status",
"required": true
},
{
"type": "String",
"description": "Tag of the member",
"html_description": "<td><em>Optional</em>. Tag of the member</td>",
"rst_description": "*Optional*. Tag of the member\n",
"name": "tag",
"required": false
},
{
"type": "User",
"description": "Information about the user",
@ -6776,6 +6832,14 @@
"name": "can_add_web_page_previews",
"required": true
},
{
"type": "Boolean",
"description": "True, if the user is allowed to edit their own tag",
"html_description": "<td><em>True</em>, if the user is allowed to edit their own tag</td>",
"rst_description": ":code:`True`, if the user is allowed to edit their own tag\n",
"name": "can_edit_tag",
"required": true
},
{
"type": "Boolean",
"description": "True, if the user is allowed to change the chat title, photo and other settings",
@ -7024,6 +7088,14 @@
"name": "can_add_web_page_previews",
"required": false
},
{
"type": "Boolean",
"description": "True, if the user is allowed to edit their own tag",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the user is allowed to edit their own tag</td>",
"rst_description": "*Optional*. :code:`True`, if the user is allowed to edit their own tag\n",
"name": "can_edit_tag",
"required": false
},
{
"type": "Boolean",
"description": "True, if the user is allowed to change the chat title, photo and other settings. Ignored in public supergroups",
@ -12959,8 +13031,8 @@
"type": "InlineKeyboardMarkup",
"required": false,
"description": "A JSON-serialized object for an inline keyboard",
"html_description": "<td>A JSON-serialized object for an inline keyboard</td>",
"rst_description": "A JSON-serialized object for an inline keyboard\n",
"html_description": "<td>A JSON-serialized object for an <a href=\"/bots/features#inline-keyboards\">inline keyboard</a></td>",
"rst_description": "A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_\n",
"name": "reply_markup"
}
],
@ -13075,9 +13147,9 @@
{
"anchor": "sendmessagedraft",
"name": "sendMessageDraft",
"description": "Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns True on success.",
"html_description": "<p>Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns :code:`True` on success.",
"description": "Use this method to stream a partial message to a user while the message is being generated. Returns True on success.",
"html_description": "<p>Use this method to stream a partial message to a user while the message is being generated. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to stream a partial message to a user while the message is being generated. Returns :code:`True` on success.",
"annotations": [
{
"type": "Integer",
@ -13610,6 +13682,14 @@
"html_description": "<td>Pass <em>True</em> if the administrator can manage direct messages within the channel and decline suggested posts; for channels only</td>",
"rst_description": "Pass :code:`True` if the administrator can manage direct messages within the channel and decline suggested posts; for channels only\n",
"name": "can_manage_direct_messages"
},
{
"type": "Boolean",
"required": false,
"description": "Pass True if the administrator can edit the tags of regular members; for groups and supergroups only",
"html_description": "<td>Pass <em>True</em> if the administrator can edit the tags of regular members; for groups and supergroups only</td>",
"rst_description": "Pass :code:`True` if the administrator can edit the tags of regular members; for groups and supergroups only\n",
"name": "can_manage_tags"
}
],
"category": "methods"
@ -13648,6 +13728,40 @@
],
"category": "methods"
},
{
"anchor": "setchatmembertag",
"name": "setChatMemberTag",
"description": "Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the can_manage_tags administrator right. Returns True on success.",
"html_description": "<p>Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the <em>can_manage_tags</em> administrator right. Returns <em>True</em> on success.</p>",
"rst_description": "Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the *can_manage_tags* administrator right. Returns :code:`True` on success.",
"annotations": [
{
"type": "Integer or String",
"required": true,
"description": "Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)",
"html_description": "<td>Unique identifier for the target chat or username of the target supergroup (in the format <code>@supergroupusername</code>)</td>",
"rst_description": "Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)\n",
"name": "chat_id"
},
{
"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": "String",
"required": false,
"description": "New tag for the member; 0-16 characters, emoji are not allowed",
"html_description": "<td>New tag for the member; 0-16 characters, emoji are not allowed</td>",
"rst_description": "New tag for the member; 0-16 characters, emoji are not allowed\n",
"name": "tag"
}
],
"category": "methods"
},
{
"anchor": "banchatsenderchat",
"name": "banChatSenderChat",
@ -16631,8 +16745,8 @@
"type": "InlineKeyboardMarkup",
"required": false,
"description": "A JSON-serialized object for the new inline keyboard for the message",
"html_description": "<td>A JSON-serialized object for the new inline keyboard for the message</td>",
"rst_description": "A JSON-serialized object for the new inline keyboard for the message\n",
"html_description": "<td>A JSON-serialized object for the new <a href=\"/bots/features#inline-keyboards\">inline keyboard</a> for the message</td>",
"rst_description": "A JSON-serialized object for the new `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ for the message\n",
"name": "reply_markup"
}
],
@ -18727,8 +18841,8 @@
{
"type": "InlineKeyboardMarkup",
"description": "Inline keyboard attached to the message",
"html_description": "<td><em>Optional</em>. Inline keyboard attached to the message</td>",
"rst_description": "*Optional*. Inline keyboard attached to the message\n",
"html_description": "<td><em>Optional</em>. <a href=\"/bots/features#inline-keyboards\">Inline keyboard</a> attached to the message</td>",
"rst_description": "*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message\n",
"name": "reply_markup",
"required": false
},
@ -22865,9 +22979,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\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-",
"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 »**",
"annotations": [
{
"type": "Integer",

View file

@ -71,6 +71,10 @@ set_administrator_custom_title:
method: setChatAdministratorCustomTitle
fill: *self
set_member_tag:
method: setChatMemberTag
fill: *self
set_permissions:
method: setChatPermissions
fill: *self

View file

@ -138,6 +138,14 @@
"rst_description": "*Optional*. :code:`True`, if the administrator can manage direct messages of the channel and decline suggested posts; for channels only\n",
"name": "can_manage_direct_messages",
"required": false
},
{
"type": "Boolean",
"description": "True, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.</td>",
"rst_description": "*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.\n",
"name": "can_manage_tags",
"required": false
}
],
"category": "types"

View file

@ -163,6 +163,14 @@
"name": "can_manage_direct_messages",
"required": false
},
{
"type": "Boolean",
"description": "True, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.</td>",
"rst_description": "*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages.\n",
"name": "can_manage_tags",
"required": false
},
{
"type": "String",
"description": "Custom title for this user",

View file

@ -19,6 +19,14 @@
"name": "status",
"required": true
},
{
"type": "String",
"description": "Tag of the member",
"html_description": "<td><em>Optional</em>. Tag of the member</td>",
"rst_description": "*Optional*. Tag of the member\n",
"name": "tag",
"required": false
},
{
"type": "User",
"description": "Information about the user",

View file

@ -19,6 +19,14 @@
"name": "status",
"required": true
},
{
"type": "String",
"description": "Tag of the member",
"html_description": "<td><em>Optional</em>. Tag of the member</td>",
"rst_description": "*Optional*. Tag of the member\n",
"name": "tag",
"required": false
},
{
"type": "User",
"description": "Information about the user",
@ -115,6 +123,14 @@
"name": "can_add_web_page_previews",
"required": true
},
{
"type": "Boolean",
"description": "True, if the user is allowed to edit their own tag",
"html_description": "<td><em>True</em>, if the user is allowed to edit their own tag</td>",
"rst_description": ":code:`True`, if the user is allowed to edit their own tag\n",
"name": "can_edit_tag",
"required": true
},
{
"type": "Boolean",
"description": "True, if the user is allowed to change the chat title, photo and other settings",

View file

@ -91,6 +91,14 @@
"name": "can_add_web_page_previews",
"required": false
},
{
"type": "Boolean",
"description": "True, if the user is allowed to edit their own tag",
"html_description": "<td><em>Optional</em>. <em>True</em>, if the user is allowed to edit their own tag</td>",
"rst_description": "*Optional*. :code:`True`, if the user is allowed to edit their own tag\n",
"name": "can_edit_tag",
"required": false
},
{
"type": "Boolean",
"description": "True, if the user is allowed to change the chat title, photo and other settings. Ignored in public supergroups",

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\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-",
"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 »**",
"annotations": [
{
"type": "Integer",

View file

@ -86,8 +86,8 @@
{
"type": "InlineKeyboardMarkup",
"description": "Inline keyboard attached to the message",
"html_description": "<td><em>Optional</em>. Inline keyboard attached to the message</td>",
"rst_description": "*Optional*. Inline keyboard attached to the message\n",
"html_description": "<td><em>Optional</em>. <a href=\"/bots/features#inline-keyboards\">Inline keyboard</a> attached to the message</td>",
"rst_description": "*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message\n",
"name": "reply_markup",
"required": false
},

View file

@ -67,6 +67,14 @@
"name": "sender_business_bot",
"required": false
},
{
"type": "String",
"description": "Tag or custom title of the sender of the message; for supergroups only",
"html_description": "<td><em>Optional</em>. Tag or custom title of the sender of the message; for supergroups only</td>",
"rst_description": "*Optional*. Tag or custom title of the sender of the message; for supergroups only\n",
"name": "sender_tag",
"required": false
},
{
"type": "Integer",
"description": "Date the message was sent in Unix time. It is always a positive number, representing a valid date.",
@ -197,9 +205,9 @@
},
{
"type": "String",
"description": "The unique identifier of a media message group this message belongs to",
"html_description": "<td><em>Optional</em>. The unique identifier of a media message group this message belongs to</td>",
"rst_description": "*Optional*. The unique identifier of a media message group this message belongs to\n",
"description": "The unique identifier inside this chat of a media message group this message belongs to",
"html_description": "<td><em>Optional</em>. The unique identifier inside this chat of a media message group this message belongs to</td>",
"rst_description": "*Optional*. The unique identifier inside this chat of a media message group this message belongs to\n",
"name": "media_group_id",
"required": false
},
@ -846,8 +854,8 @@
{
"type": "InlineKeyboardMarkup",
"description": "Inline keyboard attached to the message. login_url buttons are represented as ordinary url buttons.",
"html_description": "<td><em>Optional</em>. Inline keyboard attached to the message. <code>login_url</code> buttons are represented as ordinary <code>url</code> buttons.</td>",
"rst_description": "*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.\n",
"html_description": "<td><em>Optional</em>. <a href=\"/bots/features#inline-keyboards\">Inline keyboard</a> attached to the message. <code>login_url</code> buttons are represented as ordinary <code>url</code> buttons.</td>",
"rst_description": "*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons.\n",
"name": "reply_markup",
"required": false
},

View file

@ -13,9 +13,9 @@
"annotations": [
{
"type": "String",
"description": "Type of the entity. Currently, can be 'mention' (@username), 'hashtag' (#hashtag or #hashtag@chatusername), 'cashtag' ($USD or $USD@chatusername), 'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email' (do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic' (italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users without usernames), 'custom_emoji' (for inline custom emoji stickers)",
"html_description": "<td>Type of the entity. Currently, can be &#8220;mention&#8221; (<code>@username</code>), &#8220;hashtag&#8221; (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), &#8220;cashtag&#8221; (<code>$USD</code> or <code>$USD@chatusername</code>), &#8220;bot_command&#8221; (<code>/start@jobs_bot</code>), &#8220;url&#8221; (<code>https://telegram.org</code>), &#8220;email&#8221; (<code>do-not-reply@telegram.org</code>), &#8220;phone_number&#8221; (<code>+1-212-555-0123</code>), &#8220;bold&#8221; (<strong>bold text</strong>), &#8220;italic&#8221; (<em>italic text</em>), &#8220;underline&#8221; (underlined text), &#8220;strikethrough&#8221; (strikethrough text), &#8220;spoiler&#8221; (spoiler message), &#8220;blockquote&#8221; (block quotation), &#8220;expandable_blockquote&#8221; (collapsed-by-default block quotation), &#8220;code&#8221; (monowidth string), &#8220;pre&#8221; (monowidth block), &#8220;text_link&#8221; (for clickable text URLs), &#8220;text_mention&#8221; (for users <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), &#8220;custom_emoji&#8221; (for inline custom emoji stickers)</td>",
"rst_description": "Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers)\n",
"description": "Type of the entity. Currently, can be 'mention' (@username), 'hashtag' (#hashtag or #hashtag@chatusername), 'cashtag' ($USD or $USD@chatusername), 'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email' (do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic' (italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users without usernames), 'custom_emoji' (for inline custom emoji stickers), or 'date_time' (for formatted date and time)",
"html_description": "<td>Type of the entity. Currently, can be &#8220;mention&#8221; (<code>@username</code>), &#8220;hashtag&#8221; (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), &#8220;cashtag&#8221; (<code>$USD</code> or <code>$USD@chatusername</code>), &#8220;bot_command&#8221; (<code>/start@jobs_bot</code>), &#8220;url&#8221; (<code>https://telegram.org</code>), &#8220;email&#8221; (<code>do-not-reply@telegram.org</code>), &#8220;phone_number&#8221; (<code>+1-212-555-0123</code>), &#8220;bold&#8221; (<strong>bold text</strong>), &#8220;italic&#8221; (<em>italic text</em>), &#8220;underline&#8221; (underlined text), &#8220;strikethrough&#8221; (strikethrough text), &#8220;spoiler&#8221; (spoiler message), &#8220;blockquote&#8221; (block quotation), &#8220;expandable_blockquote&#8221; (collapsed-by-default block quotation), &#8220;code&#8221; (monowidth string), &#8220;pre&#8221; (monowidth block), &#8220;text_link&#8221; (for clickable text URLs), &#8220;text_mention&#8221; (for users <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), &#8220;custom_emoji&#8221; (for inline custom emoji stickers), or &#8220;date_time&#8221; (for formatted date and time)</td>",
"rst_description": "Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers), or 'date_time' (for formatted date and time)\n",
"name": "type",
"required": true
},
@ -66,6 +66,22 @@
"rst_description": "*Optional*. For 'custom_emoji' only, unique identifier of the custom emoji. Use :class:`aiogram.methods.get_custom_emoji_stickers.GetCustomEmojiStickers` to get full information about the sticker\n",
"name": "custom_emoji_id",
"required": false
},
{
"type": "Integer",
"description": "For 'date_time' only, the Unix time associated with the entity",
"html_description": "<td><em>Optional</em>. For &#8220;date_time&#8221; only, the Unix time associated with the entity</td>",
"rst_description": "*Optional*. For 'date_time' only, the Unix time associated with the entity\n",
"name": "unix_time",
"required": false
},
{
"type": "String",
"description": "For 'date_time' only, the string that defines the formatting of the date and time. See date-time entity formatting for more details.",
"html_description": "<td><em>Optional</em>. For &#8220;date_time&#8221; only, the string that defines the formatting of the date and time. See <a href=\"#date-time-entity-formatting\">date-time entity formatting</a> for more details.</td>",
"rst_description": "*Optional*. For 'date_time' only, the string that defines the formatting of the date and time. See `date-time entity formatting <https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details.\n",
"name": "date_time_format",
"required": false
}
],
"category": "types"

120
AGENTS.md Normal file
View file

@ -0,0 +1,120 @@
# AGENTS.md
This file defines how coding agents should contribute to `aiogram` on `dev-3.x`.
## Scope and defaults
- Base branch: `dev-3.x`
- Python: `>=3.10`
- Main tooling: `uv`, `ruff`, `mypy`, `pytest`, `towncrier`, `butcher`
- Keep diffs focused; avoid unrelated refactors/reformatting.
## Setup
```bash
uv sync --all-extras --group dev --group test
uv run pre-commit install
```
Note: `uv run pre-commit install` writes hooks to the shared repository `.git/hooks`
(common for all worktrees), not only for the current worktree.
## Mandatory local checks before PR
Code style/lint in this repository is enforced via Ruff (`ruff check` + `ruff format`).
Quick loop (recommended for most PR iterations):
```bash
uv run ruff check --show-fixes --preview aiogram examples
uv run ruff format --check --diff aiogram tests scripts examples
uv run mypy aiogram
uv run pytest tests
```
Full loop (run before final review request):
```bash
# Run quick loop first, then:
uv run pytest --redis redis://<host>:<port>/<db> tests # when Redis storage paths are affected
uv run pytest --mongo mongodb://<user>:<password>@<host>:<port> tests # when Mongo storage paths are affected
uv run --extra docs bash -c 'cd docs && make html' # when docs or generated API docs are affected
```
If changes touch Redis/Mongo storage behavior, run integration variants too:
```bash
uv run pytest --redis redis://<host>:<port>/<db> tests
uv run pytest --mongo mongodb://<user>:<password>@<host>:<port> tests
```
Run these only if you have accessible Redis/Mongo instances in your environment.
## Changelog rules (CI-gated)
- Add `CHANGES/<issue-or-pr>.<category>.rst` unless PR has `skip news` label.
- Valid categories: `feature`, `bugfix`, `doc`, `removal`, `misc`.
- Changelog text must describe user-visible behavior changes, not process/org details.
- Do not edit `CHANGES.rst` directly for regular PRs.
## Bot API/codegen workflow (critical)
`aiogram` API layers are generated. For Bot API related work:
- Prefer editing generator inputs (`.butcher/**/*.yml`, aliases, templates) instead of hand-editing generated code.
- Do not manually edit `.butcher/**/entity.json` (parser/codegen will overwrite it).
- For new shortcuts, add alias/config in `.butcher` and regenerate.
- Regeneration flow:
```bash
uv run --extra cli butcher parse
uv run --extra cli butcher refresh
uv run --extra cli butcher apply all
```
For maintainers preparing an API/version bump only:
```bash
make update-api args=patch
```
`make update-api args=...` also runs version bump scripts and updates version-related files
(`aiogram/__meta__.py`, `README.rst`, `docs/index.rst`).
After regeneration, run lint/type/tests again.
## Maintainer review signals (recent PRs)
These patterns repeatedly appeared in maintainer feedback and should be treated as hard constraints:
- Keep generation path consistent: shortcuts/features should be added through `.butcher` config + generation, not ad-hoc manual edits.
- Keep test style consistent with existing suite; avoid introducing new dependencies for small tests.
- Preserve framework contracts (e.g., dispatcher/workflow data passed to startup/shutdown callbacks).
- When fixing generated API metadata/docs, update the source mapping in `.butcher` so future regenerations keep the fix.
## Documentation work
For docs changes:
```bash
uv run --extra docs sphinx-autobuild --watch aiogram/ --watch CHANGES.rst --watch README.rst docs/ docs/_build/
```
`sphinx-autobuild` is long-running by design.
Or quick build:
```bash
uv run --extra docs bash -c 'cd docs && make html'
```
## PR quality checklist
Before requesting review:
1. Tests added/updated for behavior changes.
2. Local lint/type/tests pass.
3. Changelog fragment added (or `skip news` is justified).
4. If codegen-related: generated files and source config are both updated coherently.
5. PR body includes clear reproduction/validation steps.

View file

@ -16,6 +16,43 @@ Changelog
.. towncrier release notes start
3.25.0 (2026-03-03)
====================
Bugfixes
--------
- Fixed scene transitions to preserve middleware-injected data when moving between scenes via ``SceneWizard.goto``.
`#1687 <https://github.com/aiogram/aiogram/issues/1687>`_
- Added ``icon_custom_emoji_id`` and ``style`` parameters to ``InlineKeyboardBuilder.button`` and ``ReplyKeyboardBuilder.button`` signatures.
`#1768 <https://github.com/aiogram/aiogram/issues/1768>`_
- Fixed Pydantic protected namespace warning for `model_custom_emoji_id` by adding `protected_namespaces=()` to `model_config`.
`#1772 <https://github.com/aiogram/aiogram/issues/1772>`_
Misc
----
- Documented webhook security constraints for proxy deployments, including trust requirements for :code:`X-Forwarded-For` and recommended defense-in-depth checks.
`#47 <https://github.com/aiogram/aiogram/issues/47>`_
- Updated to `Bot API 9.5 <https://core.telegram.org/bots/api-changelog#march-1-2026>`_
**New Methods:**
- Added :class:`aiogram.methods.send_message_draft.SendMessageDraft` method - allowed for all bots to stream partial messages while they are being generated
- Added :class:`aiogram.methods.set_chat_member_tag.SetChatMemberTag` method - allows bots to set a custom tag for a chat member; available via :meth:`aiogram.types.chat.Chat.set_member_tag` shortcut
**New Fields:**
- Added :code:`date_time` type to :class:`aiogram.types.message_entity.MessageEntity` with :code:`unix_time` and :code:`date_time_format` fields - allows bots to display a formatted date and time to the user
- Added :code:`tag` field to :class:`aiogram.types.chat_member_member.ChatMemberMember` and :class:`aiogram.types.chat_member_restricted.ChatMemberRestricted` - the custom tag set for the chat member
- Added :code:`can_edit_tag` field to :class:`aiogram.types.chat_member_restricted.ChatMemberRestricted` and :class:`aiogram.types.chat_permissions.ChatPermissions` - indicates whether the user is allowed to edit their own tag
- Added :code:`can_manage_tags` field to :class:`aiogram.types.chat_member_administrator.ChatMemberAdministrator` and :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` - indicates whether the administrator can manage tags of other chat members
- Added :code:`can_manage_tags` parameter to :class:`aiogram.methods.promote_chat_member.PromoteChatMember` method
- Added :code:`sender_tag` field to :class:`aiogram.types.message.Message` - the tag of the message sender in the chat
`#1780 <https://github.com/aiogram/aiogram/issues/1780>`_
3.25.0 (2026-02-10)
====================

3
CLAUDE.md Normal file
View file

@ -0,0 +1,3 @@
# CLAUDE.md
Use @AGENTS.md as the source of truth for contribution workflow, checks, and Bot API codegen rules in this repository.

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.4 <https://core.telegram.org/bots/api>`_ and gets fast updates to the latest versions of the Bot API
- Supports `Telegram Bot API 9.5 <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.25.0"
__api_version__ = "9.4"
__version__ = "3.26.0"
__api_version__ = "9.5"

View file

@ -144,6 +144,7 @@ from ..methods import (
SetBusinessAccountUsername,
SetChatAdministratorCustomTitle,
SetChatDescription,
SetChatMemberTag,
SetChatMenuButton,
SetChatPermissions,
SetChatPhoto,
@ -2021,6 +2022,7 @@ class Bot:
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
request_timeout: int | None = None,
) -> bool:
"""
@ -2046,6 +2048,7 @@ class Bot:
:param can_pin_messages: Pass :code:`True` if the administrator can pin messages; for supergroups only
:param can_manage_topics: Pass :code:`True` if the user is allowed to create, rename, close, and reopen forum topics; for supergroups only
:param can_manage_direct_messages: Pass :code:`True` if the administrator can manage direct messages within the channel and decline suggested posts; for channels only
:param can_manage_tags: Pass :code:`True` if the administrator can edit the tags of regular members; for groups and supergroups only
:param request_timeout: Request timeout
:return: Returns :code:`True` on success.
"""
@ -2069,6 +2072,7 @@ class Bot:
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
)
return await self(call, request_timeout=request_timeout)
@ -5560,7 +5564,7 @@ class Bot:
:param chat_id: Unique identifier for the target chat
:param message_id: Unique identifier for the target message
:param checklist: A JSON-serialized object for the new checklist
:param reply_markup: A JSON-serialized object for the new inline keyboard for the message
:param reply_markup: A JSON-serialized object for the new `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ for the message
:param request_timeout: Request timeout
:return: On success, the edited :class:`aiogram.types.message.Message` is returned.
"""
@ -5614,7 +5618,7 @@ class Bot:
:param protect_content: Protects the contents of the sent message from forwarding and saving
:param message_effect_id: Unique identifier of the message effect to be added to the message
:param reply_parameters: A JSON-serialized object for description of the message to reply to
:param reply_markup: A JSON-serialized object for an inline keyboard
:param reply_markup: A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_
:param request_timeout: Request timeout
:return: On success, the sent :class:`aiogram.types.message.Message` is returned.
"""
@ -5823,7 +5827,7 @@ class Bot:
request_timeout: int | None = None,
) -> bool:
"""
Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns :code:`True` on success.
Use this method to stream a partial message to a user while the message is being generated. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#sendmessagedraft
@ -5908,3 +5912,29 @@ class Bot:
photo=photo,
)
return await self(call, request_timeout=request_timeout)
async def set_chat_member_tag(
self,
chat_id: ChatIdUnion,
user_id: int,
tag: str | None = None,
request_timeout: int | None = None,
) -> bool:
"""
Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the *can_manage_tags* administrator right. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#setchatmembertag
:param chat_id: Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)
:param user_id: Unique identifier of the target user
:param tag: New tag for the member; 0-16 characters, emoji are not allowed
:param request_timeout: Request timeout
:return: Returns :code:`True` on success.
"""
call = SetChatMemberTag(
chat_id=chat_id,
user_id=user_id,
tag=tag,
)
return await self(call, request_timeout=request_timeout)

View file

@ -27,3 +27,4 @@ class MessageEntityType(str, Enum):
TEXT_LINK = "text_link"
TEXT_MENTION = "text_mention"
CUSTOM_EMOJI = "custom_emoji"
DATE_TIME = "date_time"

View file

@ -259,6 +259,7 @@ class SceneHandlerWrapper:
)
raise SceneException(msg) from None
event_update: Update = kwargs["event_update"]
scenes.data = {**scenes.data, **kwargs}
scene = self.scene(
wizard=SceneWizard(
scene_config=self.scene.__scene_config__,
@ -712,6 +713,9 @@ class ScenesManager:
:param kwargs: Additional keyword arguments to pass to the scene's wizard.enter() method.
:return: None
"""
if kwargs:
self.data = {**self.data, **kwargs}
if _check_active:
active_scene = await self._get_active_scene()
if active_scene is not None:

View file

@ -126,6 +126,7 @@ from .set_business_account_profile_photo import SetBusinessAccountProfilePhoto
from .set_business_account_username import SetBusinessAccountUsername
from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
from .set_chat_description import SetChatDescription
from .set_chat_member_tag import SetChatMemberTag
from .set_chat_menu_button import SetChatMenuButton
from .set_chat_permissions import SetChatPermissions
from .set_chat_photo import SetChatPhoto
@ -295,6 +296,7 @@ __all__ = (
"SetBusinessAccountUsername",
"SetChatAdministratorCustomTitle",
"SetChatDescription",
"SetChatMemberTag",
"SetChatMenuButton",
"SetChatPermissions",
"SetChatPhoto",

View file

@ -25,7 +25,7 @@ class EditMessageChecklist(TelegramMethod[Message]):
checklist: InputChecklist
"""A JSON-serialized object for the new checklist"""
reply_markup: InlineKeyboardMarkup | None = None
"""A JSON-serialized object for the new inline keyboard for the message"""
"""A JSON-serialized object for the new `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ for the message"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!

View file

@ -52,6 +52,8 @@ class PromoteChatMember(TelegramMethod[bool]):
"""Pass :code:`True` if the user is allowed to create, rename, close, and reopen forum topics; for supergroups only"""
can_manage_direct_messages: bool | None = None
"""Pass :code:`True` if the administrator can manage direct messages within the channel and decline suggested posts; for channels only"""
can_manage_tags: bool | None = None
"""Pass :code:`True` if the administrator can edit the tags of regular members; for groups and supergroups only"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -78,6 +80,7 @@ class PromoteChatMember(TelegramMethod[bool]):
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -103,5 +106,6 @@ class PromoteChatMember(TelegramMethod[bool]):
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
**__pydantic_kwargs,
)

View file

@ -31,7 +31,7 @@ class SendChecklist(TelegramMethod[Message]):
reply_parameters: ReplyParameters | None = None
"""A JSON-serialized object for description of the message to reply to"""
reply_markup: InlineKeyboardMarkup | None = None
"""A JSON-serialized object for an inline keyboard"""
"""A JSON-serialized object for an `inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!

View file

@ -8,7 +8,7 @@ from .base import TelegramMethod
class SendMessageDraft(TelegramMethod[bool]):
"""
Use this method to stream a partial message to a user while the message is being generated; supported only for bots with forum topic mode enabled. Returns :code:`True` on success.
Use this method to stream a partial message to a user while the message is being generated. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#sendmessagedraft
"""

View file

@ -0,0 +1,40 @@
from typing import TYPE_CHECKING, Any
from ..types import ChatIdUnion
from .base import TelegramMethod
class SetChatMemberTag(TelegramMethod[bool]):
"""
Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the *can_manage_tags* administrator right. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#setchatmembertag
"""
__returning__ = bool
__api_method__ = "setChatMemberTag"
chat_id: ChatIdUnion
"""Unique identifier for the target chat or username of the target supergroup (in the format :code:`@supergroupusername`)"""
user_id: int
"""Unique identifier of the target user"""
tag: str | None = None
"""New tag for the member; 0-16 characters, emoji are not allowed"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
# This section was auto-generated via `butcher`
def __init__(
__pydantic__self__,
*,
chat_id: ChatIdUnion,
user_id: int,
tag: str | 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__(chat_id=chat_id, user_id=user_id, tag=tag, **__pydantic_kwargs)

View file

@ -16,6 +16,7 @@ class TelegramObject(BotContextController, BaseModel):
populate_by_name=True,
arbitrary_types_allowed=True,
defer_build=True,
protected_namespaces=(),
)
@model_validator(mode="before")

View file

@ -28,6 +28,7 @@ if TYPE_CHECKING:
SendChatAction,
SetChatAdministratorCustomTitle,
SetChatDescription,
SetChatMemberTag,
SetChatPermissions,
SetChatPhoto,
SetChatStickerSet,
@ -967,6 +968,38 @@ class Chat(TelegramObject):
**kwargs,
).as_(self._bot)
def set_member_tag(
self,
user_id: int,
tag: str | None = None,
**kwargs: Any,
) -> SetChatMemberTag:
"""
Shortcut for method :class:`aiogram.methods.set_chat_member_tag.SetChatMemberTag`
will automatically fill method attributes:
- :code:`chat_id`
Use this method to set a tag for a regular member in a group or a supergroup. The bot must be an administrator in the chat for this to work and must have the *can_manage_tags* administrator right. Returns :code:`True` on success.
Source: https://core.telegram.org/bots/api#setchatmembertag
:param user_id: Unique identifier of the target user
:param tag: New tag for the member; 0-16 characters, emoji are not allowed
:return: instance of method :class:`aiogram.methods.set_chat_member_tag.SetChatMemberTag`
"""
# DO NOT EDIT MANUALLY!!!
# This method was auto-generated via `butcher`
from aiogram.methods import SetChatMemberTag
return SetChatMemberTag(
chat_id=self.id,
user_id=user_id,
tag=tag,
**kwargs,
).as_(self._bot)
def set_permissions(
self,
permissions: ChatPermissions,
@ -1018,6 +1051,7 @@ class Chat(TelegramObject):
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
**kwargs: Any,
) -> PromoteChatMember:
"""
@ -1047,6 +1081,7 @@ class Chat(TelegramObject):
:param can_pin_messages: Pass :code:`True` if the administrator can pin messages; for supergroups only
:param can_manage_topics: Pass :code:`True` if the user is allowed to create, rename, close, and reopen forum topics; for supergroups only
:param can_manage_direct_messages: Pass :code:`True` if the administrator can manage direct messages within the channel and decline suggested posts; for channels only
:param can_manage_tags: Pass :code:`True` if the administrator can edit the tags of regular members; for groups and supergroups only
:return: instance of method :class:`aiogram.methods.promote_chat_member.PromoteChatMember`
"""
# DO NOT EDIT MANUALLY!!!
@ -1073,6 +1108,7 @@ class Chat(TelegramObject):
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
**kwargs,
).as_(self._bot)

View file

@ -47,6 +47,8 @@ class ChatAdministratorRights(TelegramObject):
"""*Optional*. :code:`True`, if the user is allowed to create, rename, close, and reopen forum topics; for supergroups only"""
can_manage_direct_messages: bool | None = None
"""*Optional*. :code:`True`, if the administrator can manage direct messages of the channel and decline suggested posts; for channels only"""
can_manage_tags: bool | None = None
"""*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -71,6 +73,7 @@ class ChatAdministratorRights(TelegramObject):
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -94,5 +97,6 @@ class ChatAdministratorRights(TelegramObject):
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
**__pydantic_kwargs,
)

View file

@ -54,6 +54,8 @@ class ChatMemberAdministrator(ChatMember):
"""*Optional*. :code:`True`, if the user is allowed to create, rename, close, and reopen forum topics; for supergroups only"""
can_manage_direct_messages: bool | None = None
"""*Optional*. :code:`True`, if the administrator can manage direct messages of the channel and decline suggested posts; for channels only"""
can_manage_tags: bool | None = None
"""*Optional*. :code:`True`, if the administrator can edit the tags of regular members; for groups and supergroups only. If omitted defaults to the value of can_pin_messages."""
custom_title: str | None = None
"""*Optional*. Custom title for this user"""
@ -83,6 +85,7 @@ class ChatMemberAdministrator(ChatMember):
can_pin_messages: bool | None = None,
can_manage_topics: bool | None = None,
can_manage_direct_messages: bool | None = None,
can_manage_tags: bool | None = None,
custom_title: str | None = None,
**__pydantic_kwargs: Any,
) -> None:
@ -110,6 +113,7 @@ class ChatMemberAdministrator(ChatMember):
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
can_manage_direct_messages=can_manage_direct_messages,
can_manage_tags=can_manage_tags,
custom_title=custom_title,
**__pydantic_kwargs,
)

View file

@ -21,6 +21,8 @@ class ChatMemberMember(ChatMember):
"""The member's status in the chat, always 'member'"""
user: User
"""Information about the user"""
tag: str | None = None
"""*Optional*. Tag of the member"""
until_date: DateTime | None = None
"""*Optional*. Date when the user's subscription will expire; Unix time"""
@ -33,6 +35,7 @@ class ChatMemberMember(ChatMember):
*,
status: Literal[ChatMemberStatus.MEMBER] = ChatMemberStatus.MEMBER,
user: User,
tag: str | None = None,
until_date: DateTime | None = None,
**__pydantic_kwargs: Any,
) -> None:
@ -40,4 +43,6 @@ class ChatMemberMember(ChatMember):
# This method was auto-generated via `butcher`
# Is needed only for type checking and IDE support without any additional plugins
super().__init__(status=status, user=user, until_date=until_date, **__pydantic_kwargs)
super().__init__(
status=status, user=user, tag=tag, until_date=until_date, **__pydantic_kwargs
)

View file

@ -43,6 +43,8 @@ class ChatMemberRestricted(ChatMember):
""":code:`True`, if the user is allowed to send animations, games, stickers and use inline bots"""
can_add_web_page_previews: bool
""":code:`True`, if the user is allowed to add web page previews to their messages"""
can_edit_tag: bool
""":code:`True`, if the user is allowed to edit their own tag"""
can_change_info: bool
""":code:`True`, if the user is allowed to change the chat title, photo and other settings"""
can_invite_users: bool
@ -53,6 +55,8 @@ class ChatMemberRestricted(ChatMember):
""":code:`True`, if the user is allowed to create forum topics"""
until_date: DateTime
"""Date when restrictions will be lifted for this user; Unix time. If 0, then the user is restricted forever"""
tag: str | None = None
"""*Optional*. Tag of the member"""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -74,11 +78,13 @@ class ChatMemberRestricted(ChatMember):
can_send_polls: bool,
can_send_other_messages: bool,
can_add_web_page_previews: bool,
can_edit_tag: bool,
can_change_info: bool,
can_invite_users: bool,
can_pin_messages: bool,
can_manage_topics: bool,
until_date: DateTime,
tag: str | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -99,10 +105,12 @@ class ChatMemberRestricted(ChatMember):
can_send_polls=can_send_polls,
can_send_other_messages=can_send_other_messages,
can_add_web_page_previews=can_add_web_page_previews,
can_edit_tag=can_edit_tag,
can_change_info=can_change_info,
can_invite_users=can_invite_users,
can_pin_messages=can_pin_messages,
can_manage_topics=can_manage_topics,
until_date=until_date,
tag=tag,
**__pydantic_kwargs,
)

View file

@ -32,6 +32,8 @@ class ChatPermissions(MutableTelegramObject):
"""*Optional*. :code:`True`, if the user is allowed to send animations, games, stickers and use inline bots"""
can_add_web_page_previews: bool | None = None
"""*Optional*. :code:`True`, if the user is allowed to add web page previews to their messages"""
can_edit_tag: bool | None = None
"""*Optional*. :code:`True`, if the user is allowed to edit their own tag"""
can_change_info: bool | None = None
"""*Optional*. :code:`True`, if the user is allowed to change the chat title, photo and other settings. Ignored in public supergroups"""
can_invite_users: bool | None = None
@ -58,6 +60,7 @@ class ChatPermissions(MutableTelegramObject):
can_send_polls: bool | None = None,
can_send_other_messages: bool | None = None,
can_add_web_page_previews: bool | None = None,
can_edit_tag: bool | None = None,
can_change_info: bool | None = None,
can_invite_users: bool | None = None,
can_pin_messages: bool | None = None,
@ -79,6 +82,7 @@ class ChatPermissions(MutableTelegramObject):
can_send_polls=can_send_polls,
can_send_other_messages=can_send_other_messages,
can_add_web_page_previews=can_add_web_page_previews,
can_edit_tag=can_edit_tag,
can_change_info=can_change_info,
can_invite_users=can_invite_users,
can_pin_messages=can_pin_messages,

View file

@ -15,8 +15,6 @@ 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

@ -38,7 +38,7 @@ class InlineQueryResultDocument(InlineQueryResult):
description: str | None = None
"""*Optional*. Short description of the result"""
reply_markup: InlineKeyboardMarkup | None = None
"""*Optional*. Inline keyboard attached to the message"""
"""*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message"""
input_message_content: InputMessageContentUnion | None = None
"""*Optional*. Content of the message to be sent instead of the file"""
thumbnail_url: str | None = None

View file

@ -157,6 +157,8 @@ class Message(MaybeInaccessibleMessage):
"""*Optional*. If the sender of the message boosted the chat, the number of boosts added by the user"""
sender_business_bot: User | None = None
"""*Optional*. The bot that actually sent the message on behalf of the business account. Available only for outgoing messages sent on behalf of the connected business account."""
sender_tag: str | None = None
"""*Optional*. Tag or custom title of the sender of the message; for supergroups only"""
business_connection_id: str | None = None
"""*Optional*. Unique identifier of the business connection from which the message was received. If non-empty, the message belongs to a chat of the corresponding business account that is independent from any potential bot chat which might share the same identifier."""
forward_origin: MessageOriginUnion | None = None
@ -186,7 +188,7 @@ class Message(MaybeInaccessibleMessage):
is_paid_post: bool | None = None
"""*Optional*. :code:`True`, if the message is a paid post. Note that such posts must not be deleted for 24 hours to receive the payment and can't be edited."""
media_group_id: str | None = None
"""*Optional*. The unique identifier of a media message group this message belongs to"""
"""*Optional*. The unique identifier inside this chat of a media message group this message belongs to"""
author_signature: str | None = None
"""*Optional*. Signature of the post author for messages in channels, or the custom title of an anonymous group administrator"""
paid_star_count: int | None = None
@ -348,7 +350,7 @@ class Message(MaybeInaccessibleMessage):
web_app_data: WebAppData | None = None
"""*Optional*. Service message: data sent by a Web App"""
reply_markup: InlineKeyboardMarkup | None = None
"""*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons."""
"""*Optional*. `Inline keyboard <https://core.telegram.org/bots/features#inline-keyboards>`_ attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons."""
forward_date: DateTime | None = Field(None, json_schema_extra={"deprecated": True})
"""*Optional*. For forwarded messages, date the original message was sent in Unix time
@ -401,6 +403,7 @@ class Message(MaybeInaccessibleMessage):
sender_chat: Chat | None = None,
sender_boost_count: int | None = None,
sender_business_bot: User | None = None,
sender_tag: str | None = None,
business_connection_id: str | None = None,
forward_origin: MessageOriginUnion | None = None,
is_topic_message: bool | None = None,
@ -520,6 +523,7 @@ class Message(MaybeInaccessibleMessage):
sender_chat=sender_chat,
sender_boost_count=sender_boost_count,
sender_business_bot=sender_business_bot,
sender_tag=sender_tag,
business_connection_id=business_connection_id,
forward_origin=forward_origin,
is_topic_message=is_topic_message,

View file

@ -17,7 +17,7 @@ class MessageEntity(MutableTelegramObject):
"""
type: str
"""Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers)"""
"""Type of the entity. Currently, can be 'mention' (:code:`@username`), 'hashtag' (:code:`#hashtag` or :code:`#hashtag@chatusername`), 'cashtag' (:code:`$USD` or :code:`$USD@chatusername`), 'bot_command' (:code:`/start@jobs_bot`), 'url' (:code:`https://telegram.org`), 'email' (:code:`do-not-reply@telegram.org`), 'phone_number' (:code:`+1-212-555-0123`), 'bold' (**bold text**), 'italic' (*italic text*), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'spoiler' (spoiler message), 'blockquote' (block quotation), 'expandable_blockquote' (collapsed-by-default block quotation), 'code' (monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs), 'text_mention' (for users `without usernames <https://telegram.org/blog/edit#new-mentions>`_), 'custom_emoji' (for inline custom emoji stickers), or 'date_time' (for formatted date and time)"""
offset: int
"""Offset in `UTF-16 code units <https://core.telegram.org/api/entities#entity-length>`_ to the start of the entity"""
length: int
@ -30,6 +30,10 @@ class MessageEntity(MutableTelegramObject):
"""*Optional*. For 'pre' only, the programming language of the entity text"""
custom_emoji_id: str | None = None
"""*Optional*. For 'custom_emoji' only, unique identifier of the custom emoji. Use :class:`aiogram.methods.get_custom_emoji_stickers.GetCustomEmojiStickers` to get full information about the sticker"""
unix_time: int | None = None
"""*Optional*. For 'date_time' only, the Unix time associated with the entity"""
date_time_format: str | None = None
"""*Optional*. For 'date_time' only, the string that defines the formatting of the date and time. See `date-time entity formatting <https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details."""
if TYPE_CHECKING:
# DO NOT EDIT MANUALLY!!!
@ -45,6 +49,8 @@ class MessageEntity(MutableTelegramObject):
user: User | None = None,
language: str | None = None,
custom_emoji_id: str | None = None,
unix_time: int | None = None,
date_time_format: str | None = None,
**__pydantic_kwargs: Any,
) -> None:
# DO NOT EDIT MANUALLY!!!
@ -59,6 +65,8 @@ class MessageEntity(MutableTelegramObject):
user=user,
language=language,
custom_emoji_id=custom_emoji_id,
unix_time=unix_time,
date_time_format=date_time_format,
**__pydantic_kwargs,
)

View file

@ -2,6 +2,7 @@ from __future__ import annotations
import textwrap
from collections.abc import Generator, Iterable, Iterator
from datetime import datetime
from typing import Any, ClassVar
from typing_extensions import Self
@ -534,6 +535,26 @@ class ExpandableBlockQuote(Text):
type = MessageEntityType.EXPANDABLE_BLOCKQUOTE
class DateTime(Text):
type = MessageEntityType.DATE_TIME
def __init__(
self,
*body: NodeType,
unix_time: int | datetime,
date_time_format: str | None = None,
**params: Any,
) -> None:
if isinstance(unix_time, datetime):
unix_time = int(unix_time.timestamp())
super().__init__(
*body,
unix_time=unix_time,
date_time_format=date_time_format,
**params,
)
NODE_TYPES: dict[str | None, type[Text]] = {
Text.type: Text,
HashTag.type: HashTag,
@ -554,6 +575,7 @@ NODE_TYPES: dict[str | None, type[Text]] = {
CustomEmoji.type: CustomEmoji,
BlockQuote.type: BlockQuote,
ExpandableBlockQuote.type: ExpandableBlockQuote,
DateTime.type: DateTime,
}

View file

@ -303,6 +303,8 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
self,
*,
text: str,
icon_custom_emoji_id: str | None = None,
style: str | None = None,
url: str | None = None,
callback_data: str | CallbackData | None = None,
web_app: WebAppInfo | None = None,
@ -319,6 +321,8 @@ class InlineKeyboardBuilder(KeyboardBuilder[InlineKeyboardButton]):
InlineKeyboardBuilder,
self._button(
text=text,
icon_custom_emoji_id=icon_custom_emoji_id,
style=style,
url=url,
callback_data=callback_data,
web_app=web_app,
@ -375,6 +379,8 @@ class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]):
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,
@ -387,6 +393,8 @@ class ReplyKeyboardBuilder(KeyboardBuilder[KeyboardButton]):
ReplyKeyboardBuilder,
self._button(
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

@ -3,9 +3,11 @@ from __future__ import annotations
import html
import re
from abc import ABC, abstractmethod
from datetime import date, datetime, time
from typing import TYPE_CHECKING, cast
from aiogram.enums import MessageEntityType
from aiogram.utils.link import create_tg_link
if TYPE_CHECKING:
from collections.abc import Generator
@ -78,6 +80,12 @@ class TextDecoration(ABC):
return self.link(value=text, link=cast(str, entity.url))
if entity.type == MessageEntityType.CUSTOM_EMOJI:
return self.custom_emoji(value=text, custom_emoji_id=cast(str, entity.custom_emoji_id))
if entity.type == MessageEntityType.DATE_TIME:
return self.date_time(
value=text,
unix_time=cast(int, entity.unix_time),
date_time_format=entity.date_time_format,
)
# This case is not possible because of `if` above, but if any new entity is added to
# API it will be here too
@ -180,54 +188,105 @@ class TextDecoration(ABC):
def expandable_blockquote(self, value: str) -> str:
pass
@abstractmethod
def date_time(
self,
value: str,
unix_time: int | datetime,
date_time_format: str | None = None,
) -> str:
pass
class HtmlDecoration(TextDecoration):
BOLD_TAG = "b"
ITALIC_TAG = "i"
UNDERLINE_TAG = "u"
STRIKETHROUGH_TAG = "s"
CODE_TAG = "code"
PRE_TAG = "pre"
LINK_TAG = "a"
SPOILER_TAG = "tg-spoiler"
EMOJI_TAG = "tg-emoji"
DATE_TIME_TAG = "tg-time"
BLOCKQUOTE_TAG = "blockquote"
def _tag(
self,
tag: str,
content: str,
*,
attrs: dict[str, str] | None = None,
flags: list[str] | None = None,
) -> str:
prepared_attrs: list[str] = []
if attrs:
prepared_attrs.extend(f'{k}="{v}"' for k, v in attrs.items())
if flags:
prepared_attrs.extend(f"{flag}" for flag in flags)
attrs_str = " ".join(prepared_attrs)
if attrs_str:
attrs_str = " " + attrs_str
return f"<{tag}{attrs_str}>{content}</{tag}>"
def link(self, value: str, link: str) -> str:
return f'<a href="{link}">{value}</a>'
return self._tag(self.LINK_TAG, value, attrs={"href": link})
def bold(self, value: str) -> str:
return f"<{self.BOLD_TAG}>{value}</{self.BOLD_TAG}>"
return self._tag(self.BOLD_TAG, value)
def italic(self, value: str) -> str:
return f"<{self.ITALIC_TAG}>{value}</{self.ITALIC_TAG}>"
return self._tag(self.ITALIC_TAG, value)
def code(self, value: str) -> str:
return f"<code>{value}</code>"
return self._tag(self.CODE_TAG, value)
def pre(self, value: str) -> str:
return f"<pre>{value}</pre>"
return self._tag(self.PRE_TAG, value)
def pre_language(self, value: str, language: str) -> str:
return f'<pre><code class="language-{language}">{value}</code></pre>'
return self._tag(
self.PRE_TAG,
self._tag(self.CODE_TAG, value, attrs={"language": f"language-{language}"}),
)
def underline(self, value: str) -> str:
return f"<{self.UNDERLINE_TAG}>{value}</{self.UNDERLINE_TAG}>"
return self._tag(self.UNDERLINE_TAG, value)
def strikethrough(self, value: str) -> str:
return f"<{self.STRIKETHROUGH_TAG}>{value}</{self.STRIKETHROUGH_TAG}>"
return self._tag(self.STRIKETHROUGH_TAG, value)
def spoiler(self, value: str) -> str:
return f"<{self.SPOILER_TAG}>{value}</{self.SPOILER_TAG}>"
return self._tag(self.SPOILER_TAG, value)
def quote(self, value: str) -> str:
return html.escape(value, quote=False)
def custom_emoji(self, value: str, custom_emoji_id: str) -> str:
return f'<{self.EMOJI_TAG} emoji-id="{custom_emoji_id}">{value}</{self.EMOJI_TAG}>'
return self._tag(self.EMOJI_TAG, value, attrs={"emoji_id": custom_emoji_id})
def blockquote(self, value: str) -> str:
return f"<{self.BLOCKQUOTE_TAG}>{value}</{self.BLOCKQUOTE_TAG}>"
return self._tag(self.BLOCKQUOTE_TAG, value)
def expandable_blockquote(self, value: str) -> str:
return f"<{self.BLOCKQUOTE_TAG} expandable>{value}</{self.BLOCKQUOTE_TAG}>"
return self._tag(self.BLOCKQUOTE_TAG, value, flags=["expandable"])
def date_time(
self,
value: str,
unix_time: int | datetime,
date_time_format: str | None = None,
) -> str:
if isinstance(unix_time, datetime):
unix_time = int(unix_time.timestamp())
args = {"unix": str(unix_time)}
if date_time_format:
args["format"] = date_time_format
return self._tag(self.DATE_TIME_TAG, value, attrs=args)
class MarkdownDecoration(TextDecoration):
@ -264,7 +323,8 @@ class MarkdownDecoration(TextDecoration):
return re.sub(pattern=self.MARKDOWN_QUOTE_PATTERN, repl=r"\\\1", string=value)
def custom_emoji(self, value: str, custom_emoji_id: str) -> str:
return f"!{self.link(value=value, link=f'tg://emoji?id={custom_emoji_id}')}"
link = create_tg_link("emoji", emoji_id=custom_emoji_id)
return f"!{self.link(value=value, link=link)}"
def blockquote(self, value: str) -> str:
return "\n".join(f">{line}" for line in value.splitlines())
@ -272,6 +332,22 @@ class MarkdownDecoration(TextDecoration):
def expandable_blockquote(self, value: str) -> str:
return "\n".join(f">{line}" for line in value.splitlines()) + "||"
def date_time(
self,
value: str,
unix_time: int | datetime,
date_time_format: str | None = None,
) -> str:
if isinstance(unix_time, datetime):
unix_time = int(unix_time.timestamp())
link_params = {"unix": str(unix_time)}
if date_time_format:
link_params["format"] = date_time_format
link = create_tg_link("time", **link_params)
return f"!{self.link(value, link=link)}"
html_decoration = HtmlDecoration()
markdown_decoration = MarkdownDecoration()

View file

@ -36,3 +36,11 @@ With specific bot
.. code-block:: python
result: UserProfileAudios = await bot(GetUserProfileAudios(...))
As shortcut from received object
--------------------------------
- :meth:`aiogram.types.user.User.get_profile_audios`

View file

@ -127,6 +127,7 @@ Available methods
set_business_account_username
set_chat_administrator_custom_title
set_chat_description
set_chat_member_tag
set_chat_menu_button
set_chat_permissions
set_chat_photo

View file

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

View file

@ -66,6 +66,19 @@ It can be acy using firewall rules or nginx configuration or middleware on appli
So, aiogram has an implementation of the IP filtering middleware for aiohttp.
`aiogram` IP filtering middleware reads the left-most IP address from `X-Forwarded-For`.
.. warning::
`X-Forwarded-For` is trustworthy only if all webhook traffic goes through a trusted reverse proxy that rewrites this header.
If your application is directly reachable from the Internet, this header can be forged.
For production deployments, use defense in depth:
- Always set and verify :code:`X-Telegram-Bot-Api-Secret-Token`
- Restrict network access to the webhook endpoint (firewall, security groups, ACL)
- Ensure the backend app is not publicly reachable and accepts requests only from the trusted proxy
.. autofunction:: aiogram.webhook.aiohttp_server.ip_filter_middleware
.. autoclass:: aiogram.webhook.security.IPFilter

View file

@ -203,45 +203,95 @@ msgstr ""
#: ../../dispatcher/webhook.rst:51
msgid "Security"
msgstr ""
msgstr "Безпека"
#: ../../dispatcher/webhook.rst:53
msgid ""
"Telegram supports two methods to verify incoming requests that they are "
"from Telegram:"
msgstr ""
msgstr "Telegram підтримує два методи перевірки вхідних запитів, що вони надходять від Telegram:"
#: ../../dispatcher/webhook.rst:56
msgid "Using a secret token"
msgstr ""
msgstr "Використання секретного токена"
#: ../../dispatcher/webhook.rst:58
msgid ""
"When you set webhook, you can specify a secret token and then use it to "
"verify incoming requests."
msgstr ""
"Коли ви налаштовуєте webhook, ви можете вказати секретний токен і потім "
"використовувати його для перевірки вхідних запитів."
#: ../../dispatcher/webhook.rst:61
msgid "Using IP filtering"
msgstr ""
msgstr "Використання фільтрації за IP"
#: ../../dispatcher/webhook.rst:63
msgid ""
"You can specify a list of IP addresses from which you expect incoming "
"requests, and then use it to verify incoming requests."
msgstr ""
"Ви можете вказати список IP-адрес, з яких очікуєте вхідні запити, і "
"використовувати його для перевірки запитів."
#: ../../dispatcher/webhook.rst:65
msgid ""
"It can be acy using firewall rules or nginx configuration or middleware "
"on application level."
msgstr ""
"Це можна зробити за допомогою правил firewall, конфігурації nginx або "
"middleware на рівні застосунку."
#: ../../dispatcher/webhook.rst:67
msgid ""
"So, aiogram has an implementation of the IP filtering middleware for "
"aiohttp."
msgstr ""
"Тому в aiogram є реалізація middleware для фільтрації за IP для aiohttp."
#: ../../dispatcher/webhook.rst:69
msgid ""
"`aiogram` IP filtering middleware reads the left-most IP address from "
"`X-Forwarded-For`."
msgstr ""
"IP-фільтр middleware в `aiogram` читає крайню ліву IP-адресу з "
"`X-Forwarded-For`."
#: ../../dispatcher/webhook.rst:73
msgid ""
"`X-Forwarded-For` is trustworthy only if all webhook traffic goes through a"
" trusted reverse proxy that rewrites this header. If your application is "
"directly reachable from the Internet, this header can be forged."
msgstr ""
"`X-Forwarded-For` можна вважати надійним лише тоді, коли весь webhook-"
"трафік проходить через довірений reverse proxy, який перезаписує цей "
"заголовок. Якщо ваш застосунок напряму доступний з Інтернету, цей "
"заголовок можна підробити."
#: ../../dispatcher/webhook.rst:76
msgid "For production deployments, use defense in depth:"
msgstr "Для production-деплойментів використовуйте багаторівневий захист:"
#: ../../dispatcher/webhook.rst:78
msgid "Always set and verify :code:`X-Telegram-Bot-Api-Secret-Token`"
msgstr "Завжди встановлюйте та перевіряйте :code:`X-Telegram-Bot-Api-Secret-Token`"
#: ../../dispatcher/webhook.rst:79
msgid ""
"Restrict network access to the webhook endpoint (firewall, security "
"groups, ACL)"
msgstr ""
"Обмежуйте мережевий доступ до webhook endpoint (firewall, security groups, "
"ACL)"
#: ../../dispatcher/webhook.rst:80
msgid ""
"Ensure the backend app is not publicly reachable and accepts requests only "
"from the trusted proxy"
msgstr ""
"Переконайтеся, що backend-застосунок не доступний публічно та приймає "
"запити лише від довіреного proxy"
#: ../../dispatcher/webhook.rst:75
msgid "Examples"

View file

@ -6,6 +6,11 @@ class TestPromoteChatMember:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(PromoteChatMember, ok=True, result=True)
response: bool = await bot.promote_chat_member(chat_id=-42, user_id=42)
bot.get_request()
response: bool = await bot.promote_chat_member(
chat_id=-42,
user_id=42,
can_manage_tags=True,
)
request = bot.get_request()
assert request.can_manage_tags is True
assert response == prepare_result.result

View file

@ -0,0 +1,14 @@
from aiogram.methods import SetChatMemberTag
from tests.mocked_bot import MockedBot
class TestSetChatMemberTag:
async def test_bot_method(self, bot: MockedBot):
prepare_result = bot.add_result_for(SetChatMemberTag, ok=True, result=True)
response: bool = await bot.set_chat_member_tag(chat_id=-42, user_id=42, tag="test")
request = bot.get_request()
assert request.chat_id == -42
assert request.user_id == 42
assert request.tag == "test"
assert response == prepare_result.result

View file

@ -115,6 +115,14 @@ class TestChat:
method = chat.set_administrator_custom_title(user_id=1, custom_title="test")
assert method.chat_id == chat.id
def test_set_member_tag(self):
chat = Chat(id=-42, type="supergroup")
method = chat.set_member_tag(user_id=42, tag="test")
assert method.chat_id == chat.id
assert method.user_id == 42
assert method.tag == "test"
def test_set_permissions(self):
chat = Chat(id=-42, type="supergroup")

View file

@ -0,0 +1,84 @@
from datetime import datetime
from aiogram.types import (
ChatAdministratorRights,
ChatMemberAdministrator,
ChatMemberMember,
ChatMemberRestricted,
ChatPermissions,
User,
)
class TestChatMemberTagPermissions:
def test_chat_administrator_rights_can_manage_tags(self):
rights = ChatAdministratorRights(
is_anonymous=False,
can_manage_chat=True,
can_delete_messages=True,
can_manage_video_chats=True,
can_restrict_members=True,
can_promote_members=True,
can_change_info=True,
can_invite_users=True,
can_post_stories=True,
can_edit_stories=True,
can_delete_stories=True,
can_manage_tags=True,
)
assert rights.can_manage_tags is True
def test_chat_member_administrator_can_manage_tags(self):
admin = ChatMemberAdministrator(
user=User(id=42, is_bot=False, first_name="User"),
can_be_edited=True,
is_anonymous=False,
can_manage_chat=True,
can_delete_messages=True,
can_manage_video_chats=True,
can_restrict_members=True,
can_promote_members=True,
can_change_info=True,
can_invite_users=True,
can_post_stories=True,
can_edit_stories=True,
can_delete_stories=True,
can_manage_tags=True,
)
assert admin.can_manage_tags is True
def test_chat_permissions_can_edit_tag(self):
permissions = ChatPermissions(can_edit_tag=True)
assert permissions.can_edit_tag is True
def test_chat_member_member_tag(self):
member = ChatMemberMember(
user=User(id=42, is_bot=False, first_name="User"),
tag="premium",
)
assert member.tag == "premium"
def test_chat_member_restricted_can_edit_tag_and_tag(self):
restricted = ChatMemberRestricted(
user=User(id=42, is_bot=False, first_name="User"),
is_member=True,
can_send_messages=True,
can_send_audios=True,
can_send_documents=True,
can_send_photos=True,
can_send_videos=True,
can_send_video_notes=True,
can_send_voice_notes=True,
can_send_polls=True,
can_send_other_messages=True,
can_add_web_page_previews=True,
can_edit_tag=True,
can_change_info=True,
can_invite_users=True,
can_pin_messages=True,
can_manage_topics=True,
until_date=datetime.now(),
tag="premium",
)
assert restricted.can_edit_tag is True
assert restricted.tag == "premium"

View file

@ -314,6 +314,7 @@ class TestChatMemberUpdatedStatusFilter:
"can_send_polls": True,
"can_send_other_messages": True,
"can_add_web_page_previews": True,
"can_edit_tag": True,
"can_post_stories": True,
"can_edit_stories": True,
"can_delete_stories": True,

View file

@ -253,6 +253,7 @@ class TestSceneHandlerWrapper:
state_mock = AsyncMock(spec=FSMContext)
scenes_mock = AsyncMock(spec=ScenesManager)
scenes_mock.data = {}
event_update_mock = Update(
update_id=42,
message=Message(
@ -282,6 +283,7 @@ class TestSceneHandlerWrapper:
state_mock = AsyncMock(spec=FSMContext)
scenes_mock = AsyncMock(spec=ScenesManager)
scenes_mock.data = {}
event_update_mock = Update(
update_id=42,
message=Message(

View file

@ -0,0 +1,106 @@
from collections.abc import Awaitable, Callable
from datetime import datetime
from typing import Any
from aiogram import BaseMiddleware, Dispatcher
from aiogram.enums import ChatType
from aiogram.filters import CommandStart
from aiogram.fsm.scene import After, Scene, SceneRegistry, on
from aiogram.types import Chat, Message, TelegramObject, Update, User
from tests.mocked_bot import MockedBot
class TestContextMiddleware(BaseMiddleware):
async def __call__(
self,
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
event: TelegramObject,
data: dict[str, Any],
) -> Any:
data["test_context"] = "context from middleware"
return await handler(event, data)
class TargetScene(Scene, state="target"):
entered_with_context: str | None = None
@on.message.enter()
async def on_enter(self, message: Message, test_context: str) -> None:
type(self).entered_with_context = test_context
class StartScene(Scene, state="start"):
@on.message.enter()
async def on_start(self, message: Message) -> None:
await self.wizard.goto(TargetScene)
class StartSceneWithAfter(Scene, state="start_with_after"):
@on.message(after=After.goto(TargetScene))
async def goto_target_with_after(self, message: Message) -> None:
pass
async def test_scene_goto_preserves_message_middleware_data(bot: MockedBot) -> None:
dp = Dispatcher()
registry = SceneRegistry(dp)
registry.add(StartScene, TargetScene)
dp.message.register(StartScene.as_handler(), CommandStart())
dp.message.middleware(TestContextMiddleware())
TargetScene.entered_with_context = None
update = Update(
update_id=1,
message=Message(
message_id=1,
date=datetime.now(),
chat=Chat(id=42, type=ChatType.PRIVATE),
from_user=User(id=42, is_bot=False, first_name="Test"),
text="/start",
),
)
await dp.feed_update(bot, update)
assert TargetScene.entered_with_context == "context from middleware"
async def test_scene_after_goto_preserves_message_middleware_data(bot: MockedBot) -> None:
dp = Dispatcher()
registry = SceneRegistry(dp)
registry.add(StartSceneWithAfter, TargetScene)
dp.message.register(StartSceneWithAfter.as_handler(), CommandStart())
dp.message.middleware(TestContextMiddleware())
TargetScene.entered_with_context = None
await dp.feed_update(
bot,
Update(
update_id=1,
message=Message(
message_id=1,
date=datetime.now(),
chat=Chat(id=42, type=ChatType.PRIVATE),
from_user=User(id=42, is_bot=False, first_name="Test"),
text="/start",
),
),
)
await dp.feed_update(
bot,
Update(
update_id=2,
message=Message(
message_id=2,
date=datetime.now(),
chat=Chat(id=42, type=ChatType.PRIVATE),
from_user=User(id=42, is_bot=False, first_name="Test"),
text="go",
),
),
)
assert TargetScene.entered_with_context == "context from middleware"

View file

@ -70,6 +70,7 @@ CHAT_MEMBER_RESTRICTED = ChatMemberRestricted(
can_send_polls=False,
can_send_other_messages=False,
can_add_web_page_previews=False,
can_edit_tag=False,
can_change_info=False,
can_invite_users=False,
can_pin_messages=False,

View file

@ -1,3 +1,5 @@
from datetime import datetime, timezone
import pytest
from aiogram.enums import MessageEntityType
@ -9,6 +11,7 @@ from aiogram.utils.formatting import (
CashTag,
Code,
CustomEmoji,
DateTime,
Email,
ExpandableBlockQuote,
HashTag,
@ -93,7 +96,7 @@ class TestNode:
],
[
Pre("test", language="python"),
'<pre><code class="language-python">test</code></pre>',
'<pre><code language="language-python">test</code></pre>',
],
[
TextLink("test", url="https://example.com"),
@ -105,7 +108,7 @@ class TestNode:
],
[
CustomEmoji("test", custom_emoji_id="42"),
'<tg-emoji emoji-id="42">test</tg-emoji>',
'<tg-emoji emoji_id="42">test</tg-emoji>',
],
[
BlockQuote("test"),
@ -115,6 +118,14 @@ class TestNode:
ExpandableBlockQuote("test"),
"<blockquote expandable>test</blockquote>",
],
[
DateTime("test", unix_time=42, date_time_format="yMd"),
'<tg-time unix="42" format="yMd">test</tg-time>',
],
[
DateTime("test", unix_time=42),
'<tg-time unix="42">test</tg-time>',
],
],
)
def test_render_plain_only(self, node: Text, result: str):
@ -358,6 +369,38 @@ class TestUtils:
assert isinstance(node, Bold)
assert node._body == ("test",)
def test_apply_entity_date_time(self):
node = _apply_entity(
MessageEntity(
type=MessageEntityType.DATE_TIME,
offset=0,
length=4,
unix_time=42,
date_time_format="yMd",
),
"test",
)
assert isinstance(node, DateTime)
assert node._body == ("test",)
assert node._params["unix_time"] == 42
assert node._params["date_time_format"] == "yMd"
def test_date_time_with_datetime_object(self):
dt = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
node = DateTime("test", unix_time=dt)
assert isinstance(node, DateTime)
assert node._params["unix_time"] == 1704067200
def test_date_time_with_datetime_and_format(self):
dt = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
node = DateTime("test", unix_time=dt, date_time_format="yMd")
assert node._params["unix_time"] == 1704067200
assert node._params["date_time_format"] == "yMd"
def test_date_time_as_markdown(self):
node = DateTime("test", unix_time=42, date_time_format="yMd")
assert node.as_markdown() == "![test](tg://time?unix=42&format=yMd)"
def test_as_line(self):
node = as_line("test", "test", "test")
assert isinstance(node, Text)

View file

@ -214,11 +214,63 @@ class TestKeyboardBuilder:
"builder_type,kwargs,expected",
[
[ReplyKeyboardBuilder, {"text": "test"}, KeyboardButton(text="test")],
[
ReplyKeyboardBuilder,
{"text": "test", "icon_custom_emoji_id": "emoji-id"},
KeyboardButton(text="test", icon_custom_emoji_id="emoji-id"),
],
[
ReplyKeyboardBuilder,
{"text": "test", "style": "success"},
KeyboardButton(text="test", style="success"),
],
[
ReplyKeyboardBuilder,
{"text": "test", "icon_custom_emoji_id": "emoji-id", "style": "success"},
KeyboardButton(
text="test",
icon_custom_emoji_id="emoji-id",
style="success",
),
],
[
InlineKeyboardBuilder,
{"text": "test", "callback_data": "callback"},
InlineKeyboardButton(text="test", callback_data="callback"),
],
[
InlineKeyboardBuilder,
{
"text": "test",
"icon_custom_emoji_id": "emoji-id",
"callback_data": "callback",
},
InlineKeyboardButton(
text="test",
icon_custom_emoji_id="emoji-id",
callback_data="callback",
),
],
[
InlineKeyboardBuilder,
{"text": "test", "style": "primary", "callback_data": "callback"},
InlineKeyboardButton(text="test", style="primary", callback_data="callback"),
],
[
InlineKeyboardBuilder,
{
"text": "test",
"icon_custom_emoji_id": "emoji-id",
"style": "primary",
"callback_data": "callback",
},
InlineKeyboardButton(
text="test",
icon_custom_emoji_id="emoji-id",
style="primary",
callback_data="callback",
),
],
[
InlineKeyboardBuilder,
{"text": "test", "callback_data": MyCallback(value="test")},
@ -242,6 +294,45 @@ class TestKeyboardBuilder:
def test_as_markup(self, builder, expected):
assert isinstance(builder.as_markup(), expected)
@pytest.mark.parametrize(
"builder,button_kwargs,icon_custom_emoji_id,style",
[
[
ReplyKeyboardBuilder(),
{"text": "test", "icon_custom_emoji_id": "emoji-id", "style": "success"},
"emoji-id",
"success",
],
[
InlineKeyboardBuilder(),
{
"text": "test",
"icon_custom_emoji_id": "emoji-id",
"style": "primary",
"callback_data": "callback",
},
"emoji-id",
"primary",
],
],
)
def test_as_markup_preserves_icon_and_style(
self,
builder,
button_kwargs,
icon_custom_emoji_id,
style,
):
builder.button(**button_kwargs)
markup = builder.as_markup()
if isinstance(markup, ReplyKeyboardMarkup):
button = markup.keyboard[0][0]
else:
button = markup.inline_keyboard[0][0]
assert button.icon_custom_emoji_id == icon_custom_emoji_id
assert button.style == style
@pytest.mark.parametrize(
"markup,builder_type",
[

View file

@ -1,3 +1,5 @@
from datetime import datetime, timezone
import pytest
from aiogram.types import MessageEntity, User
@ -25,7 +27,7 @@ class TestTextDecoration:
[
html_decoration,
MessageEntity(type="pre", offset=0, length=5, language="python"),
'<pre><code class="language-python">test</code></pre>',
'<pre><code language="language-python">test</code></pre>',
],
[html_decoration, MessageEntity(type="underline", offset=0, length=5), "<u>test</u>"],
[
@ -57,7 +59,7 @@ class TestTextDecoration:
[
html_decoration,
MessageEntity(type="custom_emoji", offset=0, length=5, custom_emoji_id="42"),
'<tg-emoji emoji-id="42">test</tg-emoji>',
'<tg-emoji emoji_id="42">test</tg-emoji>',
],
[
html_decoration,
@ -74,6 +76,17 @@ class TestTextDecoration:
MessageEntity(type="expandable_blockquote", offset=0, length=5),
"<blockquote expandable>test</blockquote>",
],
[
html_decoration,
MessageEntity(
type="date_time",
offset=0,
length=5,
unix_time=42,
date_time_format="yMd",
),
'<tg-time unix="42" format="yMd">test</tg-time>',
],
[markdown_decoration, MessageEntity(type="bold", offset=0, length=5), "*test*"],
[markdown_decoration, MessageEntity(type="italic", offset=0, length=5), "_\rtest_\r"],
[markdown_decoration, MessageEntity(type="code", offset=0, length=5), "`test`"],
@ -102,7 +115,7 @@ class TestTextDecoration:
[
markdown_decoration,
MessageEntity(type="custom_emoji", offset=0, length=5, custom_emoji_id="42"),
"![test](tg://emoji?id=42)",
"![test](tg://emoji?emoji_id=42)",
],
[
markdown_decoration,
@ -124,6 +137,27 @@ class TestTextDecoration:
MessageEntity(type="expandable_blockquote", offset=0, length=5),
">test||",
],
[
markdown_decoration,
MessageEntity(
type="date_time",
offset=0,
length=5,
unix_time=42,
date_time_format="yMd",
),
"![test](tg://time?unix=42&format=yMd)",
],
[
html_decoration,
MessageEntity(type="date_time", offset=0, length=5, unix_time=42),
'<tg-time unix="42">test</tg-time>',
],
[
markdown_decoration,
MessageEntity(type="date_time", offset=0, length=5, unix_time=42),
"![test](tg://time?unix=42)",
],
],
)
def test_apply_single_entity(
@ -131,6 +165,38 @@ class TestTextDecoration:
):
assert decorator.apply_entity(entity, "test") == result
@pytest.mark.parametrize(
"decorator,date_time_format,expected",
[
(
html_decoration,
None,
'<tg-time unix="1704067200">test</tg-time>',
),
(
html_decoration,
"yMd",
'<tg-time unix="1704067200" format="yMd">test</tg-time>',
),
(
markdown_decoration,
None,
"![test](tg://time?unix=1704067200)",
),
(
markdown_decoration,
"yMd",
"![test](tg://time?unix=1704067200&format=yMd)",
),
],
)
def test_date_time_with_datetime_object(
self, decorator: TextDecoration, date_time_format: str | None, expected: str
):
dt = datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc)
result = decorator.date_time("test", unix_time=dt, date_time_format=date_time_format)
assert result == expected
def test_unknown_apply_entity(self):
assert (
html_decoration.apply_entity(
@ -296,6 +362,22 @@ class TestTextDecoration:
],
"<b>test@example.com</b>",
],
[
html_decoration,
"test",
[MessageEntity(type="date_time", offset=0, length=4, unix_time=42)],
'<tg-time unix="42">test</tg-time>',
],
[
html_decoration,
"test",
[
MessageEntity(
type="date_time", offset=0, length=4, unix_time=42, date_time_format="yMd"
)
],
'<tg-time unix="42" format="yMd">test</tg-time>',
],
],
)
def test_unparse(