mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Merge branch 'dev-3.x'
This commit is contained in:
commit
4f435ae39b
62 changed files with 1296 additions and 83 deletions
|
|
@ -1 +1 @@
|
|||
9.4
|
||||
9.5
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ extract:
|
|||
- reply_to_story
|
||||
- business_connection_id
|
||||
- sender_business_bot
|
||||
- sender_tag
|
||||
- is_from_offline
|
||||
- has_media_spoiler
|
||||
- effect_id
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
8
.butcher/methods/promoteChatMember/entity.json
generated
8
.butcher/methods/promoteChatMember/entity.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
4
.butcher/methods/sendChecklist/entity.json
generated
4
.butcher/methods/sendChecklist/entity.json
generated
|
|
@ -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"
|
||||
}
|
||||
],
|
||||
|
|
|
|||
6
.butcher/methods/sendMessageDraft/entity.json
generated
6
.butcher/methods/sendMessageDraft/entity.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
41
.butcher/methods/setChatMemberTag/entity.json
generated
Normal file
41
.butcher/methods/setChatMemberTag/entity.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
158
.butcher/schema/schema.json
generated
158
.butcher/schema/schema.json
generated
|
|
@ -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 “mention” (<code>@username</code>), “hashtag” (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), “cashtag” (<code>$USD</code> or <code>$USD@chatusername</code>), “bot_command” (<code>/start@jobs_bot</code>), “url” (<code>https://telegram.org</code>), “email” (<code>do-not-reply@telegram.org</code>), “phone_number” (<code>+1-212-555-0123</code>), “bold” (<strong>bold text</strong>), “italic” (<em>italic text</em>), “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 <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), “custom_emoji” (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 “mention” (<code>@username</code>), “hashtag” (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), “cashtag” (<code>$USD</code> or <code>$USD@chatusername</code>), “bot_command” (<code>/start@jobs_bot</code>), “url” (<code>https://telegram.org</code>), “email” (<code>do-not-reply@telegram.org</code>), “phone_number” (<code>+1-212-555-0123</code>), “bold” (<strong>bold text</strong>), “italic” (<em>italic text</em>), “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 <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), “custom_emoji” (for inline custom emoji stickers), or “date_time” (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 “date_time” 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 “date_time” 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 »</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 »</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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
8
.butcher/types/ChatMemberMember/entity.json
generated
8
.butcher/types/ChatMemberMember/entity.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
16
.butcher/types/ChatMemberRestricted/entity.json
generated
16
.butcher/types/ChatMemberRestricted/entity.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
8
.butcher/types/ChatPermissions/entity.json
generated
8
.butcher/types/ChatPermissions/entity.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
6
.butcher/types/GameHighScore/entity.json
generated
6
.butcher/types/GameHighScore/entity.json
generated
|
|
@ -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 »</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 »</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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
18
.butcher/types/Message/entity.json
generated
18
.butcher/types/Message/entity.json
generated
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
22
.butcher/types/MessageEntity/entity.json
generated
22
.butcher/types/MessageEntity/entity.json
generated
|
|
@ -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 “mention” (<code>@username</code>), “hashtag” (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), “cashtag” (<code>$USD</code> or <code>$USD@chatusername</code>), “bot_command” (<code>/start@jobs_bot</code>), “url” (<code>https://telegram.org</code>), “email” (<code>do-not-reply@telegram.org</code>), “phone_number” (<code>+1-212-555-0123</code>), “bold” (<strong>bold text</strong>), “italic” (<em>italic text</em>), “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 <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), “custom_emoji” (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 “mention” (<code>@username</code>), “hashtag” (<code>#hashtag</code> or <code>#hashtag@chatusername</code>), “cashtag” (<code>$USD</code> or <code>$USD@chatusername</code>), “bot_command” (<code>/start@jobs_bot</code>), “url” (<code>https://telegram.org</code>), “email” (<code>do-not-reply@telegram.org</code>), “phone_number” (<code>+1-212-555-0123</code>), “bold” (<strong>bold text</strong>), “italic” (<em>italic text</em>), “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 <a href=\"https://telegram.org/blog/edit#new-mentions\">without usernames</a>), “custom_emoji” (for inline custom emoji stickers), or “date_time” (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 “date_time” 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 “date_time” 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
120
AGENTS.md
Normal 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.
|
||||
37
CHANGES.rst
37
CHANGES.rst
|
|
@ -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
3
CLAUDE.md
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
__version__ = "3.25.0"
|
||||
__api_version__ = "9.4"
|
||||
__version__ = "3.26.0"
|
||||
__api_version__ = "9.5"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -27,3 +27,4 @@ class MessageEntityType(str, Enum):
|
|||
TEXT_LINK = "text_link"
|
||||
TEXT_MENTION = "text_mention"
|
||||
CUSTOM_EMOJI = "custom_emoji"
|
||||
DATE_TIME = "date_time"
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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!!!
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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!!!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
|
|
|||
40
aiogram/methods/set_chat_member_tag.py
Normal file
40
aiogram/methods/set_chat_member_tag.py
Normal 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)
|
||||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
45
docs/api/methods/set_chat_member_tag.rst
Normal file
45
docs/api/methods/set_chat_member_tag.rst
Normal 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(...)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
14
tests/test_api/test_methods/test_set_chat_member_tag.py
Normal file
14
tests/test_api/test_methods/test_set_chat_member_tag.py
Normal 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
|
||||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
106
tests/test_issues/test_1687_scene_goto_loses_middleware_data.py
Normal file
106
tests/test_issues/test_1687_scene_goto_loses_middleware_data.py
Normal 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"
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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() == ""
|
||||
|
||||
def test_as_line(self):
|
||||
node = as_line("test", "test", "test")
|
||||
assert isinstance(node, Text)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
"",
|
||||
"",
|
||||
],
|
||||
[
|
||||
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",
|
||||
),
|
||||
"",
|
||||
],
|
||||
[
|
||||
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),
|
||||
"",
|
||||
],
|
||||
],
|
||||
)
|
||||
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,
|
||||
"",
|
||||
),
|
||||
(
|
||||
markdown_decoration,
|
||||
"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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue